aboutsummaryrefslogtreecommitdiff
path: root/Tests
diff options
context:
space:
mode:
Diffstat (limited to 'Tests')
-rw-r--r--Tests/afmLib/afmLib_test.py94
-rw-r--r--Tests/agl_test.py4
-rw-r--r--Tests/cffLib/cffLib_test.py35
-rw-r--r--Tests/cffLib/data/TestCFF2Widths.ttx1
-rw-r--r--Tests/cffLib/data/TestSparseCFF2VF.ttx1
-rw-r--r--Tests/cffLib/specializer_test.py783
-rw-r--r--Tests/cu2qu/cli_test.py26
-rw-r--r--Tests/cu2qu/cu2qu_test.py94
-rw-r--r--Tests/cu2qu/ufo_test.py115
-rw-r--r--Tests/designspaceLib/data/test_avar2.designspace117
-rw-r--r--Tests/designspaceLib/data/test_v5.designspace7
-rw-r--r--Tests/designspaceLib/designspace_test.py401
-rw-r--r--Tests/designspaceLib/designspace_v5_test.py9
-rw-r--r--Tests/designspaceLib/split_test.py89
-rw-r--r--Tests/designspaceLib/statNames_test.py22
-rw-r--r--Tests/encodings/codecs_test.py38
-rw-r--r--Tests/feaLib/ast_test.py1
-rw-r--r--Tests/feaLib/builder_test.py170
-rw-r--r--Tests/feaLib/data/GPOS_1_zero.ttx2
-rw-r--r--Tests/feaLib/data/GSUB_2.fea21
-rw-r--r--Tests/feaLib/data/GSUB_2.ttx121
-rw-r--r--Tests/feaLib/data/GSUB_5_formats.fea12
-rw-r--r--Tests/feaLib/data/PairPosSubtable.ttx16
-rw-r--r--Tests/feaLib/data/STAT_test.ttx6
-rw-r--r--Tests/feaLib/data/bug2949.fea20
-rw-r--r--Tests/feaLib/data/bug2949.ttx133
-rw-r--r--Tests/feaLib/data/bug509.fea2
-rw-r--r--Tests/feaLib/data/bug512.ttx3
-rw-r--r--Tests/feaLib/data/bug633.ttx12
-rw-r--r--Tests/feaLib/data/name.ttx18
-rw-r--r--Tests/feaLib/data/spec5f_ii_3.ttx22
-rw-r--r--Tests/feaLib/data/spec8b.ttx8
-rw-r--r--Tests/feaLib/data/spec8c.ttx14
-rw-r--r--Tests/feaLib/data/spec8d.ttx28
-rw-r--r--Tests/feaLib/data/spec9e.ttx4
-rw-r--r--Tests/feaLib/data/variable_bug2772.fea4
-rw-r--r--Tests/feaLib/data/variable_bug2772.ttx103
-rw-r--r--Tests/feaLib/data/variable_scalar_valuerecord.fea1
-rw-r--r--Tests/feaLib/data/variable_scalar_valuerecord.ttx9
-rw-r--r--Tests/feaLib/error_test.py1
-rw-r--r--Tests/feaLib/lexer_test.py171
-rw-r--r--Tests/feaLib/parser_test.py68
-rw-r--r--Tests/fontBuilder/data/test_var.otf.ttx10
-rw-r--r--Tests/fontBuilder/fontBuilder_test.py202
-rw-r--r--Tests/merge/data/CFFFont_expected.ttx558
-rw-r--r--Tests/merge/merge_test.py393
-rw-r--r--Tests/misc/arrayTools_test.py38
-rw-r--r--Tests/misc/bezierTools_test.py179
-rw-r--r--Tests/misc/classifyTools_test.py37
-rw-r--r--Tests/misc/eexec_test.py4
-rw-r--r--Tests/misc/encodingTools_test.py43
-rw-r--r--Tests/misc/filenames_test.py245
-rw-r--r--Tests/misc/fixedTools_test.py60
-rw-r--r--Tests/misc/loggingTools_test.py73
-rw-r--r--Tests/misc/macRes_test.py132
-rw-r--r--Tests/misc/plistlib_test.py67
-rw-r--r--Tests/misc/psCharStrings_test.py123
-rw-r--r--Tests/misc/py23_test.py722
-rw-r--r--Tests/misc/testTools_test.py109
-rw-r--r--Tests/misc/textTools_test.py10
-rw-r--r--Tests/misc/timeTools_test.py12
-rw-r--r--Tests/misc/transform_test.py107
-rw-r--r--Tests/misc/treeTools_test.py2
-rw-r--r--Tests/misc/visitor_test.py1
-rw-r--r--Tests/misc/xmlReader_test.py339
-rw-r--r--Tests/misc/xmlWriter_test.py260
-rw-r--r--Tests/mtiLib/data/featurename-backward.ttx.GSUB1
-rw-r--r--Tests/mtiLib/data/featurename-forward.ttx.GSUB1
-rw-r--r--Tests/mtiLib/data/lookupnames-backward.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/lookupnames-forward.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mixed-toplevels.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS2
-rw-r--r--Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/chainedclass.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB2
-rw-r--r--Tests/mtiLib/data/mti/gposcursive.ttx.GPOS1
-rw-r--r--Tests/mtiLib/data/mti/gposkernset.ttx.GPOS1
-rw-r--r--Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS1
-rw-r--r--Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS1
-rw-r--r--Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS1
-rw-r--r--Tests/mtiLib/data/mti/gpossingle.ttx.GPOS1
-rw-r--r--Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB1
-rw-r--r--Tests/mtiLib/data/mti/gsubligature.ttx.GSUB1
-rw-r--r--Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB1
-rw-r--r--Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB1
-rw-r--r--Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB1
-rw-r--r--Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS1
-rw-r--r--Tests/mtiLib/mti_test.py565
-rw-r--r--Tests/otlLib/builder_test.py2
-rw-r--r--Tests/otlLib/maxContextCalc_test.py27
-rw-r--r--Tests/otlLib/mock_builder_test.py8
-rw-r--r--Tests/pens/__init__.py11
-rw-r--r--Tests/pens/areaPen_test.py195
-rw-r--r--Tests/pens/basePen_test.py78
-rw-r--r--Tests/pens/boundsPen_test.py3
-rw-r--r--Tests/pens/cocoaPen_test.py13
-rw-r--r--Tests/pens/cu2quPen_test.py386
-rw-r--r--Tests/pens/perimeterPen_test.py195
-rw-r--r--Tests/pens/pointInsidePen_test.py190
-rw-r--r--Tests/pens/pointPen_test.py253
-rw-r--r--Tests/pens/qu2cuPen_test.py253
-rw-r--r--Tests/pens/quartzPen_test.py15
-rw-r--r--Tests/pens/reverseContourPen_test.py589
-rw-r--r--Tests/pens/t2CharStringPen_test.py225
-rw-r--r--Tests/pens/ttGlyphPen_test.py256
-rw-r--r--Tests/pens/utils.py62
-rw-r--r--Tests/qu2cu/data/NotoSansArabic-Regular.quadratic.subset.ttfbin0 -> 2612 bytes
-rw-r--r--Tests/qu2cu/qu2cu_cli_test.py62
-rw-r--r--Tests/qu2cu/qu2cu_test.py104
-rw-r--r--Tests/subset/data/NotoSansCJKjp-Regular.subset.ttx417
-rw-r--r--Tests/subset/data/TestGVAR.ttx1
-rw-r--r--Tests/subset/data/TestHVVAR.ttx1
-rw-r--r--Tests/subset/data/expect_HVVAR.ttx1
-rw-r--r--Tests/subset/data/expect_HVVAR_retain_gids.ttx1
-rw-r--r--Tests/subset/data/expect_keep_gvar.ttx1
-rw-r--r--Tests/subset/data/expect_keep_gvar_notdef_outline.ttx1
-rw-r--r--Tests/subset/subset_test.py829
-rw-r--r--Tests/svgLib/path/parser_test.py170
-rw-r--r--Tests/svgLib/path/path_test.py24
-rw-r--r--Tests/svgLib/path/shapes_test.py76
-rw-r--r--Tests/t1Lib/t1Lib_test.py335
-rw-r--r--Tests/ttLib/data/I-512upem.ttx3
-rw-r--r--Tests/ttLib/data/I.otfbin0 -> 3716 bytes
-rw-r--r--Tests/ttLib/data/TestOTF-Regular.otx4
-rw-r--r--Tests/ttLib/data/TestTTF-Regular.ttx4
-rw-r--r--Tests/ttLib/data/TestTTF_normalizeLocation.ttx28
-rw-r--r--Tests/ttLib/data/bogus_post_format_1.ttfbin0 -> 3840 bytes
-rw-r--r--Tests/ttLib/data/dot-cubic.ttfbin0 -> 476 bytes
-rw-r--r--Tests/ttLib/data/issue2824.ttfbin0 -> 1216 bytes
-rw-r--r--Tests/ttLib/data/varc-6868.ttfbin0 -> 10848 bytes
-rw-r--r--Tests/ttLib/data/varc-ac00-ac01-500upem.ttx2055
-rw-r--r--Tests/ttLib/data/varc-ac00-ac01.ttfbin0 -> 4808 bytes
-rw-r--r--Tests/ttLib/main_test.py105
-rw-r--r--Tests/ttLib/scaleUpem_test.py22
-rw-r--r--Tests/ttLib/sfnt_test.py39
-rw-r--r--Tests/ttLib/tables/C_F_F__2_test.py22
-rw-r--r--Tests/ttLib/tables/C_F_F_test.py18
-rw-r--r--Tests/ttLib/tables/C_O_L_R_test.py124
-rw-r--r--Tests/ttLib/tables/C_P_A_L_test.py282
-rw-r--r--Tests/ttLib/tables/M_V_A_R_test.py149
-rw-r--r--Tests/ttLib/tables/O_S_2f_2_test.py105
-rw-r--r--Tests/ttLib/tables/S_T_A_T_test.py236
-rw-r--r--Tests/ttLib/tables/T_S_I__0_test.py44
-rw-r--r--Tests/ttLib/tables/T_S_I__1_test.py111
-rw-r--r--Tests/ttLib/tables/TupleVariation_test.py1843
-rw-r--r--Tests/ttLib/tables/_a_n_k_r_test.py142
-rw-r--r--Tests/ttLib/tables/_a_v_a_r_test.py133
-rw-r--r--Tests/ttLib/tables/_b_s_l_n_test.py135
-rw-r--r--Tests/ttLib/tables/_c_i_d_g_test.py70
-rw-r--r--Tests/ttLib/tables/_c_m_a_p_test.py322
-rw-r--r--Tests/ttLib/tables/_c_v_a_r_test.py63
-rw-r--r--Tests/ttLib/tables/_f_v_a_r_test.py153
-rw-r--r--Tests/ttLib/tables/_g_c_i_d_test.py68
-rw-r--r--Tests/ttLib/tables/_g_l_y_f_test.py632
-rw-r--r--Tests/ttLib/tables/_g_v_a_r_test.py301
-rw-r--r--Tests/ttLib/tables/_h_h_e_a_test.py140
-rw-r--r--Tests/ttLib/tables/_h_m_t_x_test.py109
-rw-r--r--Tests/ttLib/tables/_k_e_r_n_test.py264
-rw-r--r--Tests/ttLib/tables/_l_c_a_r_test.py88
-rw-r--r--Tests/ttLib/tables/_l_t_a_g_test.py111
-rw-r--r--Tests/ttLib/tables/_m_e_t_a_test.py64
-rw-r--r--Tests/ttLib/tables/_m_o_r_t_test.py99
-rw-r--r--Tests/ttLib/tables/_m_o_r_x_test.py873
-rw-r--r--Tests/ttLib/tables/_n_a_m_e_test.py1150
-rw-r--r--Tests/ttLib/tables/_o_p_b_d_test.py184
-rw-r--r--Tests/ttLib/tables/_p_r_o_p_test.py54
-rw-r--r--Tests/ttLib/tables/_t_r_a_k_test.py602
-rw-r--r--Tests/ttLib/tables/_v_h_e_a_test.py182
-rw-r--r--Tests/ttLib/tables/_v_m_t_x_test.py2
-rw-r--r--Tests/ttLib/tables/data/COLRv1-clip-boxes-cff.ttx1213
-rw-r--r--Tests/ttLib/tables/data/COLRv1-clip-boxes-glyf.ttx1414
-rw-r--r--Tests/ttLib/tables/data/COLRv1-clip-boxes-q1-expected.ttx919
-rw-r--r--Tests/ttLib/tables/data/COLRv1-clip-boxes-q10-expected.ttx911
-rw-r--r--Tests/ttLib/tables/data/COLRv1-clip-boxes-q100-expected.ttx863
-rw-r--r--Tests/ttLib/tables/data/NotoSans-VF-cubic.subset.ttfbin0 -> 4248 bytes
-rw-r--r--Tests/ttLib/tables/data/_g_l_y_f_instructions.ttx82
-rw-r--r--Tests/ttLib/tables/otBase_test.py4
-rw-r--r--Tests/ttLib/tables/otConverters_test.py382
-rw-r--r--Tests/ttLib/tables/otTables_test.py456
-rw-r--r--Tests/ttLib/tables/tables_test.py449
-rw-r--r--Tests/ttLib/tables/ttProgram_test.py136
-rw-r--r--Tests/ttLib/ttFont_test.py114
-rw-r--r--Tests/ttLib/ttGlyphSet_test.py658
-rw-r--r--Tests/ttLib/ttVisitor_test.py2
-rw-r--r--Tests/ttLib/woff2_test.py2683
-rw-r--r--Tests/ttx/data/TestOTF.ttx4
-rw-r--r--Tests/ttx/data/TestTTF.ttx4
-rw-r--r--Tests/ttx/data/roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx224
-rw-r--r--Tests/ttx/ttx_test.py67
-rw-r--r--Tests/ufoLib/GLIF1_test.py908
-rw-r--r--Tests/ufoLib/GLIF2_test.py1370
-rw-r--r--Tests/ufoLib/UFO1_test.py254
-rw-r--r--Tests/ufoLib/UFO2_test.py2946
-rw-r--r--Tests/ufoLib/UFO3_test.py9680
-rw-r--r--Tests/ufoLib/UFOConversion_test.py633
-rw-r--r--Tests/ufoLib/UFOZ_test.py8
-rw-r--r--Tests/ufoLib/__init__.py3
-rw-r--r--Tests/ufoLib/filenames_test.py27
-rw-r--r--Tests/ufoLib/glifLib_test.py527
-rwxr-xr-xTests/ufoLib/testSupport.py1226
-rw-r--r--Tests/unicodedata_test.py316
-rw-r--r--Tests/varLib/builder_test.py191
-rw-r--r--Tests/varLib/data/BuildAvar2.designspace55
-rw-r--r--Tests/varLib/data/DropOnCurves.designspace20
-rw-r--r--Tests/varLib/data/InterpolateLayout.glyphs2402
-rw-r--r--Tests/varLib/data/SparseCFF2.designspace23
-rw-r--r--Tests/varLib/data/SparseMasters.glyphs486
-rw-r--r--Tests/varLib/data/SparseMasters_ufo.designspace23
-rw-r--r--Tests/varLib/data/TestNoOverwriteSTAT.designspace36
-rw-r--r--Tests/varLib/data/master_no_overwrite_stat/Test-CondensedBlack.ttx243
-rw-r--r--Tests/varLib/data/master_no_overwrite_stat/Test-CondensedThin.ttx373
-rw-r--r--Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedBlack.ttx243
-rw-r--r--Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedThin.ttx243
-rw-r--r--Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Bold.ttx302
-rw-r--r--Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Medium.ttx100
-rw-r--r--Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Regular.ttx302
-rw-r--r--Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx312
-rw-r--r--Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx313
-rw-r--r--Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx1
-rw-r--r--Tests/varLib/data/master_ttx_varfont_ttf/SparseMasters-VF.ttx501
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/fontinfo.plist20
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/_notdef.glif18
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/a.glif29
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/contents.plist18
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/dotabovecomb.glif12
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/e.glif22
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/edotabove.glif9
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/s.glif21
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/layercontents.plist10
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/lib.plist15
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/metainfo.plist10
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/fontinfo.plist20
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/_notdef.glif18
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/contents.plist10
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/e.glif21
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/layercontents.plist10
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/lib.plist11
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/metainfo.plist10
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/fontinfo.plist20
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/_notdef.glif18
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/a.glif29
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/contents.plist18
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/dotabovecomb.glif12
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/e.glif22
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/edotabove.glif9
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/s.glif21
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/layercontents.plist10
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/lib.plist15
-rw-r--r--Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/metainfo.plist10
-rw-r--r--Tests/varLib/data/test_results/Build.ttx99
-rw-r--r--Tests/varLib/data/test_results/BuildAvar2.ttx41
-rw-r--r--Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx1
-rw-r--r--Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx1
-rw-r--r--Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx1
-rw-r--r--Tests/varLib/data/test_results/BuildMain.ttx99
-rw-r--r--Tests/varLib/data/test_results/DropOnCurves.ttx498
-rw-r--r--Tests/varLib/data/test_results/FeatureVars_rclt.ttx2
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx116
-rw-r--r--Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx116
-rw-r--r--Tests/varLib/data/test_results/SparseCFF2-VF.ttx157
-rw-r--r--Tests/varLib/data/test_results/SparseMasters.ttx25
-rw-r--r--Tests/varLib/data/test_results/TestSparseCFF2VF.ttx808
-rw-r--r--Tests/varLib/data/test_results/TestVVAR.ttx11
-rw-r--r--Tests/varLib/featureVars_test.py93
-rw-r--r--Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx17
-rw-r--r--Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx1
-rw-r--r--Tests/varLib/instancer/data/STATInstancerTest.ttx1
-rw-r--r--Tests/varLib/instancer/data/SinglePos.ttx1
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx2
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx2
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx2
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx2
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx2
-rw-r--r--Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx2
-rw-r--r--Tests/varLib/instancer/instancer_test.py364
-rw-r--r--Tests/varLib/instancer/names_test.py37
-rw-r--r--Tests/varLib/instancer/solver_test.py300
-rw-r--r--Tests/varLib/interpolatable_test.py192
-rw-r--r--Tests/varLib/interpolate_layout_test.py655
-rw-r--r--Tests/varLib/iup_test.py114
-rw-r--r--Tests/varLib/merger_test.py100
-rw-r--r--Tests/varLib/models_test.py123
-rw-r--r--Tests/varLib/mutator_test.py88
-rw-r--r--Tests/varLib/stat_test.py24
-rw-r--r--Tests/varLib/varLib_test.py343
-rw-r--r--Tests/varLib/varStore_test.py206
-rw-r--r--Tests/voltLib/data/Empty.ttfbin0 -> 1432 bytes
-rw-r--r--Tests/voltLib/data/NamdhinggoSIL1006.fea506
-rw-r--r--Tests/voltLib/data/NamdhinggoSIL1006.vtp1
-rw-r--r--Tests/voltLib/data/Nutso.fea328
-rw-r--r--Tests/voltLib/data/Nutso.ttfbin0 -> 29456 bytes
-rw-r--r--Tests/voltLib/data/Nutso.vtp1
-rw-r--r--Tests/voltLib/lexer_test.py15
-rw-r--r--Tests/voltLib/parser_test.py1219
-rw-r--r--Tests/voltLib/volttofea_test.py1253
295 files changed, 45480 insertions, 20653 deletions
diff --git a/Tests/afmLib/afmLib_test.py b/Tests/afmLib/afmLib_test.py
index 3e9d9d88..e3640819 100644
--- a/Tests/afmLib/afmLib_test.py
+++ b/Tests/afmLib/afmLib_test.py
@@ -4,50 +4,56 @@ from fontTools import afmLib
CWD = os.path.abspath(os.path.dirname(__file__))
-DATADIR = os.path.join(CWD, 'data')
-AFM = os.path.join(DATADIR, 'TestAFM.afm')
+DATADIR = os.path.join(CWD, "data")
+AFM = os.path.join(DATADIR, "TestAFM.afm")
class AFMTest(unittest.TestCase):
-
- def test_read_afm(self):
- afm = afmLib.AFM(AFM)
- self.assertEqual(sorted(afm.kernpairs()),
- sorted([('V', 'A'), ('T', 'comma'), ('V', 'd'), ('T', 'c'), ('T', 'period')]))
- self.assertEqual(afm['V', 'A'], -60)
- self.assertEqual(afm['V', 'd'], 30)
- self.assertEqual(afm['A'], (65, 668, (8, -25, 660, 666)))
-
- def test_write_afm(self):
- afm = afmLib.AFM(AFM)
- newAfm, afmData = self.write(afm)
- self.assertEqual(afm.kernpairs(), newAfm.kernpairs())
- self.assertEqual(afm.chars(), newAfm.chars())
- self.assertEqual(afm.comments(), newAfm.comments()[1:]) # skip the "generated by afmLib" comment
- for pair in afm.kernpairs():
- self.assertEqual(afm[pair], newAfm[pair])
- for char in afm.chars():
- self.assertEqual(afm[char], newAfm[char])
- with open(AFM, 'r') as f:
- originalLines = f.read().splitlines()
- newLines = afmData.splitlines()
- del newLines[1] # remove the "generated by afmLib" comment
- self.assertEqual(originalLines, newLines)
-
- @staticmethod
- def write(afm, sep='\r'):
- temp = os.path.join(DATADIR, 'temp.afm')
- try:
- afm.write(temp, sep)
- with open(temp, 'r') as f:
- afmData = f.read()
- afm = afmLib.AFM(temp)
- finally:
- if os.path.exists(temp):
- os.remove(temp)
- return afm, afmData
-
-
-if __name__ == '__main__':
- import sys
- sys.exit(unittest.main())
+ def test_read_afm(self):
+ afm = afmLib.AFM(AFM)
+ self.assertEqual(
+ sorted(afm.kernpairs()),
+ sorted(
+ [("V", "A"), ("T", "comma"), ("V", "d"), ("T", "c"), ("T", "period")]
+ ),
+ )
+ self.assertEqual(afm["V", "A"], -60)
+ self.assertEqual(afm["V", "d"], 30)
+ self.assertEqual(afm["A"], (65, 668, (8, -25, 660, 666)))
+
+ def test_write_afm(self):
+ afm = afmLib.AFM(AFM)
+ newAfm, afmData = self.write(afm)
+ self.assertEqual(afm.kernpairs(), newAfm.kernpairs())
+ self.assertEqual(afm.chars(), newAfm.chars())
+ self.assertEqual(
+ afm.comments(), newAfm.comments()[1:]
+ ) # skip the "generated by afmLib" comment
+ for pair in afm.kernpairs():
+ self.assertEqual(afm[pair], newAfm[pair])
+ for char in afm.chars():
+ self.assertEqual(afm[char], newAfm[char])
+ with open(AFM, "r") as f:
+ originalLines = f.read().splitlines()
+ newLines = afmData.splitlines()
+ del newLines[1] # remove the "generated by afmLib" comment
+ self.assertEqual(originalLines, newLines)
+
+ @staticmethod
+ def write(afm, sep="\r"):
+ temp = os.path.join(DATADIR, "temp.afm")
+ try:
+ afm.write(temp, sep)
+ with open(temp, "r") as f:
+ afmData = f.read()
+ afm = afmLib.AFM(temp)
+ finally:
+ if os.path.exists(temp):
+ os.remove(temp)
+ return afm, afmData
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/agl_test.py b/Tests/agl_test.py
index f2fb72d0..d48c2b64 100644
--- a/Tests/agl_test.py
+++ b/Tests/agl_test.py
@@ -12,7 +12,8 @@ class AglToUnicodeTest(unittest.TestCase):
self.assertEqual(agl.toUnicode("uni20ac"), "")
self.assertEqual(
agl.toUnicode("Lcommaaccent_uni20AC0308_u1040C.alternate"),
- "\u013B\u20AC\u0308\U0001040C")
+ "\u013B\u20AC\u0308\U0001040C",
+ )
self.assertEqual(agl.toUnicode("Lcommaaccent_uni013B_u013B"), "ĻĻĻ")
self.assertEqual(agl.toUnicode("foo"), "")
self.assertEqual(agl.toUnicode(".notdef"), "")
@@ -55,4 +56,5 @@ class AglToUnicodeTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/cffLib/cffLib_test.py b/Tests/cffLib/cffLib_test.py
index 7a6e9216..2d4d3023 100644
--- a/Tests/cffLib/cffLib_test.py
+++ b/Tests/cffLib/cffLib_test.py
@@ -8,11 +8,14 @@ import unittest
class CffLibTest(DataFilesHandler):
-
def test_topDict_recalcFontBBox(self):
topDict = TopDict()
topDict.CharStrings = CharStrings(None, None, None, PrivateDict(), None, None)
- topDict.CharStrings.fromXML(None, None, parseXML("""
+ topDict.CharStrings.fromXML(
+ None,
+ None,
+ parseXML(
+ """
<CharString name=".notdef">
endchar
</CharString>
@@ -25,7 +28,9 @@ class CffLibTest(DataFilesHandler):
<CharString name="baz"><!-- [-55.1, -55.1, 55.1, 55.1] -->
-55.1 -55.1 rmoveto 110.2 hlineto 110.2 vlineto -110.2 hlineto endchar
</CharString>
- """))
+ """
+ ),
+ )
topDict.recalcFontBBox()
self.assertEqual(topDict.FontBBox, [-56, -100, 300, 200])
@@ -33,20 +38,26 @@ class CffLibTest(DataFilesHandler):
def test_topDict_recalcFontBBox_empty(self):
topDict = TopDict()
topDict.CharStrings = CharStrings(None, None, None, PrivateDict(), None, None)
- topDict.CharStrings.fromXML(None, None, parseXML("""
+ topDict.CharStrings.fromXML(
+ None,
+ None,
+ parseXML(
+ """
<CharString name=".notdef">
endchar
</CharString>
<CharString name="space">
123 endchar
</CharString>
- """))
+ """
+ ),
+ )
topDict.recalcFontBBox()
self.assertEqual(topDict.FontBBox, [0, 0, 0, 0])
def test_topDict_set_Encoding(self):
- ttx_path = self.getpath('TestOTF.ttx')
+ ttx_path = self.getpath("TestOTF.ttx")
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.importXML(ttx_path)
@@ -54,9 +65,9 @@ class CffLibTest(DataFilesHandler):
encoding = [".notdef"] * 256
encoding[0x20] = "space"
topDict.Encoding = encoding
-
+
self.temp_dir()
- save_path = os.path.join(self.tempdir, 'TestOTF.otf')
+ save_path = os.path.join(self.tempdir, "TestOTF.otf")
font.save(save_path)
font2 = TTFont(save_path)
@@ -79,12 +90,12 @@ class CffLibTest(DataFilesHandler):
copy.deepcopy(font)
def test_FDSelect_format_4(self):
- ttx_path = self.getpath('TestFDSelect4.ttx')
+ ttx_path = self.getpath("TestFDSelect4.ttx")
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.importXML(ttx_path)
self.temp_dir()
- save_path = os.path.join(self.tempdir, 'TestOTF.otf')
+ save_path = os.path.join(self.tempdir, "TestOTF.otf")
font.save(save_path)
font2 = TTFont(save_path)
@@ -93,14 +104,14 @@ class CffLibTest(DataFilesHandler):
self.assertEqual(topDict2.FDSelect.gidArray, [0, 0, 1])
def test_unique_glyph_names(self):
- font_path = self.getpath('LinLibertine_RBI.otf')
+ 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')
+ save_path = os.path.join(self.tempdir, "TestOTF.otf")
font.save(save_path)
font2 = TTFont(save_path)
diff --git a/Tests/cffLib/data/TestCFF2Widths.ttx b/Tests/cffLib/data/TestCFF2Widths.ttx
index e3a3c9c1..eba2c20c 100644
--- a/Tests/cffLib/data/TestCFF2Widths.ttx
+++ b/Tests/cffLib/data/TestCFF2Widths.ttx
@@ -637,6 +637,7 @@
</STAT>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/cffLib/data/TestSparseCFF2VF.ttx b/Tests/cffLib/data/TestSparseCFF2VF.ttx
index f1ae063b..3dbf014a 100644
--- a/Tests/cffLib/data/TestSparseCFF2VF.ttx
+++ b/Tests/cffLib/data/TestSparseCFF2VF.ttx
@@ -1809,6 +1809,7 @@
</VORG>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/cffLib/specializer_test.py b/Tests/cffLib/specializer_test.py
index a9b778c0..6a8e0190 100644
--- a/Tests/cffLib/specializer_test.py
+++ b/Tests/cffLib/specializer_test.py
@@ -1,8 +1,13 @@
-from fontTools.cffLib.specializer import (programToString, stringToProgram,
- generalizeProgram, specializeProgram,
- programToCommands, commandsToProgram,
- generalizeCommands,
- specializeCommands)
+from fontTools.cffLib.specializer import (
+ programToString,
+ stringToProgram,
+ generalizeProgram,
+ specializeProgram,
+ programToCommands,
+ commandsToProgram,
+ generalizeCommands,
+ specializeCommands,
+)
from fontTools.ttLib import TTFont
import os
import unittest
@@ -26,7 +31,6 @@ def get_specialized_charstr(charstr, **kwargs):
class CFFGeneralizeProgramTest(unittest.TestCase):
-
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
@@ -34,468 +38,503 @@ class CFFGeneralizeProgramTest(unittest.TestCase):
if not hasattr(self, "assertRaisesRegex"):
self.assertRaisesRegex = self.assertRaisesRegexp
-# no arguments/operands
+ # no arguments/operands
def test_rmoveto_none(self):
- test_charstr = 'rmoveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rmoveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_hmoveto_none(self):
- test_charstr = 'hmoveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hmoveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_vmoveto_none(self):
- test_charstr = 'vmoveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vmoveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_rlineto_none(self):
- test_charstr = 'rlineto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rlineto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_hlineto_none(self):
- test_charstr = 'hlineto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hlineto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_vlineto_none(self):
- test_charstr = 'vlineto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vlineto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_rrcurveto_none(self):
- test_charstr = 'rrcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rrcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_hhcurveto_none(self):
- test_charstr = 'hhcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hhcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_vvcurveto_none(self):
- test_charstr = 'vvcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vvcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_hvcurveto_none(self):
- test_charstr = 'hvcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hvcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_vhcurveto_none(self):
- test_charstr = 'vhcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vhcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_rcurveline_none(self):
- test_charstr = 'rcurveline'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rcurveline"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
def test_rlinecurve_none(self):
- test_charstr = 'rlinecurve'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rlinecurve"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_generalized_charstr(test_charstr)
-# rmoveto
+ # rmoveto
def test_rmoveto_zero(self):
- test_charstr = '0 0 rmoveto'
+ test_charstr = "0 0 rmoveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rmoveto_zero_width(self):
- test_charstr = '100 0 0 rmoveto'
+ test_charstr = "100 0 0 rmoveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rmoveto(self):
- test_charstr = '.55 -.8 rmoveto'
- xpct_charstr = '0.55 -0.8 rmoveto'
+ test_charstr = ".55 -.8 rmoveto"
+ xpct_charstr = "0.55 -0.8 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rmoveto_width(self):
- test_charstr = '100.5 50 -5.8 rmoveto'
+ test_charstr = "100.5 50 -5.8 rmoveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# hmoveto
+ # hmoveto
def test_hmoveto_zero(self):
- test_charstr = '0 hmoveto'
- xpct_charstr = '0 0 rmoveto'
+ test_charstr = "0 hmoveto"
+ xpct_charstr = "0 0 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hmoveto_zero_width(self):
- test_charstr = '100 0 hmoveto'
- xpct_charstr = '100 0 0 rmoveto'
+ test_charstr = "100 0 hmoveto"
+ xpct_charstr = "100 0 0 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hmoveto(self):
- test_charstr = '.67 hmoveto'
- xpct_charstr = '0.67 0 rmoveto'
+ test_charstr = ".67 hmoveto"
+ xpct_charstr = "0.67 0 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hmoveto_width(self):
- test_charstr = '100 -70 hmoveto'
- xpct_charstr = '100 -70 0 rmoveto'
+ test_charstr = "100 -70 hmoveto"
+ xpct_charstr = "100 -70 0 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# vmoveto
+ # vmoveto
def test_vmoveto_zero(self):
- test_charstr = '0 vmoveto'
- xpct_charstr = '0 0 rmoveto'
+ test_charstr = "0 vmoveto"
+ xpct_charstr = "0 0 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vmoveto_zero_width(self):
- test_charstr = '100 0 vmoveto'
- xpct_charstr = '100 0 0 rmoveto'
+ test_charstr = "100 0 vmoveto"
+ xpct_charstr = "100 0 0 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vmoveto(self):
- test_charstr = '-.24 vmoveto'
- xpct_charstr = '0 -0.24 rmoveto'
+ test_charstr = "-.24 vmoveto"
+ xpct_charstr = "0 -0.24 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vmoveto_width(self):
- test_charstr = '100 44 vmoveto'
- xpct_charstr = '100 0 44 rmoveto'
+ test_charstr = "100 44 vmoveto"
+ xpct_charstr = "100 0 44 rmoveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# rlineto
+ # rlineto
def test_rlineto_zero(self):
- test_charstr = '0 0 rlineto'
+ test_charstr = "0 0 rlineto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rlineto_zero_mult(self):
- test_charstr = '0 0 0 0 0 0 rlineto'
- xpct_charstr = ('0 0 rlineto '*3).rstrip()
+ test_charstr = "0 0 0 0 0 0 rlineto"
+ xpct_charstr = ("0 0 rlineto " * 3).rstrip()
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rlineto(self):
- test_charstr = '.55 -.8 rlineto'
- xpct_charstr = '0.55 -0.8 rlineto'
+ test_charstr = ".55 -.8 rlineto"
+ xpct_charstr = "0.55 -0.8 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rlineto_mult(self):
- test_charstr = '.55 -.8 .55 -.8 .55 -.8 rlineto'
- xpct_charstr = ('0.55 -0.8 rlineto '*3).rstrip()
+ test_charstr = ".55 -.8 .55 -.8 .55 -.8 rlineto"
+ xpct_charstr = ("0.55 -0.8 rlineto " * 3).rstrip()
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# hlineto
+ # hlineto
def test_hlineto_zero(self):
- test_charstr = '0 hlineto'
- xpct_charstr = '0 0 rlineto'
+ test_charstr = "0 hlineto"
+ xpct_charstr = "0 0 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hlineto_zero_mult(self):
- test_charstr = '0 0 0 0 hlineto'
- xpct_charstr = ('0 0 rlineto '*4).rstrip()
+ test_charstr = "0 0 0 0 hlineto"
+ xpct_charstr = ("0 0 rlineto " * 4).rstrip()
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hlineto(self):
- test_charstr = '.67 hlineto'
- xpct_charstr = '0.67 0 rlineto'
+ test_charstr = ".67 hlineto"
+ xpct_charstr = "0.67 0 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hlineto_mult(self):
- test_charstr = '.67 -6.0 .67 hlineto'
- xpct_charstr = '0.67 0 rlineto 0 -6.0 rlineto 0.67 0 rlineto'
+ test_charstr = ".67 -6.0 .67 hlineto"
+ xpct_charstr = "0.67 0 rlineto 0 -6.0 rlineto 0.67 0 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# vlineto
+ # vlineto
def test_vlineto_zero(self):
- test_charstr = '0 vlineto'
- xpct_charstr = '0 0 rlineto'
+ test_charstr = "0 vlineto"
+ xpct_charstr = "0 0 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vlineto_zero_mult(self):
- test_charstr = '0 0 0 vlineto'
- xpct_charstr = ('0 0 rlineto '*3).rstrip()
+ test_charstr = "0 0 0 vlineto"
+ xpct_charstr = ("0 0 rlineto " * 3).rstrip()
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vlineto(self):
- test_charstr = '-.24 vlineto'
- xpct_charstr = '0 -0.24 rlineto'
+ test_charstr = "-.24 vlineto"
+ xpct_charstr = "0 -0.24 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vlineto_mult(self):
- test_charstr = '-.24 +50 30 -4 vlineto'
- xpct_charstr = '0 -0.24 rlineto 50 0 rlineto 0 30 rlineto -4 0 rlineto'
+ test_charstr = "-.24 +50 30 -4 vlineto"
+ xpct_charstr = "0 -0.24 rlineto 50 0 rlineto 0 30 rlineto -4 0 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# rrcurveto
+ # rrcurveto
def test_rrcurveto(self):
- test_charstr = '-1 56 -2 57 -1 57 rrcurveto'
+ test_charstr = "-1 56 -2 57 -1 57 rrcurveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_mult(self):
- test_charstr = '-30 8 -36 15 -37 22 44 54 31 61 22 68 rrcurveto'
- xpct_charstr = '-30 8 -36 15 -37 22 rrcurveto 44 54 31 61 22 68 rrcurveto'
+ test_charstr = "-30 8 -36 15 -37 22 44 54 31 61 22 68 rrcurveto"
+ xpct_charstr = "-30 8 -36 15 -37 22 rrcurveto 44 54 31 61 22 68 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_d3947b8(self):
- test_charstr = '1 2 3 4 5 0 rrcurveto'
+ test_charstr = "1 2 3 4 5 0 rrcurveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_v0_0h_h0(self):
- test_charstr = '0 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '0 10 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto'
+ test_charstr = "0 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "0 10 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_h0_0h_h0(self):
- test_charstr = '10 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '10 0 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto'
+ test_charstr = "10 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "10 0 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_00_0h_h0(self):
- test_charstr = '0 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '0 0 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto'
+ test_charstr = "0 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "0 0 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_r0_0h_h0(self):
- test_charstr = '10 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '10 10 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto'
+ test_charstr = "10 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "10 10 1 2 0 0 rrcurveto 0 0 1 2 0 1 rrcurveto 0 1 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_v0_0v_v0(self):
- test_charstr = '0 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '0 10 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto'
+ test_charstr = "0 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "0 10 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_h0_0v_v0(self):
- test_charstr = '10 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '10 0 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto'
+ test_charstr = "10 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "10 0 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_00_0v_v0(self):
- test_charstr = '0 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '0 0 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto'
+ test_charstr = "0 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "0 0 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_r0_0v_v0(self):
- test_charstr = '10 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '10 10 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto'
+ test_charstr = "10 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = (
+ "10 10 1 2 0 0 rrcurveto 0 0 1 2 1 0 rrcurveto 1 0 3 4 0 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# hhcurveto
+ # hhcurveto
def test_hhcurveto_4(self):
- test_charstr = '10 30 0 10 hhcurveto'
- xpct_charstr = '10 0 30 0 10 0 rrcurveto'
+ test_charstr = "10 30 0 10 hhcurveto"
+ xpct_charstr = "10 0 30 0 10 0 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_5(self):
- test_charstr = '40 -38 -60 41 -91 hhcurveto'
- xpct_charstr = '-38 40 -60 41 -91 0 rrcurveto'
+ test_charstr = "40 -38 -60 41 -91 hhcurveto"
+ xpct_charstr = "-38 40 -60 41 -91 0 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_4_4(self):
- test_charstr = '43 23 25 18 29 56 42 -84 hhcurveto'
- xpct_charstr = '43 0 23 25 18 0 rrcurveto 29 0 56 42 -84 0 rrcurveto'
+ test_charstr = "43 23 25 18 29 56 42 -84 hhcurveto"
+ xpct_charstr = "43 0 23 25 18 0 rrcurveto 29 0 56 42 -84 0 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_5_4(self):
- test_charstr = '43 23 25 18 29 56 42 -84 79 hhcurveto'
- xpct_charstr = '23 43 25 18 29 0 rrcurveto 56 0 42 -84 79 0 rrcurveto'
+ test_charstr = "43 23 25 18 29 56 42 -84 79 hhcurveto"
+ xpct_charstr = "23 43 25 18 29 0 rrcurveto 56 0 42 -84 79 0 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_4_4_4(self):
- test_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 hhcurveto'
- xpct_charstr = '1 0 2 3 4 0 rrcurveto 5 0 6 7 8 0 rrcurveto 9 0 10 11 12 0 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 hhcurveto"
+ xpct_charstr = (
+ "1 0 2 3 4 0 rrcurveto 5 0 6 7 8 0 rrcurveto 9 0 10 11 12 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_5_4_4(self):
- test_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 13 hhcurveto'
- xpct_charstr = '2 1 3 4 5 0 rrcurveto 6 0 7 8 9 0 rrcurveto 10 0 11 12 13 0 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 13 hhcurveto"
+ xpct_charstr = (
+ "2 1 3 4 5 0 rrcurveto 6 0 7 8 9 0 rrcurveto 10 0 11 12 13 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# vvcurveto
+ # vvcurveto
def test_vvcurveto_4(self):
- test_charstr = '61 6 52 68 vvcurveto'
- xpct_charstr = '0 61 6 52 0 68 rrcurveto'
+ test_charstr = "61 6 52 68 vvcurveto"
+ xpct_charstr = "0 61 6 52 0 68 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_5(self):
- test_charstr = '61 38 35 56 72 vvcurveto'
- xpct_charstr = '61 38 35 56 0 72 rrcurveto'
+ test_charstr = "61 38 35 56 72 vvcurveto"
+ xpct_charstr = "61 38 35 56 0 72 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_4_4(self):
- test_charstr = '-84 -88 -30 -90 -13 19 23 -11 vvcurveto'
- xpct_charstr = '0 -84 -88 -30 0 -90 rrcurveto 0 -13 19 23 0 -11 rrcurveto'
+ test_charstr = "-84 -88 -30 -90 -13 19 23 -11 vvcurveto"
+ xpct_charstr = "0 -84 -88 -30 0 -90 rrcurveto 0 -13 19 23 0 -11 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_5_4(self):
- test_charstr = '43 12 17 32 65 68 -6 52 61 vvcurveto'
- xpct_charstr = '43 12 17 32 0 65 rrcurveto 0 68 -6 52 0 61 rrcurveto'
+ test_charstr = "43 12 17 32 65 68 -6 52 61 vvcurveto"
+ xpct_charstr = "43 12 17 32 0 65 rrcurveto 0 68 -6 52 0 61 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_4_4_4(self):
- test_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 vvcurveto'
- xpct_charstr = '0 1 2 3 0 4 rrcurveto 0 5 6 7 0 8 rrcurveto 0 9 10 11 0 12 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 vvcurveto"
+ xpct_charstr = (
+ "0 1 2 3 0 4 rrcurveto 0 5 6 7 0 8 rrcurveto 0 9 10 11 0 12 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_5_4_4(self):
- test_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 13 vvcurveto'
- xpct_charstr = '1 2 3 4 0 5 rrcurveto 0 6 7 8 0 9 rrcurveto 0 10 11 12 0 13 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 13 vvcurveto"
+ xpct_charstr = (
+ "1 2 3 4 0 5 rrcurveto 0 6 7 8 0 9 rrcurveto 0 10 11 12 0 13 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# hvcurveto
+ # hvcurveto
def test_hvcurveto_4(self):
- test_charstr = '1 2 3 4 hvcurveto'
- xpct_charstr = '1 0 2 3 0 4 rrcurveto'
+ test_charstr = "1 2 3 4 hvcurveto"
+ xpct_charstr = "1 0 2 3 0 4 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_5(self):
- test_charstr = '57 44 22 40 34 hvcurveto'
- xpct_charstr = '57 0 44 22 34 40 rrcurveto'
+ test_charstr = "57 44 22 40 34 hvcurveto"
+ xpct_charstr = "57 0 44 22 34 40 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4(self):
- test_charstr = '65 33 -19 -45 -45 -29 -25 -71 hvcurveto'
- xpct_charstr = '65 0 33 -19 0 -45 rrcurveto 0 -45 -29 -25 -71 0 rrcurveto'
+ test_charstr = "65 33 -19 -45 -45 -29 -25 -71 hvcurveto"
+ xpct_charstr = "65 0 33 -19 0 -45 rrcurveto 0 -45 -29 -25 -71 0 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_5(self):
- test_charstr = '97 69 41 86 58 -36 34 -64 11 hvcurveto'
- xpct_charstr = '97 0 69 41 0 86 rrcurveto 0 58 -36 34 -64 11 rrcurveto'
+ test_charstr = "97 69 41 86 58 -36 34 -64 11 hvcurveto"
+ xpct_charstr = "97 0 69 41 0 86 rrcurveto 0 58 -36 34 -64 11 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_4(self):
- test_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 hvcurveto'
- xpct_charstr = '1 0 2 3 0 4 rrcurveto 0 5 6 7 8 0 rrcurveto 9 0 10 11 0 12 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 hvcurveto"
+ xpct_charstr = (
+ "1 0 2 3 0 4 rrcurveto 0 5 6 7 8 0 rrcurveto 9 0 10 11 0 12 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_5(self):
- test_charstr = '-124 -79 104 165 163 82 102 124 56 43 -25 -37 35 hvcurveto'
- xpct_charstr = '-124 0 -79 104 0 165 rrcurveto 0 163 82 102 124 0 rrcurveto 56 0 43 -25 35 -37 rrcurveto'
+ test_charstr = "-124 -79 104 165 163 82 102 124 56 43 -25 -37 35 hvcurveto"
+ xpct_charstr = "-124 0 -79 104 0 165 rrcurveto 0 163 82 102 124 0 rrcurveto 56 0 43 -25 35 -37 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_4_4(self):
- test_charstr = '32 25 22 32 31 -25 22 -32 -32 -25 -22 -31 -32 25 -22 32 hvcurveto'
- xpct_charstr = '32 0 25 22 0 32 rrcurveto 0 31 -25 22 -32 0 rrcurveto -32 0 -25 -22 0 -31 rrcurveto 0 -32 25 -22 32 0 rrcurveto'
+ test_charstr = (
+ "32 25 22 32 31 -25 22 -32 -32 -25 -22 -31 -32 25 -22 32 hvcurveto"
+ )
+ xpct_charstr = "32 0 25 22 0 32 rrcurveto 0 31 -25 22 -32 0 rrcurveto -32 0 -25 -22 0 -31 rrcurveto 0 -32 25 -22 32 0 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_4_4_5(self):
- test_charstr = '-170 -128 111 195 234 172 151 178 182 95 -118 -161 -130 -71 -77 -63 -55 -19 38 79 20 hvcurveto'
- xpct_charstr = '-170 0 -128 111 0 195 rrcurveto 0 234 172 151 178 0 rrcurveto 182 0 95 -118 0 -161 rrcurveto 0 -130 -71 -77 -63 0 rrcurveto -55 0 -19 38 20 79 rrcurveto'
+ test_charstr = "-170 -128 111 195 234 172 151 178 182 95 -118 -161 -130 -71 -77 -63 -55 -19 38 79 20 hvcurveto"
+ xpct_charstr = "-170 0 -128 111 0 195 rrcurveto 0 234 172 151 178 0 rrcurveto 182 0 95 -118 0 -161 rrcurveto 0 -130 -71 -77 -63 0 rrcurveto -55 0 -19 38 20 79 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# vhcurveto
+ # vhcurveto
def test_vhcurveto_4(self):
- test_charstr = '-57 43 -30 53 vhcurveto'
- xpct_charstr = '0 -57 43 -30 53 0 rrcurveto'
+ test_charstr = "-57 43 -30 53 vhcurveto"
+ xpct_charstr = "0 -57 43 -30 53 0 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_5(self):
- test_charstr = '41 -27 19 -46 11 vhcurveto'
- xpct_charstr = '0 41 -27 19 -46 11 rrcurveto'
+ test_charstr = "41 -27 19 -46 11 vhcurveto"
+ xpct_charstr = "0 41 -27 19 -46 11 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4(self):
- test_charstr = '1 2 3 4 5 6 7 8 vhcurveto'
- xpct_charstr = '0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 7 8 vhcurveto"
+ xpct_charstr = "0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_5(self):
- test_charstr = '-64 -23 -25 -45 -30 -24 14 33 -19 vhcurveto'
- xpct_charstr = '0 -64 -23 -25 -45 0 rrcurveto -30 0 -24 14 -19 33 rrcurveto'
+ test_charstr = "-64 -23 -25 -45 -30 -24 14 33 -19 vhcurveto"
+ xpct_charstr = "0 -64 -23 -25 -45 0 rrcurveto -30 0 -24 14 -19 33 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4_4(self):
- test_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 vhcurveto'
- xpct_charstr = '0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto 0 9 10 11 12 0 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 vhcurveto"
+ xpct_charstr = (
+ "0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto 0 9 10 11 12 0 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4_5(self):
- test_charstr = '108 59 81 98 99 59 -81 -108 -100 -46 -66 -63 -47 vhcurveto'
- xpct_charstr = '0 108 59 81 98 0 rrcurveto 99 0 59 -81 0 -108 rrcurveto 0 -100 -46 -66 -63 -47 rrcurveto'
+ test_charstr = "108 59 81 98 99 59 -81 -108 -100 -46 -66 -63 -47 vhcurveto"
+ xpct_charstr = "0 108 59 81 98 0 rrcurveto 99 0 59 -81 0 -108 rrcurveto 0 -100 -46 -66 -63 -47 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4_4_5(self):
- test_charstr = '60 -26 37 -43 -33 -28 -22 -36 -37 27 -20 32 3 4 0 1 3 vhcurveto'
- xpct_charstr = '0 60 -26 37 -43 0 rrcurveto -33 0 -28 -22 0 -36 rrcurveto 0 -37 27 -20 32 0 rrcurveto 3 0 4 0 3 1 rrcurveto'
+ test_charstr = "60 -26 37 -43 -33 -28 -22 -36 -37 27 -20 32 3 4 0 1 3 vhcurveto"
+ xpct_charstr = "0 60 -26 37 -43 0 rrcurveto -33 0 -28 -22 0 -36 rrcurveto 0 -37 27 -20 32 0 rrcurveto 3 0 4 0 3 1 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# rcurveline
+ # rcurveline
def test_rcurveline_6_2(self):
- test_charstr = '21 -76 21 -72 24 -73 31 -100 rcurveline'
- xpct_charstr = '21 -76 21 -72 24 -73 rrcurveto 31 -100 rlineto'
+ test_charstr = "21 -76 21 -72 24 -73 31 -100 rcurveline"
+ xpct_charstr = "21 -76 21 -72 24 -73 rrcurveto 31 -100 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rcurveline_6_6_2(self):
- test_charstr = '-73 80 -80 121 -49 96 60 65 55 41 54 17 -8 78 rcurveline'
- xpct_charstr = '-73 80 -80 121 -49 96 rrcurveto 60 65 55 41 54 17 rrcurveto -8 78 rlineto'
+ test_charstr = "-73 80 -80 121 -49 96 60 65 55 41 54 17 -8 78 rcurveline"
+ xpct_charstr = (
+ "-73 80 -80 121 -49 96 rrcurveto 60 65 55 41 54 17 rrcurveto -8 78 rlineto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rcurveline_6_6_6_2(self):
- test_charstr = '1 64 10 51 29 39 15 21 15 20 15 18 47 -89 63 -98 52 -59 91 8 rcurveline'
- xpct_charstr = '1 64 10 51 29 39 rrcurveto 15 21 15 20 15 18 rrcurveto 47 -89 63 -98 52 -59 rrcurveto 91 8 rlineto'
+ test_charstr = (
+ "1 64 10 51 29 39 15 21 15 20 15 18 47 -89 63 -98 52 -59 91 8 rcurveline"
+ )
+ xpct_charstr = "1 64 10 51 29 39 rrcurveto 15 21 15 20 15 18 rrcurveto 47 -89 63 -98 52 -59 rrcurveto 91 8 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rcurveline_6_6_6_6_2(self):
- test_charstr = '1 64 10 51 29 39 15 21 15 20 15 18 46 -88 63 -97 52 -59 -38 -57 -49 -62 -52 -54 96 -8 rcurveline'
- xpct_charstr = '1 64 10 51 29 39 rrcurveto 15 21 15 20 15 18 rrcurveto 46 -88 63 -97 52 -59 rrcurveto -38 -57 -49 -62 -52 -54 rrcurveto 96 -8 rlineto'
+ test_charstr = "1 64 10 51 29 39 15 21 15 20 15 18 46 -88 63 -97 52 -59 -38 -57 -49 -62 -52 -54 96 -8 rcurveline"
+ xpct_charstr = "1 64 10 51 29 39 rrcurveto 15 21 15 20 15 18 rrcurveto 46 -88 63 -97 52 -59 rrcurveto -38 -57 -49 -62 -52 -54 rrcurveto 96 -8 rlineto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# rlinecurve
+ # rlinecurve
def test_rlinecurve_2_6(self):
- test_charstr = '21 -76 21 -72 24 -73 31 -100 rlinecurve'
- xpct_charstr = '21 -76 rlineto 21 -72 24 -73 31 -100 rrcurveto'
+ test_charstr = "21 -76 21 -72 24 -73 31 -100 rlinecurve"
+ xpct_charstr = "21 -76 rlineto 21 -72 24 -73 31 -100 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rlinecurve_2_2_6(self):
- test_charstr = '-73 80 -80 121 -49 96 60 65 55 41 rlinecurve'
- xpct_charstr = '-73 80 rlineto -80 121 rlineto -49 96 60 65 55 41 rrcurveto'
+ test_charstr = "-73 80 -80 121 -49 96 60 65 55 41 rlinecurve"
+ xpct_charstr = "-73 80 rlineto -80 121 rlineto -49 96 60 65 55 41 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rlinecurve_2_2_2_6(self):
- test_charstr = '1 64 10 51 29 39 15 21 15 20 15 18 rlinecurve'
- xpct_charstr = '1 64 rlineto 10 51 rlineto 29 39 rlineto 15 21 15 20 15 18 rrcurveto'
+ test_charstr = "1 64 10 51 29 39 15 21 15 20 15 18 rlinecurve"
+ xpct_charstr = (
+ "1 64 rlineto 10 51 rlineto 29 39 rlineto 15 21 15 20 15 18 rrcurveto"
+ )
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
def test_rlinecurve_2_2_2_2_6(self):
- test_charstr = '1 64 10 51 29 39 15 21 15 20 15 18 46 -88 rlinecurve'
- xpct_charstr = '1 64 rlineto 10 51 rlineto 29 39 rlineto 15 21 rlineto 15 20 15 18 46 -88 rrcurveto'
+ test_charstr = "1 64 10 51 29 39 15 21 15 20 15 18 46 -88 rlinecurve"
+ xpct_charstr = "1 64 rlineto 10 51 rlineto 29 39 rlineto 15 21 rlineto 15 20 15 18 46 -88 rrcurveto"
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# hstem/vstem
+ # hstem/vstem
def test_hstem_vstem(self):
- test_charstr = '95 0 58 542 60 hstem 89 65 344 67 vstem 89 45 rmoveto'
+ test_charstr = "95 0 58 542 60 hstem 89 65 344 67 vstem 89 45 rmoveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# hstemhm/vstemhm
+ # hstemhm/vstemhm
def test_hstemhm_vstemhm(self):
- test_charstr = '-16 577 60 24 60 hstemhm 98 55 236 55 vstemhm 343 577 rmoveto'
+ test_charstr = "-16 577 60 24 60 hstemhm 98 55 236 55 vstemhm 343 577 rmoveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# hintmask/cntrmask
+ # hintmask/cntrmask
def test_hintmask_cntrmask(self):
- test_charstr = '52 80 153 61 4 83 -71.5 71.5 hintmask 11011100 94 119 216 119 216 119 cntrmask 1110000 154 -12 rmoveto'
+ test_charstr = "52 80 153 61 4 83 -71.5 71.5 hintmask 11011100 94 119 216 119 216 119 cntrmask 1110000 154 -12 rmoveto"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# endchar
+ # endchar
def test_endchar(self):
- test_charstr = '-255 319 rmoveto 266 57 rlineto endchar'
+ test_charstr = "-255 319 rmoveto 266 57 rlineto endchar"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
-# xtra
+ # xtra
def test_xtra(self):
- test_charstr = '-255 319 rmoveto 266 57 rlineto xtra 90 34'
+ test_charstr = "-255 319 rmoveto 266 57 rlineto xtra 90 34"
xpct_charstr = test_charstr
self.assertEqual(get_generalized_charstr(test_charstr), xpct_charstr)
class CFFSpecializeProgramTest(unittest.TestCase):
-
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
@@ -503,429 +542,450 @@ class CFFSpecializeProgramTest(unittest.TestCase):
if not hasattr(self, "assertRaisesRegex"):
self.assertRaisesRegex = self.assertRaisesRegexp
-# no arguments/operands
+ # no arguments/operands
def test_rmoveto_none(self):
- test_charstr = 'rmoveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rmoveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_hmoveto_none(self):
- test_charstr = 'hmoveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hmoveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_vmoveto_none(self):
- test_charstr = 'vmoveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vmoveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_rlineto_none(self):
- test_charstr = 'rlineto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rlineto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_hlineto_none(self):
- test_charstr = 'hlineto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hlineto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_vlineto_none(self):
- test_charstr = 'vlineto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vlineto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_rrcurveto_none(self):
- test_charstr = 'rrcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rrcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_hhcurveto_none(self):
- test_charstr = 'hhcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hhcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_vvcurveto_none(self):
- test_charstr = 'vvcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vvcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_hvcurveto_none(self):
- test_charstr = 'hvcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "hvcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_vhcurveto_none(self):
- test_charstr = 'vhcurveto'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "vhcurveto"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_rcurveline_none(self):
- test_charstr = 'rcurveline'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rcurveline"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
def test_rlinecurve_none(self):
- test_charstr = 'rlinecurve'
- with self.assertRaisesRegex(ValueError, r'\[\]'):
+ test_charstr = "rlinecurve"
+ with self.assertRaisesRegex(ValueError, r"\[\]"):
get_specialized_charstr(test_charstr)
-# rmoveto
+ # rmoveto
def test_rmoveto_zero(self):
- test_charstr = '0 0 rmoveto'
- xpct_charstr = '0 hmoveto'
- self.assertEqual(get_specialized_charstr(test_charstr,
- generalizeFirst=False), xpct_charstr)
+ test_charstr = "0 0 rmoveto"
+ xpct_charstr = "0 hmoveto"
+ self.assertEqual(
+ get_specialized_charstr(test_charstr, generalizeFirst=False), xpct_charstr
+ )
def test_rmoveto_zero_mult(self):
- test_charstr = '0 0 rmoveto '*3
- xpct_charstr = '0 hmoveto'
- self.assertEqual(get_specialized_charstr(test_charstr,
- generalizeFirst=False), xpct_charstr)
+ test_charstr = "0 0 rmoveto " * 3
+ xpct_charstr = "0 hmoveto"
+ self.assertEqual(
+ get_specialized_charstr(test_charstr, generalizeFirst=False), xpct_charstr
+ )
def test_rmoveto_zero_width(self):
- test_charstr = '100 0 0 rmoveto'
- xpct_charstr = '100 0 hmoveto'
+ test_charstr = "100 0 0 rmoveto"
+ xpct_charstr = "100 0 hmoveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rmoveto(self):
- test_charstr = '.55 -.8 rmoveto'
- xpct_charstr = '0.55 -0.8 rmoveto'
+ test_charstr = ".55 -.8 rmoveto"
+ xpct_charstr = "0.55 -0.8 rmoveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rmoveto_mult(self):
- test_charstr = '55 -8 rmoveto '*3
- xpct_charstr = '165 -24 rmoveto'
+ test_charstr = "55 -8 rmoveto " * 3
+ xpct_charstr = "165 -24 rmoveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rmoveto_width(self):
- test_charstr = '100.5 50 -5.8 rmoveto'
+ test_charstr = "100.5 50 -5.8 rmoveto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
-# rlineto
+ # rlineto
def test_rlineto_zero(self):
- test_charstr = '0 0 rlineto'
- xpct_charstr = ''
+ test_charstr = "0 0 rlineto"
+ xpct_charstr = ""
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rlineto_zero_mult(self):
- test_charstr = '0 0 rlineto '*3
- xpct_charstr = ''
+ test_charstr = "0 0 rlineto " * 3
+ xpct_charstr = ""
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rlineto(self):
- test_charstr = '.55 -.8 rlineto'
- xpct_charstr = '0.55 -0.8 rlineto'
+ test_charstr = ".55 -.8 rlineto"
+ xpct_charstr = "0.55 -0.8 rlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rlineto_mult(self):
- test_charstr = '.55 -.8 rlineto '*3
- xpct_charstr = '0.55 -0.8 0.55 -0.8 0.55 -0.8 rlineto'
+ test_charstr = ".55 -.8 rlineto " * 3
+ xpct_charstr = "0.55 -0.8 0.55 -0.8 0.55 -0.8 rlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hlineto(self):
- test_charstr = '.67 0 rlineto'
- xpct_charstr = '0.67 hlineto'
+ test_charstr = ".67 0 rlineto"
+ xpct_charstr = "0.67 hlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hlineto_zero_mult(self):
- test_charstr = '62 0 rlineto '*3
- xpct_charstr = '186 hlineto'
+ test_charstr = "62 0 rlineto " * 3
+ xpct_charstr = "186 hlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hlineto_mult(self):
- test_charstr = '.67 0 rlineto 0 -6.0 rlineto .67 0 rlineto'
- xpct_charstr = '0.67 -6.0 0.67 hlineto'
+ test_charstr = ".67 0 rlineto 0 -6.0 rlineto .67 0 rlineto"
+ xpct_charstr = "0.67 -6.0 0.67 hlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vlineto(self):
- test_charstr = '0 -.24 rlineto'
- xpct_charstr = '-0.24 vlineto'
+ test_charstr = "0 -.24 rlineto"
+ xpct_charstr = "-0.24 vlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vlineto_zero_mult(self):
- test_charstr = '0 -24 rlineto '*3
- xpct_charstr = '-72 vlineto'
+ test_charstr = "0 -24 rlineto " * 3
+ xpct_charstr = "-72 vlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vlineto_mult(self):
- test_charstr = '0 -.24 rlineto +50 0 rlineto 0 30 rlineto -4 0 rlineto'
- xpct_charstr = '-0.24 50 30 -4 vlineto'
+ test_charstr = "0 -.24 rlineto +50 0 rlineto 0 30 rlineto -4 0 rlineto"
+ xpct_charstr = "-0.24 50 30 -4 vlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_0lineto_peephole(self):
- test_charstr = '1 2 0 0 3 4 rlineto'
- xpct_charstr = '1 2 3 4 rlineto'
+ test_charstr = "1 2 0 0 3 4 rlineto"
+ xpct_charstr = "1 2 3 4 rlineto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hlineto_peephole(self):
- test_charstr = '1 2 5 0 3 4 rlineto'
+ test_charstr = "1 2 5 0 3 4 rlineto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vlineto_peephole(self):
- test_charstr = '1 2 0 5 3 4 rlineto'
+ test_charstr = "1 2 0 5 3 4 rlineto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
-# rrcurveto
+ # rrcurveto
def test_rrcurveto(self):
- test_charstr = '-1 56 -2 57 -1 57 rrcurveto'
+ test_charstr = "-1 56 -2 57 -1 57 rrcurveto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_mult(self):
- test_charstr = '-30 8 -36 15 -37 22 rrcurveto 44 54 31 61 22 68 rrcurveto'
- xpct_charstr = '-30 8 -36 15 -37 22 44 54 31 61 22 68 rrcurveto'
+ test_charstr = "-30 8 -36 15 -37 22 rrcurveto 44 54 31 61 22 68 rrcurveto"
+ xpct_charstr = "-30 8 -36 15 -37 22 44 54 31 61 22 68 rrcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_d3947b8(self):
- test_charstr = '1 2 3 4 5 0 rrcurveto'
- xpct_charstr = '2 1 3 4 5 hhcurveto'
+ test_charstr = "1 2 3 4 5 0 rrcurveto"
+ xpct_charstr = "2 1 3 4 5 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_4(self):
- test_charstr = '10 0 30 0 10 0 rrcurveto'
- xpct_charstr = '10 30 0 10 hhcurveto'
+ test_charstr = "10 0 30 0 10 0 rrcurveto"
+ xpct_charstr = "10 30 0 10 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_5(self):
- test_charstr = '-38 40 -60 41 -91 0 rrcurveto'
- xpct_charstr = '40 -38 -60 41 -91 hhcurveto'
+ test_charstr = "-38 40 -60 41 -91 0 rrcurveto"
+ xpct_charstr = "40 -38 -60 41 -91 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_4_4(self):
- test_charstr = '43 0 23 25 18 0 rrcurveto 29 0 56 42 -84 0 rrcurveto'
- xpct_charstr = '43 23 25 18 29 56 42 -84 hhcurveto'
+ test_charstr = "43 0 23 25 18 0 rrcurveto 29 0 56 42 -84 0 rrcurveto"
+ xpct_charstr = "43 23 25 18 29 56 42 -84 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_5_4(self):
- test_charstr = '23 43 25 18 29 0 rrcurveto 56 0 42 -84 79 0 rrcurveto'
- xpct_charstr = '43 23 25 18 29 56 42 -84 79 hhcurveto'
+ test_charstr = "23 43 25 18 29 0 rrcurveto 56 0 42 -84 79 0 rrcurveto"
+ xpct_charstr = "43 23 25 18 29 56 42 -84 79 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_4_4_4(self):
- test_charstr = '1 0 2 3 4 0 rrcurveto 5 0 6 7 8 0 rrcurveto 9 0 10 11 12 0 rrcurveto'
- xpct_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 hhcurveto'
+ test_charstr = (
+ "1 0 2 3 4 0 rrcurveto 5 0 6 7 8 0 rrcurveto 9 0 10 11 12 0 rrcurveto"
+ )
+ xpct_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_mult_5_4_4(self):
- test_charstr = '2 1 3 4 5 0 rrcurveto 6 0 7 8 9 0 rrcurveto 10 0 11 12 13 0 rrcurveto'
- xpct_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 13 hhcurveto'
+ test_charstr = (
+ "2 1 3 4 5 0 rrcurveto 6 0 7 8 9 0 rrcurveto 10 0 11 12 13 0 rrcurveto"
+ )
+ xpct_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 13 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_4(self):
- test_charstr = '0 61 6 52 0 68 rrcurveto'
- xpct_charstr = '61 6 52 68 vvcurveto'
+ test_charstr = "0 61 6 52 0 68 rrcurveto"
+ xpct_charstr = "61 6 52 68 vvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_5(self):
- test_charstr = '61 38 35 56 0 72 rrcurveto'
- xpct_charstr = '61 38 35 56 72 vvcurveto'
+ test_charstr = "61 38 35 56 0 72 rrcurveto"
+ xpct_charstr = "61 38 35 56 72 vvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_4_4(self):
- test_charstr = '0 -84 -88 -30 0 -90 rrcurveto 0 -13 19 23 0 -11 rrcurveto'
- xpct_charstr = '-84 -88 -30 -90 -13 19 23 -11 vvcurveto'
+ test_charstr = "0 -84 -88 -30 0 -90 rrcurveto 0 -13 19 23 0 -11 rrcurveto"
+ xpct_charstr = "-84 -88 -30 -90 -13 19 23 -11 vvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_5_4(self):
- test_charstr = '43 12 17 32 0 65 rrcurveto 0 68 -6 52 0 61 rrcurveto'
- xpct_charstr = '43 12 17 32 65 68 -6 52 61 vvcurveto'
+ test_charstr = "43 12 17 32 0 65 rrcurveto 0 68 -6 52 0 61 rrcurveto"
+ xpct_charstr = "43 12 17 32 65 68 -6 52 61 vvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_4_4_4(self):
- test_charstr = '0 1 2 3 0 4 rrcurveto 0 5 6 7 0 8 rrcurveto 0 9 10 11 0 12 rrcurveto'
- xpct_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 vvcurveto'
+ test_charstr = (
+ "0 1 2 3 0 4 rrcurveto 0 5 6 7 0 8 rrcurveto 0 9 10 11 0 12 rrcurveto"
+ )
+ xpct_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 vvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_mult_5_4_4(self):
- test_charstr = '1 2 3 4 0 5 rrcurveto 0 6 7 8 0 9 rrcurveto 0 10 11 12 0 13 rrcurveto'
- xpct_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 13 vvcurveto'
+ test_charstr = (
+ "1 2 3 4 0 5 rrcurveto 0 6 7 8 0 9 rrcurveto 0 10 11 12 0 13 rrcurveto"
+ )
+ xpct_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 13 vvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4(self):
- test_charstr = '1 0 2 3 0 4 rrcurveto'
- xpct_charstr = '1 2 3 4 hvcurveto'
+ test_charstr = "1 0 2 3 0 4 rrcurveto"
+ xpct_charstr = "1 2 3 4 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_5(self):
- test_charstr = '57 0 44 22 34 40 rrcurveto'
- xpct_charstr = '57 44 22 40 34 hvcurveto'
+ test_charstr = "57 0 44 22 34 40 rrcurveto"
+ xpct_charstr = "57 44 22 40 34 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4(self):
- test_charstr = '65 0 33 -19 0 -45 rrcurveto 0 -45 -29 -25 -71 0 rrcurveto'
- xpct_charstr = '65 33 -19 -45 -45 -29 -25 -71 hvcurveto'
+ test_charstr = "65 0 33 -19 0 -45 rrcurveto 0 -45 -29 -25 -71 0 rrcurveto"
+ xpct_charstr = "65 33 -19 -45 -45 -29 -25 -71 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_5(self):
- test_charstr = '97 0 69 41 0 86 rrcurveto 0 58 -36 34 -64 11 rrcurveto'
- xpct_charstr = '97 69 41 86 58 -36 34 -64 11 hvcurveto'
+ test_charstr = "97 0 69 41 0 86 rrcurveto 0 58 -36 34 -64 11 rrcurveto"
+ xpct_charstr = "97 69 41 86 58 -36 34 -64 11 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_4(self):
- test_charstr = '1 0 2 3 0 4 rrcurveto 0 5 6 7 8 0 rrcurveto 9 0 10 11 0 12 rrcurveto'
- xpct_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 hvcurveto'
+ test_charstr = (
+ "1 0 2 3 0 4 rrcurveto 0 5 6 7 8 0 rrcurveto 9 0 10 11 0 12 rrcurveto"
+ )
+ xpct_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_5(self):
- test_charstr = '-124 0 -79 104 0 165 rrcurveto 0 163 82 102 124 0 rrcurveto 56 0 43 -25 35 -37 rrcurveto'
- xpct_charstr = '-124 -79 104 165 163 82 102 124 56 43 -25 -37 35 hvcurveto'
+ test_charstr = "-124 0 -79 104 0 165 rrcurveto 0 163 82 102 124 0 rrcurveto 56 0 43 -25 35 -37 rrcurveto"
+ xpct_charstr = "-124 -79 104 165 163 82 102 124 56 43 -25 -37 35 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_4_4(self):
- test_charstr = '32 0 25 22 0 32 rrcurveto 0 31 -25 22 -32 0 rrcurveto -32 0 -25 -22 0 -31 rrcurveto 0 -32 25 -22 32 0 rrcurveto'
- xpct_charstr = '32 25 22 32 31 -25 22 -32 -32 -25 -22 -31 -32 25 -22 32 hvcurveto'
+ test_charstr = "32 0 25 22 0 32 rrcurveto 0 31 -25 22 -32 0 rrcurveto -32 0 -25 -22 0 -31 rrcurveto 0 -32 25 -22 32 0 rrcurveto"
+ xpct_charstr = (
+ "32 25 22 32 31 -25 22 -32 -32 -25 -22 -31 -32 25 -22 32 hvcurveto"
+ )
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_4_4_4_4_5(self):
- test_charstr = '-170 0 -128 111 0 195 rrcurveto 0 234 172 151 178 0 rrcurveto 182 0 95 -118 0 -161 rrcurveto 0 -130 -71 -77 -63 0 rrcurveto -55 0 -19 38 20 79 rrcurveto'
- xpct_charstr = '-170 -128 111 195 234 172 151 178 182 95 -118 -161 -130 -71 -77 -63 -55 -19 38 79 20 hvcurveto'
+ test_charstr = "-170 0 -128 111 0 195 rrcurveto 0 234 172 151 178 0 rrcurveto 182 0 95 -118 0 -161 rrcurveto 0 -130 -71 -77 -63 0 rrcurveto -55 0 -19 38 20 79 rrcurveto"
+ xpct_charstr = "-170 -128 111 195 234 172 151 178 182 95 -118 -161 -130 -71 -77 -63 -55 -19 38 79 20 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4(self):
- test_charstr = '0 -57 43 -30 53 0 rrcurveto'
- xpct_charstr = '-57 43 -30 53 vhcurveto'
+ test_charstr = "0 -57 43 -30 53 0 rrcurveto"
+ xpct_charstr = "-57 43 -30 53 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_5(self):
- test_charstr = '0 41 -27 19 -46 11 rrcurveto'
- xpct_charstr = '41 -27 19 -46 11 vhcurveto'
+ test_charstr = "0 41 -27 19 -46 11 rrcurveto"
+ xpct_charstr = "41 -27 19 -46 11 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4(self):
- test_charstr = '0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto'
- xpct_charstr = '1 2 3 4 5 6 7 8 vhcurveto'
+ test_charstr = "0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto"
+ xpct_charstr = "1 2 3 4 5 6 7 8 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_5(self):
- test_charstr = '0 -64 -23 -25 -45 0 rrcurveto -30 0 -24 14 -19 33 rrcurveto'
- xpct_charstr = '-64 -23 -25 -45 -30 -24 14 33 -19 vhcurveto'
+ test_charstr = "0 -64 -23 -25 -45 0 rrcurveto -30 0 -24 14 -19 33 rrcurveto"
+ xpct_charstr = "-64 -23 -25 -45 -30 -24 14 33 -19 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4_4(self):
- test_charstr = '0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto 0 9 10 11 12 0 rrcurveto'
- xpct_charstr = '1 2 3 4 5 6 7 8 9 10 11 12 vhcurveto'
+ test_charstr = (
+ "0 1 2 3 4 0 rrcurveto 5 0 6 7 0 8 rrcurveto 0 9 10 11 12 0 rrcurveto"
+ )
+ xpct_charstr = "1 2 3 4 5 6 7 8 9 10 11 12 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4_5(self):
- test_charstr = '0 108 59 81 98 0 rrcurveto 99 0 59 -81 0 -108 rrcurveto 0 -100 -46 -66 -63 -47 rrcurveto'
- xpct_charstr = '108 59 81 98 99 59 -81 -108 -100 -46 -66 -63 -47 vhcurveto'
+ test_charstr = "0 108 59 81 98 0 rrcurveto 99 0 59 -81 0 -108 rrcurveto 0 -100 -46 -66 -63 -47 rrcurveto"
+ xpct_charstr = "108 59 81 98 99 59 -81 -108 -100 -46 -66 -63 -47 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_4_4_4_5(self):
- test_charstr = '0 60 -26 37 -43 0 rrcurveto -33 0 -28 -22 0 -36 rrcurveto 0 -37 27 -20 32 0 rrcurveto 3 0 4 0 3 1 rrcurveto'
- xpct_charstr = '60 -26 37 -43 -33 -28 -22 -36 -37 27 -20 32 3 4 0 1 3 vhcurveto'
+ test_charstr = "0 60 -26 37 -43 0 rrcurveto -33 0 -28 -22 0 -36 rrcurveto 0 -37 27 -20 32 0 rrcurveto 3 0 4 0 3 1 rrcurveto"
+ xpct_charstr = "60 -26 37 -43 -33 -28 -22 -36 -37 27 -20 32 3 4 0 1 3 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_v0_0h_h0(self):
- test_charstr = '0 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '10 1 2 0 0 1 2 1 1 3 4 0 vhcurveto'
+ test_charstr = "0 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = "10 1 2 0 0 1 2 1 1 3 4 0 vhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_h0_0h_h0(self):
- test_charstr = '10 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '10 1 2 0 hhcurveto 0 1 2 1 1 3 4 0 hvcurveto'
+ test_charstr = "10 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = "10 1 2 0 hhcurveto 0 1 2 1 1 3 4 0 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_00_0h_h0(self):
- test_charstr = '0 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '1 2 rlineto 0 1 2 1 1 3 4 0 hvcurveto'
+ test_charstr = "0 0 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = "1 2 rlineto 0 1 2 1 1 3 4 0 hvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_r0_0h_h0(self):
- test_charstr = '10 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto'
- xpct_charstr = '10 10 1 2 0 0 1 2 1 1 3 4 0 vvcurveto'
+ test_charstr = "10 10 1 2 0 0 0 0 1 2 0 1 0 1 3 4 0 0 rrcurveto"
+ xpct_charstr = "10 10 1 2 0 0 1 2 1 1 3 4 0 vvcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_v0_0v_v0(self):
- test_charstr = '0 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '10 1 2 0 vhcurveto 0 1 2 1 1 3 4 0 hhcurveto'
+ test_charstr = "0 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = "10 1 2 0 vhcurveto 0 1 2 1 1 3 4 0 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_h0_0v_v0(self):
- test_charstr = '10 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '10 1 2 0 0 1 2 1 1 3 4 0 hhcurveto'
+ test_charstr = "10 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = "10 1 2 0 0 1 2 1 1 3 4 0 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_00_0v_v0(self):
- test_charstr = '0 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '1 2 rlineto 0 1 2 1 1 3 4 0 hhcurveto'
+ test_charstr = "0 0 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = "1 2 rlineto 0 1 2 1 1 3 4 0 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rrcurveto_r0_0v_v0(self):
- test_charstr = '10 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto'
- xpct_charstr = '10 10 1 2 0 0 1 2 1 1 3 4 0 hhcurveto'
+ test_charstr = "10 10 1 2 0 0 0 0 1 2 1 0 1 0 3 4 0 0 rrcurveto"
+ xpct_charstr = "10 10 1 2 0 0 1 2 1 1 3 4 0 hhcurveto"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hhcurveto_peephole(self):
- test_charstr = '1 2 3 4 5 6 1 2 3 4 5 0 1 2 3 4 5 6 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 1 2 3 4 5 0 1 2 3 4 5 6 rrcurveto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vvcurveto_peephole(self):
- test_charstr = '1 2 3 4 5 6 1 2 3 4 0 6 1 2 3 4 5 6 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 1 2 3 4 0 6 1 2 3 4 5 6 rrcurveto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_hvcurveto_peephole(self):
- test_charstr = '1 2 3 4 5 6 1 0 3 4 5 6 1 2 3 4 5 6 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 1 0 3 4 5 6 1 2 3 4 5 6 rrcurveto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_vhcurveto_peephole(self):
- test_charstr = '1 2 3 4 5 6 0 2 3 4 5 6 1 2 3 4 5 6 rrcurveto'
+ test_charstr = "1 2 3 4 5 6 0 2 3 4 5 6 1 2 3 4 5 6 rrcurveto"
xpct_charstr = test_charstr
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rcurveline_6_2(self):
- test_charstr = '21 -76 21 -72 24 -73 rrcurveto 31 -100 rlineto'
- xpct_charstr = '21 -76 21 -72 24 -73 31 -100 rcurveline'
+ test_charstr = "21 -76 21 -72 24 -73 rrcurveto 31 -100 rlineto"
+ xpct_charstr = "21 -76 21 -72 24 -73 31 -100 rcurveline"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rcurveline_6_6_2(self):
- test_charstr = '-73 80 -80 121 -49 96 rrcurveto 60 65 55 41 54 17 rrcurveto -8 78 rlineto'
- xpct_charstr = '-73 80 -80 121 -49 96 60 65 55 41 54 17 -8 78 rcurveline'
+ test_charstr = (
+ "-73 80 -80 121 -49 96 rrcurveto 60 65 55 41 54 17 rrcurveto -8 78 rlineto"
+ )
+ xpct_charstr = "-73 80 -80 121 -49 96 60 65 55 41 54 17 -8 78 rcurveline"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rcurveline_6_6_6_2(self):
- test_charstr = '1 64 10 51 29 39 rrcurveto 15 21 15 20 15 18 rrcurveto 47 -89 63 -98 52 -59 rrcurveto 91 8 rlineto'
- xpct_charstr = '1 64 10 51 29 39 15 21 15 20 15 18 47 -89 63 -98 52 -59 91 8 rcurveline'
+ test_charstr = "1 64 10 51 29 39 rrcurveto 15 21 15 20 15 18 rrcurveto 47 -89 63 -98 52 -59 rrcurveto 91 8 rlineto"
+ xpct_charstr = (
+ "1 64 10 51 29 39 15 21 15 20 15 18 47 -89 63 -98 52 -59 91 8 rcurveline"
+ )
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rlinecurve_2_6(self):
- test_charstr = '21 -76 rlineto 21 -72 24 -73 31 -100 rrcurveto'
- xpct_charstr = '21 -76 21 -72 24 -73 31 -100 rlinecurve'
+ test_charstr = "21 -76 rlineto 21 -72 24 -73 31 -100 rrcurveto"
+ xpct_charstr = "21 -76 21 -72 24 -73 31 -100 rlinecurve"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rlinecurve_2_2_6(self):
- test_charstr = '-73 80 rlineto -80 121 rlineto -49 96 60 65 55 41 rrcurveto'
- xpct_charstr = '-73 80 -80 121 -49 96 60 65 55 41 rlinecurve'
+ test_charstr = "-73 80 rlineto -80 121 rlineto -49 96 60 65 55 41 rrcurveto"
+ xpct_charstr = "-73 80 -80 121 -49 96 60 65 55 41 rlinecurve"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
def test_rlinecurve_2_2_2_6(self):
- test_charstr = '1 64 rlineto 10 51 rlineto 29 39 rlineto 15 21 15 20 15 18 rrcurveto'
- xpct_charstr = '1 64 10 51 29 39 15 21 15 20 15 18 rlinecurve'
+ test_charstr = (
+ "1 64 rlineto 10 51 rlineto 29 39 rlineto 15 21 15 20 15 18 rrcurveto"
+ )
+ xpct_charstr = "1 64 10 51 29 39 15 21 15 20 15 18 rlinecurve"
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
-# maxstack CFF=48, specializer uses up to 47
+ # maxstack CFF=48, specializer uses up to 47
def test_maxstack(self):
- operands = '1 2 3 4 5 6 '
- operator = 'rrcurveto '
- test_charstr = (operands + operator)*9
- xpct_charstr = (operands*2 + operator + operands*7 + operator).rstrip()
+ operands = "1 2 3 4 5 6 "
+ operator = "rrcurveto "
+ test_charstr = (operands + operator) * 9
+ xpct_charstr = (operands * 2 + operator + operands * 7 + operator).rstrip()
self.assertEqual(get_specialized_charstr(test_charstr), xpct_charstr)
class CFF2VFTestSpecialize(DataFilesHandler):
-
def test_blend_round_trip(self):
- ttx_path = self.getpath('TestSparseCFF2VF.ttx')
+ ttx_path = self.getpath("TestSparseCFF2VF.ttx")
ttf_font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
ttf_font.importXML(ttx_path)
fontGlyphList = ttf_font.getGlyphOrder()
- topDict = ttf_font['CFF2'].cff.topDictIndex[0]
+ topDict = ttf_font["CFF2"].cff.topDictIndex[0]
charstrings = topDict.CharStrings
for glyphName in fontGlyphList:
cs = charstrings[glyphName]
@@ -942,11 +1002,11 @@ class CFF2VFTestSpecialize(DataFilesHandler):
self.assertEqual(program, program_g)
def test_blend_programToCommands(self):
- ttx_path = self.getpath('TestCFF2Widths.ttx')
+ ttx_path = self.getpath("TestCFF2Widths.ttx")
ttf_font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
ttf_font.importXML(ttx_path)
fontGlyphList = ttf_font.getGlyphOrder()
- topDict = ttf_font['CFF2'].cff.topDictIndex[0]
+ topDict = ttf_font["CFF2"].cff.topDictIndex[0]
charstrings = topDict.CharStrings
for glyphName in fontGlyphList:
cs = charstrings[glyphName]
@@ -958,4 +1018,5 @@ class CFF2VFTestSpecialize(DataFilesHandler):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/cu2qu/cli_test.py b/Tests/cu2qu/cli_test.py
index f6798a63..be646847 100644
--- a/Tests/cu2qu/cli_test.py
+++ b/Tests/cu2qu/cli_test.py
@@ -9,7 +9,7 @@ from fontTools.cu2qu.ufo import CURVE_TYPE_LIB_KEY
from fontTools.cu2qu.cli import main
-DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+DATADIR = os.path.join(os.path.dirname(__file__), "data")
TEST_UFOS = [
py.path.local(DATADIR).join("RobotoSubset-Regular.ufo"),
@@ -28,7 +28,6 @@ def test_paths(tmpdir):
class MainTest(object):
-
@staticmethod
def run_main(*args):
main([str(p) for p in args if p])
@@ -44,13 +43,13 @@ class MainTest(object):
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)
+ 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)
+ self.run_main("-d", output_dir, *TEST_UFOS)
assert output_dir.check(dir=1)
outputs = set(p.basename for p in output_dir.listdir())
@@ -58,29 +57,28 @@ class MainTest(object):
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
+ self.run_main("-i", *test_paths)
+ self.run_main("-i", *test_paths) # idempotent
- @pytest.mark.parametrize(
- "mode", ["", "-i"], ids=["normal", "interpolatable"])
+ @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)
+ 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())
+ 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())
+ 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)
+ self.run_main("--keep-direction", *test_paths)
def test_conversion_error(self, test_paths):
- self.run_main('--conversion-error', 0.002, *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])
+ self.run_main("-e", 0.003, test_paths[0])
diff --git a/Tests/cu2qu/cu2qu_test.py b/Tests/cu2qu/cu2qu_test.py
index 456d2103..b125f865 100644
--- a/Tests/cu2qu/cu2qu_test.py
+++ b/Tests/cu2qu/cu2qu_test.py
@@ -21,31 +21,31 @@ import json
from fontTools.cu2qu import curve_to_quadratic, curves_to_quadratic
-DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+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_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)]
+ 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)]
+ 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]
+ 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)]
+ for curve_group, splines in zip(curve_groups, cls.compat_splines)
+ ]
cls.results = []
@@ -54,10 +54,16 @@ class CurveToQuadraticTest(unittest.TestCase):
"""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()))))
+ 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
@@ -65,40 +71,30 @@ class CurveToQuadraticTest(unittest.TestCase):
the conversion algorithm.
"""
- expected = {
- 2: 6,
- 3: 26,
- 4: 82,
- 5: 232,
- 6: 360,
- 7: 266,
- 8: 28}
+ 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))
+ 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}
+ 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')
+ self.assertEqual(
+ len(spline) - 2, n, "Got incompatible conversion results"
+ )
results[n] += 1
self.assertEqual(results, expected)
- self.results.append(('compatible spline lengths', results))
+ self.results.append(("compatible spline lengths", results))
def test_does_not_exceed_tolerance(self):
"""Test that conversion results do not exceed given error tolerance."""
@@ -107,7 +103,7 @@ class CurveToQuadraticTest(unittest.TestCase):
for error in self.single_errors:
results[round(error, 1)] += 1
self.assertLessEqual(error, MAX_ERR)
- self.results.append(('single errors', results))
+ self.results.append(("single errors", results))
def test_does_not_exceed_tolerance_multiple(self):
"""Test that error tolerance isn't exceeded for multiple curves."""
@@ -117,7 +113,7 @@ class CurveToQuadraticTest(unittest.TestCase):
for error in errors:
results[round(error, 1)] += 1
self.assertLessEqual(error, MAX_ERR)
- self.results.append(('compatible errors', results))
+ self.results.append(("compatible errors", results))
@classmethod
def curve_spline_dist(cls, bezier, spline, total_steps=20):
@@ -135,9 +131,13 @@ class CurveToQuadraticTest(unittest.TestCase):
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)))
+ error = max(
+ error,
+ cls.dist(
+ cls.cubic_bezier_at(bezier, (j / steps + i) / n),
+ cls.quadratic_bezier_at(segment, j / steps),
+ ),
+ )
return error
@classmethod
@@ -157,8 +157,7 @@ class CurveToQuadraticTest(unittest.TestCase):
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)
+ 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):
@@ -170,9 +169,24 @@ class CurveToQuadraticTest(unittest.TestCase):
_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)
+ 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,
+ )
+
+
+class AllQuadraticFalseTest(unittest.TestCase):
+ def test_cubic(self):
+ cubic = [(0, 0), (0, 1), (2, 1), (2, 0)]
+ result = curve_to_quadratic(cubic, 0.1, all_quadratic=False)
+ assert result == cubic
+
+ def test_quadratic(self):
+ cubic = [(0, 0), (2, 2), (4, 2), (6, 0)]
+ result = curve_to_quadratic(cubic, 0.1, all_quadratic=False)
+ quadratic = [(0, 0), (3, 3), (6, 0)]
+ assert result == quadratic
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main()
diff --git a/Tests/cu2qu/ufo_test.py b/Tests/cu2qu/ufo_test.py
index b678ae3d..aa9765e6 100644
--- a/Tests/cu2qu/ufo_test.py
+++ b/Tests/cu2qu/ufo_test.py
@@ -20,7 +20,7 @@ import pytest
ufoLib2 = pytest.importorskip("ufoLib2")
-DATADIR = os.path.join(os.path.dirname(__file__), 'data')
+DATADIR = os.path.join(os.path.dirname(__file__), "data")
TEST_UFOS = [
os.path.join(DATADIR, "RobotoSubset-Regular.ufo"),
@@ -34,7 +34,6 @@ def fonts():
class FontsToQuadraticTest(object):
-
def test_modified(self, fonts):
modified = fonts_to_quadratic(fonts)
assert modified
@@ -42,67 +41,74 @@ class FontsToQuadraticTest(object):
def test_stats(self, fonts):
stats = {}
fonts_to_quadratic(fonts, stats=stats)
- assert stats == {'1': 1, '2': 79, '3': 130, '4': 2}
+ 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):
+ def test_remember_curve_type_quadratic(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_remember_curve_type_mixed(self, fonts):
+ fonts_to_quadratic(fonts, remember_curve_type=True, all_quadratic=False)
+ assert fonts[0].lib[CURVE_TYPE_LIB_KEY] == "mixed"
+ 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]
+ 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}
+ 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}
+ 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}
+ 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}
+ 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)
+ assert font_to_quadratic(fonts[0], max_err_em=0.002, reverse_direction=True)
+ assert font_to_quadratic(
+ fonts[1], max_err_em=0.002, reverse_direction=True, all_quadratic=False
+ )
class GlyphsToQuadraticTest(object):
-
@pytest.mark.parametrize(
["glyph", "expected"],
- [('A', False), # contains no curves, it is not modified
- ('a', True)],
- ids=['lines-only', 'has-curves']
+ [("A", False), ("a", True)], # contains no curves, it is not modified
+ ids=["lines-only", "has-curves"],
)
def test_modified(self, fonts, glyph, expected):
glyphs = [f[glyph] for f in fonts]
@@ -110,28 +116,27 @@ class GlyphsToQuadraticTest(object):
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}
+ 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]
+ glyphs = [f["a"] for f in fonts]
stats = {}
glyphs_to_quadratic(glyphs, max_err=4.096, stats=stats)
- assert stats == {'2': 11, '3': 1}
+ assert stats == {"2": 11, "3": 1}
def test_max_err_list(self, fonts):
- glyphs = [f['a'] for f in 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}
+ assert stats == {"2": 11, "3": 1}
def test_reverse_direction(self, fonts):
- glyphs = [f['A'] for f in 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)
+ assert glyph_to_quadratic(fonts[0]["a"], max_err=4.096, reverse_direction=True)
@pytest.mark.parametrize(
["outlines", "exception", "message"],
@@ -139,32 +144,31 @@ class GlyphsToQuadraticTest(object):
[
[
[
- ('moveTo', ((0, 0),)),
- ('curveTo', ((1, 1), (2, 2), (3, 3))),
- ('curveTo', ((4, 4), (5, 5), (6, 6))),
- ('closePath', ()),
+ ("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', ()),
- ]
+ ("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", ((0, 0),)),
+ ("curveTo", ((1, 1), (2, 2), (3, 3))),
+ ("closePath", ()),
],
[
- ('moveTo', ((4, 4),)),
- ('lineTo', ((5, 5),)),
- ('closePath', ()),
+ ("moveTo", ((4, 4),)),
+ ("lineTo", ((5, 5),)),
+ ("closePath", ()),
],
],
IncompatibleSegmentTypesError,
@@ -174,7 +178,7 @@ class GlyphsToQuadraticTest(object):
ids=[
"unequal-length",
"different-segment-types",
- ]
+ ],
)
def test_incompatible_glyphs(self, outlines, exception, message):
glyphs = []
@@ -193,18 +197,22 @@ class GlyphsToQuadraticTest(object):
font1.info.unitsPerEm = 1000
glyph1 = font1.newGlyph("a")
pen1 = glyph1.getPen()
- for operator, args in [("moveTo", ((0, 0),)),
- ("lineTo", ((1, 1),)),
- ("endPath", ())]:
+ 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", ())]:
+ 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:
@@ -212,7 +220,7 @@ class GlyphsToQuadraticTest(object):
assert excinfo.match("fonts contains incompatible glyphs: 'a'")
assert hasattr(excinfo.value, "glyph_errors")
- error = excinfo.value.glyph_errors['a']
+ error = excinfo.value.glyph_errors["a"]
assert isinstance(error, IncompatibleSegmentTypesError)
assert error.segments == {1: ["line", "curve"]}
@@ -238,7 +246,7 @@ class GlyphsToQuadraticTest(object):
def test_ignore_components(self):
glyph = ufoLib2.objects.Glyph()
pen = glyph.getPen()
- pen.addComponent('a', (1, 0, 0, 1, 0, 0))
+ pen.addComponent("a", (1, 0, 0, 1, 0, 0))
pen.moveTo((0, 0))
pen.curveTo((1, 1), (2, 2), (3, 3))
pen.closePath()
@@ -276,10 +284,5 @@ class GlyphsToQuadraticTest(object):
(0, 101),
(0, 101),
],
- [
- (1, 651),
- (4, 651),
- (3, 101),
- (2, 101)
- ],
+ [(1, 651), (4, 651), (3, 101), (2, 101)],
]
diff --git a/Tests/designspaceLib/data/test_avar2.designspace b/Tests/designspaceLib/data/test_avar2.designspace
new file mode 100644
index 00000000..d54588a6
--- /dev/null
+++ b/Tests/designspaceLib/data/test_avar2.designspace
@@ -0,0 +1,117 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="JSTF" name="Justify" minimum="-100" maximum="100" default="0"/>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="400">
+ <map input="100" output="26"/>
+ <map input="200" output="39"/>
+ <map input="300" output="58"/>
+ <map input="400" output="90"/>
+ <map input="500" output="108"/>
+ <map input="600" output="128"/>
+ <map input="700" output="151"/>
+ <map input="800" output="169"/>
+ <map input="900" output="190"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="62.5" maximum="100" default="100">
+ <map input="62.5" output="70"/>
+ <map input="75" output="79"/>
+ <map input="87.5" output="89"/>
+ <map input="100" output="100"/>
+ </axis>
+ <mappings>
+ <mapping>
+ <input>
+ <dimension name="Justify" xvalue="-100"/>
+ <dimension name="Width" xvalue="100"/>
+ </input>
+ <output>
+ <dimension name="Width" xvalue="70"/>
+ </output>
+ </mapping>
+ </mappings>
+ </axes>
+ <variable-fonts>
+ <variable-font name="NotoSansArabic_Justify_Width">
+ <axis-subsets>
+ <axis-subset name="Justify"/>
+ <axis-subset name="Width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="NotoSansArabic_Weight_Width">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ <axis-subset name="Width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="NotoSansArabic_Weight">
+ <axis-subsets>
+ <axis-subset name="Weight"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="NotoSansArabic_Width">
+ <axis-subsets>
+ <axis-subset name="Width"/>
+ </axis-subsets>
+ </variable-font>
+ <variable-font name="NotoSansArabic_Justify">
+ <axis-subsets>
+ <axis-subset name="Justify"/>
+ </axis-subsets>
+ </variable-font>
+ </variable-fonts>
+ <sources>
+ <source filename="NotoSansArabic-Light.ufo" name="Noto Sans Arabic Light" familyname="Noto Sans Arabic" stylename="Light">
+ <location>
+ <dimension name="Weight" xvalue="26"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="NotoSansArabic-Regular.ufo" name="Noto Sans Arabic Regular" familyname="Noto Sans Arabic" stylename="Regular">
+ <lib copy="1"/>
+ <groups copy="1"/>
+ <features copy="1"/>
+ <info copy="1"/>
+ <location>
+ <dimension name="Weight" xvalue="90"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="NotoSansArabic-SemiBold.ufo" name="Noto Sans Arabic SemiBold" familyname="Noto Sans Arabic" stylename="SemiBold">
+ <location>
+ <dimension name="Weight" xvalue="151"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="NotoSansArabic-Bold.ufo" name="Noto Sans Arabic Bold" familyname="Noto Sans Arabic" stylename="Bold">
+ <location>
+ <dimension name="Weight" xvalue="190"/>
+ <dimension name="Width" xvalue="100"/>
+ </location>
+ </source>
+ <source filename="NotoSansArabic-CondensedLight.ufo" name="Noto Sans Arabic Condensed Light" familyname="Noto Sans Arabic" stylename="Condensed Light">
+ <location>
+ <dimension name="Weight" xvalue="26"/>
+ <dimension name="Width" xvalue="70"/>
+ </location>
+ </source>
+ <source filename="NotoSansArabic-Condensed.ufo" name="Noto Sans Arabic Condensed" familyname="Noto Sans Arabic" stylename="Condensed">
+ <location>
+ <dimension name="Weight" xvalue="90"/>
+ <dimension name="Width" xvalue="70"/>
+ </location>
+ </source>
+ <source filename="NotoSansArabic-CondensedSemiBold.ufo" name="Noto Sans Arabic Condensed SemiBold" familyname="Noto Sans Arabic" stylename="Condensed SemiBold">
+ <location>
+ <dimension name="Weight" xvalue="151"/>
+ <dimension name="Width" xvalue="70"/>
+ </location>
+ </source>
+ <source filename="NotoSansArabic-CondensedBold.ufo" name="Noto Sans Arabic Condensed Bold" familyname="Noto Sans Arabic" stylename="Condensed Bold">
+ <location>
+ <dimension name="Weight" xvalue="190"/>
+ <dimension name="Width" xvalue="70"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/designspaceLib/data/test_v5.designspace b/Tests/designspaceLib/data/test_v5.designspace
index d2b3cdae..498956cb 100644
--- a/Tests/designspaceLib/data/test_v5.designspace
+++ b/Tests/designspaceLib/data/test_v5.designspace
@@ -21,6 +21,13 @@
<label uservalue="600" userminimum="450" usermaximum="650" name="Semi Bold"/>
<label uservalue="700" userminimum="650" usermaximum="850" name="Bold"/>
<label uservalue="900" userminimum="850" usermaximum="900" name="Black"/>
+ <!--
+ Add "recursive" linked user values, see:
+ https://github.com/fonttools/fonttools/issues/2852
+ https://github.com/fonttools/fonttools/discussions/2790
+ -->
+ <label uservalue="400" name="Regular" elidable="true" linkeduservalue="700"/>
+ <label uservalue="700" name="Bold" linkeduservalue="400"/>
</labels>
</axis>
diff --git a/Tests/designspaceLib/designspace_test.py b/Tests/designspaceLib/designspace_test.py
index ee2d19e6..ceddfd10 100644
--- a/Tests/designspaceLib/designspace_test.py
+++ b/Tests/designspaceLib/designspace_test.py
@@ -1,12 +1,15 @@
# coding=utf-8
import os
+from pathlib import Path
import re
+import shutil
import pytest
from fontTools import ttLib
from fontTools.designspaceLib import (
AxisDescriptor,
+ AxisMappingDescriptor,
AxisLabelDescriptor,
DesignSpaceDocument,
DesignSpaceDocumentError,
@@ -21,20 +24,22 @@ from fontTools.designspaceLib import (
from fontTools.designspaceLib.types import Range
from fontTools.misc import plistlib
+from .fixtures import datadir
+
def _axesAsDict(axes):
"""
- Make the axis data we have available in
+ Make the axis data we have available in
"""
axesDict = {}
for axisDescriptor in axes:
d = {
- 'name': axisDescriptor.name,
- 'tag': axisDescriptor.tag,
- 'minimum': axisDescriptor.minimum,
- 'maximum': axisDescriptor.maximum,
- 'default': axisDescriptor.default,
- 'map': axisDescriptor.map,
+ "name": axisDescriptor.name,
+ "tag": axisDescriptor.tag,
+ "minimum": axisDescriptor.minimum,
+ "maximum": axisDescriptor.maximum,
+ "default": axisDescriptor.default,
+ "map": axisDescriptor.map,
}
axesDict[axisDescriptor.name] = d
return axesDict
@@ -72,8 +77,8 @@ def test_fill_document(tmpdir):
a1.name = "weight"
a1.tag = "wght"
# note: just to test the element language, not an actual label name recommendations.
- a1.labelNames[u'fa-IR'] = u"قطر"
- a1.labelNames[u'en'] = u"Wéíght"
+ a1.labelNames["fa-IR"] = "قطر"
+ a1.labelNames["en"] = "Wéíght"
doc.addAxis(a1)
a2 = AxisDescriptor()
a2.minimum = 0
@@ -83,7 +88,7 @@ def test_fill_document(tmpdir):
a2.tag = "wdth"
a2.map = [(0.0, 10.0), (15.0, 20.0), (401.0, 66.0), (1000.0, 990.0)]
a2.hidden = True
- a2.labelNames[u'fr'] = u"Chasse"
+ a2.labelNames["fr"] = "Chasse"
doc.addAxis(a2)
# add master 1
@@ -131,18 +136,22 @@ def test_fill_document(tmpdir):
i1.familyName = "InstanceFamilyName"
i1.styleName = "InstanceStyleName"
i1.name = "instance.ufo1"
- i1.location = dict(weight=500, spooky=666) # this adds a dimension that is not defined.
+ i1.location = dict(
+ weight=500, spooky=666
+ ) # this adds a dimension that is not defined.
i1.postScriptFontName = "InstancePostscriptName"
i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
i1.styleMapStyleName = "InstanceStyleMapStyleName"
i1.localisedStyleName = dict(fr="Demigras", ja="半ば")
i1.localisedFamilyName = dict(fr="Montserrat", ja="モンセラート")
i1.localisedStyleMapStyleName = dict(de="Standard")
- i1.localisedStyleMapFamilyName = dict(de="Montserrat Halbfett", ja="モンセラート SemiBold")
+ i1.localisedStyleMapFamilyName = dict(
+ de="Montserrat Halbfett", ja="モンセラート SemiBold"
+ )
glyphData = dict(name="arrow", mute=True, unicodes=[0x123, 0x124, 0x125])
- i1.glyphs['arrow'] = glyphData
- i1.lib['com.coolDesignspaceApp.binaryData'] = plistlib.Data(b'<binary gunk>')
- i1.lib['com.coolDesignspaceApp.specimenText'] = "Hamburgerwhatever"
+ i1.glyphs["arrow"] = glyphData
+ i1.lib["com.coolDesignspaceApp.binaryData"] = plistlib.Data(b"<binary gunk>")
+ i1.lib["com.coolDesignspaceApp.specimenText"] = "Hamburgerwhatever"
doc.addInstance(i1)
# add instance 2
i2 = InstanceDescriptor()
@@ -151,46 +160,51 @@ def test_fill_document(tmpdir):
i2.styleName = "InstanceStyleName"
i2.name = "instance.ufo2"
# anisotropic location
- i2.location = dict(weight=500, width=(400,300))
+ i2.location = dict(weight=500, width=(400, 300))
i2.postScriptFontName = "InstancePostscriptName"
i2.styleMapFamilyName = "InstanceStyleMapFamilyName"
i2.styleMapStyleName = "InstanceStyleMapStyleName"
- glyphMasters = [dict(font="master.ufo1", glyphName="BB", location=dict(width=20,weight=20)), dict(font="master.ufo2", glyphName="CC", location=dict(width=900,weight=900))]
+ glyphMasters = [
+ dict(font="master.ufo1", glyphName="BB", location=dict(width=20, weight=20)),
+ dict(font="master.ufo2", glyphName="CC", location=dict(width=900, weight=900)),
+ ]
glyphData = dict(name="arrow", unicodes=[101, 201, 301])
- glyphData['masters'] = glyphMasters
- glyphData['note'] = "A note about this glyph"
- glyphData['instanceLocation'] = dict(width=100, weight=120)
- i2.glyphs['arrow'] = glyphData
- i2.glyphs['arrow2'] = dict(mute=False)
+ glyphData["masters"] = glyphMasters
+ glyphData["note"] = "A note about this glyph"
+ glyphData["instanceLocation"] = dict(width=100, weight=120)
+ i2.glyphs["arrow"] = glyphData
+ i2.glyphs["arrow2"] = dict(mute=False)
doc.addInstance(i2)
doc.filename = "suggestedFileName.designspace"
- doc.lib['com.coolDesignspaceApp.previewSize'] = 30
+ doc.lib["com.coolDesignspaceApp.previewSize"] = 30
# write some rules
r1 = RuleDescriptor()
r1.name = "named.rule.1"
- r1.conditionSets.append([
- dict(name='axisName_a', minimum=0, maximum=1),
- dict(name='axisName_b', minimum=2, maximum=3)
- ])
+ r1.conditionSets.append(
+ [
+ dict(name="axisName_a", minimum=0, maximum=1),
+ dict(name="axisName_b", minimum=2, maximum=3),
+ ]
+ )
r1.subs.append(("a", "a.alt"))
doc.addRule(r1)
# write the document; without an explicit format it will be 5.0 by default
doc.write(testDocPath5)
assert os.path.exists(testDocPath5)
- assert_equals_test_file(testDocPath5, 'data/test_v5_original.designspace')
+ assert_equals_test_file(testDocPath5, "data/test_v5_original.designspace")
# write again with an explicit format = 4.1
doc.formatVersion = "4.1"
doc.write(testDocPath)
assert os.path.exists(testDocPath)
- assert_equals_test_file(testDocPath, 'data/test_v4_original.designspace')
+ assert_equals_test_file(testDocPath, "data/test_v4_original.designspace")
# import it again
new = DesignSpaceDocument()
new.read(testDocPath)
- assert new.default.location == {'width': 20.0, 'weight': 0.0}
- assert new.filename == 'test_v4.designspace'
+ assert new.default.location == {"width": 20.0, "weight": 0.0}
+ assert new.filename == "test_v4.designspace"
assert new.lib == doc.lib
assert new.instances[0].lib == doc.instances[0].lib
@@ -240,10 +254,10 @@ def test_unicodes(tmpdir):
i1.name = "instance.ufo1"
i1.location = dict(weight=500)
glyphData = dict(name="arrow", mute=True, unicodes=[100, 200, 300])
- i1.glyphs['arrow'] = glyphData
+ i1.glyphs["arrow"] = glyphData
doc.addInstance(i1)
# now we have sources and instances, but no axes yet.
- doc.axes = [] # clear the axes
+ doc.axes = [] # clear the axes
# write some axes
a1 = AxisDescriptor()
a1.minimum = 0
@@ -260,13 +274,13 @@ def test_unicodes(tmpdir):
new.read(testDocPath)
new.write(testDocPath2)
# compare the file contents
- with open(testDocPath, 'r', encoding='utf-8') as f1:
+ with open(testDocPath, "r", encoding="utf-8") as f1:
t1 = f1.read()
- with open(testDocPath2, 'r', encoding='utf-8') as f2:
+ with open(testDocPath2, "r", encoding="utf-8") as f2:
t2 = f2.read()
assert t1 == t2
# check the unicode values read from the document
- assert new.instances[0].glyphs['arrow']['unicodes'] == [100,200,300]
+ assert new.instances[0].glyphs["arrow"]["unicodes"] == [100, 200, 300]
def test_localisedNames(tmpdir):
@@ -299,20 +313,22 @@ def test_localisedNames(tmpdir):
i1.styleMapFamilyName = "Montserrat SemiBold"
i1.styleMapStyleName = "Regular"
i1.setFamilyName("Montserrat", "fr")
- i1.setFamilyName(u"モンセラート", "ja")
+ i1.setFamilyName("モンセラート", "ja")
i1.setStyleName("Demigras", "fr")
- i1.setStyleName(u"半ば", "ja")
- i1.setStyleMapStyleName(u"Standard", "de")
+ i1.setStyleName("半ば", "ja")
+ i1.setStyleMapStyleName("Standard", "de")
i1.setStyleMapFamilyName("Montserrat Halbfett", "de")
- i1.setStyleMapFamilyName(u"モンセラート SemiBold", "ja")
+ i1.setStyleMapFamilyName("モンセラート SemiBold", "ja")
i1.name = "instance.ufo1"
- i1.location = dict(weight=500, spooky=666) # this adds a dimension that is not defined.
+ i1.location = dict(
+ weight=500, spooky=666
+ ) # this adds a dimension that is not defined.
i1.postScriptFontName = "InstancePostscriptName"
glyphData = dict(name="arrow", mute=True, unicodes=[0x123])
- i1.glyphs['arrow'] = glyphData
+ i1.glyphs["arrow"] = glyphData
doc.addInstance(i1)
# now we have sources and instances, but no axes yet.
- doc.axes = [] # clear the axes
+ doc.axes = [] # clear the axes
# write some axes
a1 = AxisDescriptor()
a1.minimum = 0
@@ -321,8 +337,8 @@ def test_localisedNames(tmpdir):
a1.name = "weight"
a1.tag = "wght"
# note: just to test the element language, not an actual label name recommendations.
- a1.labelNames[u'fa-IR'] = u"قطر"
- a1.labelNames[u'en'] = u"Wéíght"
+ a1.labelNames["fa-IR"] = "قطر"
+ a1.labelNames["en"] = "Wéíght"
doc.addAxis(a1)
a2 = AxisDescriptor()
a2.minimum = 0
@@ -331,7 +347,7 @@ def test_localisedNames(tmpdir):
a2.name = "width"
a2.tag = "wdth"
a2.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
- a2.labelNames[u'fr'] = u"Poids"
+ a2.labelNames["fr"] = "Poids"
doc.addAxis(a2)
# add an axis that is not part of any location to see if that works
a3 = AxisDescriptor()
@@ -341,14 +357,16 @@ def test_localisedNames(tmpdir):
a3.name = "spooky"
a3.tag = "spok"
a3.map = [(0.0, 10.0), (401.0, 66.0), (1000.0, 990.0)]
- #doc.addAxis(a3) # uncomment this line to test the effects of default axes values
+ # doc.addAxis(a3) # uncomment this line to test the effects of default axes values
# write some rules
r1 = RuleDescriptor()
r1.name = "named.rule.1"
- r1.conditionSets.append([
- dict(name='weight', minimum=200, maximum=500),
- dict(name='width', minimum=0, maximum=150)
- ])
+ r1.conditionSets.append(
+ [
+ dict(name="weight", minimum=200, maximum=500),
+ dict(name="width", minimum=0, maximum=150),
+ ]
+ )
r1.subs.append(("a", "a.alt"))
doc.addRule(r1)
# write the document
@@ -358,9 +376,9 @@ def test_localisedNames(tmpdir):
new = DesignSpaceDocument()
new.read(testDocPath)
new.write(testDocPath2)
- with open(testDocPath, 'r', encoding='utf-8') as f1:
+ with open(testDocPath, "r", encoding="utf-8") as f1:
t1 = f1.read()
- with open(testDocPath2, 'r', encoding='utf-8') as f2:
+ with open(testDocPath2, "r", encoding="utf-8") as f2:
t2 = f2.read()
assert t1 == t2
@@ -378,7 +396,7 @@ def test_handleNoAxes(tmpdir):
# Case 1: No axes element in the document, but there are sources and instances
doc = DesignSpaceDocument()
- for name, value in [('One', 1),('Two', 2),('Three', 3)]:
+ for name, value in [("One", 1), ("Two", 2), ("Three", 3)]:
a = AxisDescriptor()
a.minimum = 0
a.maximum = 1000
@@ -417,7 +435,7 @@ def test_handleNoAxes(tmpdir):
i1.familyName = "InstanceFamilyName"
i1.styleName = "InstanceStyleName"
i1.name = "instance.ufo1"
- i1.location = dict(axisNameOne=(-1000,500), axisNameTwo=100)
+ i1.location = dict(axisNameOne=(-1000, 500), axisNameTwo=100)
i1.postScriptFontName = "InstancePostscriptName"
i1.styleMapFamilyName = "InstanceStyleMapFamilyName"
i1.styleMapStyleName = "InstanceStyleMapStyleName"
@@ -428,6 +446,7 @@ def test_handleNoAxes(tmpdir):
verify.read(testDocPath)
verify.write(testDocPath2)
+
def test_pathNameResolve(tmpdir):
tmpdir = str(tmpdir)
# test how descriptor.path and descriptor.filename are resolved
@@ -499,7 +518,9 @@ def test_pathNameResolve(tmpdir):
verify.read(testDocPath3)
assert verify.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
# make the absolute path for filename so we can see if it matches the path
- p = os.path.abspath(os.path.join(os.path.dirname(testDocPath3), verify.sources[0].filename))
+ p = os.path.abspath(
+ os.path.join(os.path.dirname(testDocPath3), verify.sources[0].filename)
+ )
assert verify.sources[0].path == posix(p)
# Case 4: the filename points to one file, the path points to another. The path takes precedence.
@@ -529,7 +550,7 @@ def test_pathNameResolve(tmpdir):
s.familyName = "MasterFamilyName"
s.styleName = "MasterStyleNameOne"
doc.addSource(s)
- doc.write(testDocPath5) # so that the document has a path
+ doc.write(testDocPath5) # so that the document has a path
doc.updateFilenameFromPath()
assert doc.sources[0].filename == "masters/masterTest1.ufo"
@@ -543,7 +564,7 @@ def test_pathNameResolve(tmpdir):
s.location = dict(weight=0)
s.familyName = "MasterFamilyName"
s.styleName = "MasterStyleNameOne"
- doc.write(testDocPath5) # so that the document has a path
+ doc.write(testDocPath5) # so that the document has a path
doc.addSource(s)
assert doc.sources[0].filename == "../somewhere/over/the/rainbow.ufo"
doc.updateFilenameFromPath(force=True)
@@ -561,21 +582,22 @@ def test_normalise1():
a1.name = "axisName_a"
a1.tag = "TAGA"
doc.addAxis(a1)
- assert doc.normalizeLocation(dict(axisName_a=0)) == {'axisName_a': 0.0}
- assert doc.normalizeLocation(dict(axisName_a=1000)) == {'axisName_a': 1.0}
+ assert doc.normalizeLocation(dict(axisName_a=0)) == {"axisName_a": 0.0}
+ assert doc.normalizeLocation(dict(axisName_a=1000)) == {"axisName_a": 1.0}
# clipping beyond max values:
- assert doc.normalizeLocation(dict(axisName_a=1001)) == {'axisName_a': 1.0}
- assert doc.normalizeLocation(dict(axisName_a=500)) == {'axisName_a': 0.5}
- assert doc.normalizeLocation(dict(axisName_a=-1000)) == {'axisName_a': -1.0}
- assert doc.normalizeLocation(dict(axisName_a=-1001)) == {'axisName_a': -1.0}
+ assert doc.normalizeLocation(dict(axisName_a=1001)) == {"axisName_a": 1.0}
+ assert doc.normalizeLocation(dict(axisName_a=500)) == {"axisName_a": 0.5}
+ assert doc.normalizeLocation(dict(axisName_a=-1000)) == {"axisName_a": -1.0}
+ assert doc.normalizeLocation(dict(axisName_a=-1001)) == {"axisName_a": -1.0}
# anisotropic coordinates normalise to isotropic
- assert doc.normalizeLocation(dict(axisName_a=(1000, -1000))) == {'axisName_a': 1.0}
+ assert doc.normalizeLocation(dict(axisName_a=(1000, -1000))) == {"axisName_a": 1.0}
doc.normalize()
r = []
for axis in doc.axes:
r.append((axis.name, axis.minimum, axis.default, axis.maximum))
r.sort()
- assert r == [('axisName_a', -1.0, 0.0, 1.0)]
+ assert r == [("axisName_a", -1.0, 0.0, 1.0)]
+
def test_normalise2():
# normalisation with minimum > 0
@@ -587,22 +609,25 @@ def test_normalise2():
a2.default = 100
a2.name = "axisName_b"
doc.addAxis(a2)
- assert doc.normalizeLocation(dict(axisName_b=0)) == {'axisName_b': 0.0}
- assert doc.normalizeLocation(dict(axisName_b=1000)) == {'axisName_b': 1.0}
+ assert doc.normalizeLocation(dict(axisName_b=0)) == {"axisName_b": 0.0}
+ assert doc.normalizeLocation(dict(axisName_b=1000)) == {"axisName_b": 1.0}
# clipping beyond max values:
- assert doc.normalizeLocation(dict(axisName_b=1001)) == {'axisName_b': 1.0}
- assert doc.normalizeLocation(dict(axisName_b=500)) == {'axisName_b': 0.4444444444444444}
- assert doc.normalizeLocation(dict(axisName_b=-1000)) == {'axisName_b': 0.0}
- assert doc.normalizeLocation(dict(axisName_b=-1001)) == {'axisName_b': 0.0}
+ assert doc.normalizeLocation(dict(axisName_b=1001)) == {"axisName_b": 1.0}
+ assert doc.normalizeLocation(dict(axisName_b=500)) == {
+ "axisName_b": 0.4444444444444444
+ }
+ assert doc.normalizeLocation(dict(axisName_b=-1000)) == {"axisName_b": 0.0}
+ assert doc.normalizeLocation(dict(axisName_b=-1001)) == {"axisName_b": 0.0}
# anisotropic coordinates normalise to isotropic
- assert doc.normalizeLocation(dict(axisName_b=(1000,-1000))) == {'axisName_b': 1.0}
- assert doc.normalizeLocation(dict(axisName_b=1001)) == {'axisName_b': 1.0}
+ assert doc.normalizeLocation(dict(axisName_b=(1000, -1000))) == {"axisName_b": 1.0}
+ assert doc.normalizeLocation(dict(axisName_b=1001)) == {"axisName_b": 1.0}
doc.normalize()
r = []
for axis in doc.axes:
r.append((axis.name, axis.minimum, axis.default, axis.maximum))
r.sort()
- assert r == [('axisName_b', 0.0, 0.0, 1.0)]
+ assert r == [("axisName_b", 0.0, 0.0, 1.0)]
+
def test_normalise3():
# normalisation of negative values, with default == maximum
@@ -614,16 +639,17 @@ def test_normalise3():
a3.default = 0
a3.name = "ccc"
doc.addAxis(a3)
- assert doc.normalizeLocation(dict(ccc=0)) == {'ccc': 0.0}
- assert doc.normalizeLocation(dict(ccc=1)) == {'ccc': 0.0}
- assert doc.normalizeLocation(dict(ccc=-1000)) == {'ccc': -1.0}
- assert doc.normalizeLocation(dict(ccc=-1001)) == {'ccc': -1.0}
+ assert doc.normalizeLocation(dict(ccc=0)) == {"ccc": 0.0}
+ assert doc.normalizeLocation(dict(ccc=1)) == {"ccc": 0.0}
+ assert doc.normalizeLocation(dict(ccc=-1000)) == {"ccc": -1.0}
+ assert doc.normalizeLocation(dict(ccc=-1001)) == {"ccc": -1.0}
doc.normalize()
r = []
for axis in doc.axes:
r.append((axis.name, axis.minimum, axis.default, axis.maximum))
r.sort()
- assert r == [('ccc', -1.0, 0.0, 0.0)]
+ assert r == [("ccc", -1.0, 0.0, 0.0)]
+
def test_normalise4():
# normalisation with a map
@@ -634,14 +660,15 @@ def test_normalise4():
a4.maximum = 1000
a4.default = 0
a4.name = "ddd"
- a4.map = [(0,100), (300, 500), (600, 500), (1000,900)]
+ a4.map = [(0, 100), (300, 500), (600, 500), (1000, 900)]
doc.addAxis(a4)
doc.normalize()
r = []
for axis in doc.axes:
r.append((axis.name, axis.map))
r.sort()
- assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
+ assert r == [("ddd", [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
+
def test_axisMapping():
# note: because designspance lib does not do any actual
@@ -653,68 +680,113 @@ def test_axisMapping():
a4.maximum = 1000
a4.default = 0
a4.name = "ddd"
- a4.map = [(0,100), (300, 500), (600, 500), (1000,900)]
+ a4.map = [(0, 100), (300, 500), (600, 500), (1000, 900)]
doc.addAxis(a4)
doc.normalize()
r = []
for axis in doc.axes:
r.append((axis.name, axis.map))
r.sort()
- assert r == [('ddd', [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
+ assert r == [("ddd", [(0, 0.0), (300, 0.5), (600, 0.5), (1000, 1.0)])]
+
+
+def test_axisMappingsRoundtrip(tmpdir):
+ # tests of axisMappings in a document, roundtripping.
+
+ tmpdir = str(tmpdir)
+ srcDocPath = (Path(__file__) / "../data/test_avar2.designspace").resolve()
+ testDocPath = os.path.join(tmpdir, "test_avar2.designspace")
+ shutil.copy(srcDocPath, testDocPath)
+ testDocPath2 = os.path.join(tmpdir, "test_avar2_roundtrip.designspace")
+ doc = DesignSpaceDocument()
+ doc.read(testDocPath)
+ assert doc.axisMappings
+ assert len(doc.axisMappings) == 1
+ assert doc.axisMappings[0].inputLocation == {"Justify": -100.0, "Width": 100.0}
+
+ # This is a bit of a hack, but it's the only way to make sure
+ # that the save works on Windows if the tempdir and the data
+ # dir are on different drives.
+ for descriptor in doc.sources + doc.instances:
+ descriptor.path = None
+
+ doc.write(testDocPath2)
+ # verify these results
+ doc2 = DesignSpaceDocument()
+ doc2.read(testDocPath2)
+ assert [mapping.inputLocation for mapping in doc.axisMappings] == [
+ mapping.inputLocation for mapping in doc2.axisMappings
+ ]
+ assert [mapping.outputLocation for mapping in doc.axisMappings] == [
+ mapping.outputLocation for mapping in doc2.axisMappings
+ ]
+
def test_rulesConditions(tmpdir):
# tests of rules, conditionsets and conditions
r1 = RuleDescriptor()
r1.name = "named.rule.1"
- r1.conditionSets.append([
- dict(name='axisName_a', minimum=0, maximum=1000),
- dict(name='axisName_b', minimum=0, maximum=3000)
- ])
+ r1.conditionSets.append(
+ [
+ dict(name="axisName_a", minimum=0, maximum=1000),
+ dict(name="axisName_b", minimum=0, maximum=3000),
+ ]
+ )
r1.subs.append(("a", "a.alt"))
- assert evaluateRule(r1, dict(axisName_a = 500, axisName_b = 0)) == True
- assert evaluateRule(r1, dict(axisName_a = 0, axisName_b = 0)) == True
- assert evaluateRule(r1, dict(axisName_a = 1000, axisName_b = 0)) == True
- assert evaluateRule(r1, dict(axisName_a = 1000, axisName_b = -100)) == False
- assert evaluateRule(r1, dict(axisName_a = 1000.0001, axisName_b = 0)) == False
- assert evaluateRule(r1, dict(axisName_a = -0.0001, axisName_b = 0)) == False
- assert evaluateRule(r1, dict(axisName_a = -100, axisName_b = 0)) == False
- assert processRules([r1], dict(axisName_a = 500, axisName_b = 0), ["a", "b", "c"]) == ['a.alt', 'b', 'c']
- assert processRules([r1], dict(axisName_a = 500, axisName_b = 0), ["a.alt", "b", "c"]) == ['a.alt', 'b', 'c']
- assert processRules([r1], dict(axisName_a = 2000, axisName_b = 0), ["a", "b", "c"]) == ['a', 'b', 'c']
+ assert evaluateRule(r1, dict(axisName_a=500, axisName_b=0)) == True
+ assert evaluateRule(r1, dict(axisName_a=0, axisName_b=0)) == True
+ assert evaluateRule(r1, dict(axisName_a=1000, axisName_b=0)) == True
+ assert evaluateRule(r1, dict(axisName_a=1000, axisName_b=-100)) == False
+ assert evaluateRule(r1, dict(axisName_a=1000.0001, axisName_b=0)) == False
+ assert evaluateRule(r1, dict(axisName_a=-0.0001, axisName_b=0)) == False
+ assert evaluateRule(r1, dict(axisName_a=-100, axisName_b=0)) == False
+ assert processRules([r1], dict(axisName_a=500, axisName_b=0), ["a", "b", "c"]) == [
+ "a.alt",
+ "b",
+ "c",
+ ]
+ assert processRules(
+ [r1], dict(axisName_a=500, axisName_b=0), ["a.alt", "b", "c"]
+ ) == ["a.alt", "b", "c"]
+ assert processRules([r1], dict(axisName_a=2000, axisName_b=0), ["a", "b", "c"]) == [
+ "a",
+ "b",
+ "c",
+ ]
# rule with only a maximum
r2 = RuleDescriptor()
r2.name = "named.rule.2"
- r2.conditionSets.append([dict(name='axisName_a', maximum=500)])
+ r2.conditionSets.append([dict(name="axisName_a", maximum=500)])
r2.subs.append(("b", "b.alt"))
- assert evaluateRule(r2, dict(axisName_a = 0)) == True
- assert evaluateRule(r2, dict(axisName_a = -500)) == True
- assert evaluateRule(r2, dict(axisName_a = 1000)) == False
+ assert evaluateRule(r2, dict(axisName_a=0)) == True
+ assert evaluateRule(r2, dict(axisName_a=-500)) == True
+ assert evaluateRule(r2, dict(axisName_a=1000)) == False
# rule with only a minimum
r3 = RuleDescriptor()
r3.name = "named.rule.3"
- r3.conditionSets.append([dict(name='axisName_a', minimum=500)])
+ r3.conditionSets.append([dict(name="axisName_a", minimum=500)])
r3.subs.append(("c", "c.alt"))
- assert evaluateRule(r3, dict(axisName_a = 0)) == False
- assert evaluateRule(r3, dict(axisName_a = 1000)) == True
- assert evaluateRule(r3, dict(axisName_a = 1000)) == True
+ assert evaluateRule(r3, dict(axisName_a=0)) == False
+ assert evaluateRule(r3, dict(axisName_a=1000)) == True
+ assert evaluateRule(r3, dict(axisName_a=1000)) == True
# rule with only a minimum, maximum in separate conditions
r4 = RuleDescriptor()
r4.name = "named.rule.4"
- r4.conditionSets.append([
- dict(name='axisName_a', minimum=500),
- dict(name='axisName_b', maximum=500)
- ])
+ r4.conditionSets.append(
+ [dict(name="axisName_a", minimum=500), dict(name="axisName_b", maximum=500)]
+ )
r4.subs.append(("c", "c.alt"))
- assert evaluateRule(r4, dict(axisName_a = 1000, axisName_b = 0)) == True
- assert evaluateRule(r4, dict(axisName_a = 0, axisName_b = 0)) == False
- assert evaluateRule(r4, dict(axisName_a = 1000, axisName_b = 1000)) == False
+ assert evaluateRule(r4, dict(axisName_a=1000, axisName_b=0)) == True
+ assert evaluateRule(r4, dict(axisName_a=0, axisName_b=0)) == False
+ assert evaluateRule(r4, dict(axisName_a=1000, axisName_b=1000)) == False
+
def test_rulesDocument(tmpdir):
# tests of rules in a document, roundtripping.
@@ -739,26 +811,51 @@ def test_rulesDocument(tmpdir):
doc.addAxis(b1)
r1 = RuleDescriptor()
r1.name = "named.rule.1"
- r1.conditionSets.append([
- dict(name='axisName_a', minimum=0, maximum=1000),
- dict(name='axisName_b', minimum=0, maximum=3000)
- ])
+ r1.conditionSets.append(
+ [
+ dict(name="axisName_a", minimum=0, maximum=1000),
+ dict(name="axisName_b", minimum=0, maximum=3000),
+ ]
+ )
r1.subs.append(("a", "a.alt"))
# rule with minium and maximum
doc.addRule(r1)
assert len(doc.rules) == 1
assert len(doc.rules[0].conditionSets) == 1
assert len(doc.rules[0].conditionSets[0]) == 2
- assert _axesAsDict(doc.axes) == {'axisName_a': {'map': [], 'name': 'axisName_a', 'default': 0, 'minimum': 0, 'maximum': 1000, 'tag': 'TAGA'}, 'axisName_b': {'map': [], 'name': 'axisName_b', 'default': 2000, 'minimum': 2000, 'maximum': 3000, 'tag': 'TAGB'}}
- assert doc.rules[0].conditionSets == [[
- {'minimum': 0, 'maximum': 1000, 'name': 'axisName_a'},
- {'minimum': 0, 'maximum': 3000, 'name': 'axisName_b'}]]
- assert doc.rules[0].subs == [('a', 'a.alt')]
+ assert _axesAsDict(doc.axes) == {
+ "axisName_a": {
+ "map": [],
+ "name": "axisName_a",
+ "default": 0,
+ "minimum": 0,
+ "maximum": 1000,
+ "tag": "TAGA",
+ },
+ "axisName_b": {
+ "map": [],
+ "name": "axisName_b",
+ "default": 2000,
+ "minimum": 2000,
+ "maximum": 3000,
+ "tag": "TAGB",
+ },
+ }
+ assert doc.rules[0].conditionSets == [
+ [
+ {"minimum": 0, "maximum": 1000, "name": "axisName_a"},
+ {"minimum": 0, "maximum": 3000, "name": "axisName_b"},
+ ]
+ ]
+ assert doc.rules[0].subs == [("a", "a.alt")]
doc.normalize()
- assert doc.rules[0].name == 'named.rule.1'
- assert doc.rules[0].conditionSets == [[
- {'minimum': 0.0, 'maximum': 1.0, 'name': 'axisName_a'},
- {'minimum': 0.0, 'maximum': 1.0, 'name': 'axisName_b'}]]
+ assert doc.rules[0].name == "named.rule.1"
+ assert doc.rules[0].conditionSets == [
+ [
+ {"minimum": 0.0, "maximum": 1.0, "name": "axisName_a"},
+ {"minimum": 0.0, "maximum": 1.0, "name": "axisName_b"},
+ ]
+ ]
# still one conditionset
assert len(doc.rules[0].conditionSets) == 1
doc.write(testDocPath)
@@ -778,17 +875,22 @@ def test_rulesDocument(tmpdir):
assert len(doc3.rules) == 1
assert len(doc3.rules[0].conditionSets) == 2
+
def _addUnwrappedCondition(path):
# only for testing, so we can make an invalid designspace file
# older designspace files may have conditions that are not wrapped in a conditionset
# These can be read into a new conditionset.
- with open(path, 'r', encoding='utf-8') as f:
+ with open(path, "r", encoding="utf-8") as f:
d = f.read()
print(d)
- d = d.replace('<rule name="named.rule.1">', '<rule name="named.rule.1">\n\t<condition maximum="22" minimum="33" name="axisName_a" />')
- with open(path, 'w', encoding='utf-8') as f:
+ d = d.replace(
+ '<rule name="named.rule.1">',
+ '<rule name="named.rule.1">\n\t<condition maximum="22" minimum="33" name="axisName_a" />',
+ )
+ with open(path, "w", encoding="utf-8") as f:
f.write(d)
+
def test_documentLib(tmpdir):
# roundtrip test of the document lib with some nested data
tmpdir = str(tmpdir)
@@ -801,7 +903,7 @@ def test_documentLib(tmpdir):
a1.maximum = 1000
a1.default = 0
doc.addAxis(a1)
- dummyData = dict(a=123, b=u"äbc", c=[1,2,3], d={'a':123})
+ dummyData = dict(a=123, b="äbc", c=[1, 2, 3], d={"a": 123})
dummyKey = "org.fontTools.designspaceLib"
doc.lib = {dummyKey: dummyData}
doc.write(testDocPath1)
@@ -855,17 +957,15 @@ def test_updatePaths(tmpdir):
def test_read_with_path_object():
- import pathlib
- source = (pathlib.Path(__file__) / "../data/test_v4_original.designspace").resolve()
+ source = (Path(__file__) / "../data/test_v4_original.designspace").resolve()
assert source.exists()
doc = DesignSpaceDocument()
doc.read(source)
def test_with_with_path_object(tmpdir):
- import pathlib
tmpdir = str(tmpdir)
- dest = pathlib.Path(tmpdir) / "test_v4_original.designspace"
+ dest = Path(tmpdir) / "test_v4_original.designspace"
doc = DesignSpaceDocument()
doc.write(dest)
assert dest.exists()
@@ -934,7 +1034,6 @@ def test_findDefault_axis_mapping():
def test_loadSourceFonts():
-
def opener(path):
font = ttLib.TTFont()
font.importXML(path)
@@ -945,7 +1044,7 @@ def test_loadSourceFonts():
os.path.dirname(os.path.dirname(__file__)),
"varLib",
"data",
- "SparseMasters.designspace"
+ "SparseMasters.designspace",
)
designspace = DesignSpaceDocument.fromfile(path)
@@ -976,7 +1075,7 @@ def test_addAxisDescriptor():
ds = DesignSpaceDocument()
axis = ds.addAxisDescriptor(
- name="Weight", tag="wght", minimum=100, default=400, maximum=900
+ name="Weight", tag="wght", minimum=100, default=400, maximum=900
)
assert ds.axes[0] is axis
@@ -988,6 +1087,19 @@ def test_addAxisDescriptor():
assert axis.maximum == 900
+def test_addAxisDescriptor():
+ ds = DesignSpaceDocument()
+
+ mapping = ds.addAxisMappingDescriptor(
+ inputLocation={"weight": 900, "width": 150}, outputLocation={"weight": 870}
+ )
+
+ assert ds.axisMappings[0] is mapping
+ assert isinstance(mapping, AxisMappingDescriptor)
+ assert mapping.inputLocation == {"weight": 900, "width": 150}
+ assert mapping.outputLocation == {"weight": 870}
+
+
def test_addSourceDescriptor():
ds = DesignSpaceDocument()
@@ -1003,10 +1115,10 @@ def test_addInstanceDescriptor():
ds = DesignSpaceDocument()
instance = ds.addInstanceDescriptor(
- name="TestInstance",
- location={"Weight": 400},
- styleName="Regular",
- styleMapStyleName="regular",
+ name="TestInstance",
+ location={"Weight": 400},
+ styleName="Regular",
+ styleMapStyleName="regular",
)
assert ds.instances[0] is instance
@@ -1064,3 +1176,10 @@ def test_Range_post_init():
assert r.minimum == -1
assert r.maximum == 2
assert r.default == -1
+
+
+def test_get_axes(datadir: Path) -> None:
+ ds = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")
+
+ assert ds.getAxis("Width") is ds.getAxisByTag("wdth")
+ assert ds.getAxis("Italic") is ds.getAxisByTag("ital")
diff --git a/Tests/designspaceLib/designspace_v5_test.py b/Tests/designspaceLib/designspace_v5_test.py
index 35ad29b2..84c927a2 100644
--- a/Tests/designspaceLib/designspace_v5_test.py
+++ b/Tests/designspaceLib/designspace_v5_test.py
@@ -78,6 +78,15 @@ def test_read_v5_document_simple(datadir):
AxisLabelDescriptor(
name="Black", userMinimum=850, userValue=900, userMaximum=900
),
+ AxisLabelDescriptor(
+ name="Regular",
+ userValue=400,
+ linkedUserValue=700,
+ elidable=True,
+ ),
+ AxisLabelDescriptor(
+ name="Bold", userValue=700, linkedUserValue=400
+ ),
],
),
AxisDescriptor(
diff --git a/Tests/designspaceLib/split_test.py b/Tests/designspaceLib/split_test.py
index 8708f704..3364133f 100644
--- a/Tests/designspaceLib/split_test.py
+++ b/Tests/designspaceLib/split_test.py
@@ -1,9 +1,16 @@
+import math
import shutil
from pathlib import Path
import pytest
from fontTools.designspaceLib import DesignSpaceDocument
-from fontTools.designspaceLib.split import splitInterpolable, splitVariableFonts, convert5to4
+from fontTools.designspaceLib.split import (
+ _conditionSetFrom,
+ convert5to4,
+ splitInterpolable,
+ splitVariableFonts,
+)
+from fontTools.designspaceLib.types import ConditionSet, Range
from .fixtures import datadir
@@ -74,7 +81,9 @@ def test_split(datadir, tmpdir, test_ds, expected_interpolable_spaces):
vfs = list(splitVariableFonts(sub_doc))
assert expected_vf_names == set(vf[0] for vf in vfs)
- loc_str = "_".join(f"{name}_{value}"for name, value in sorted(location.items()))
+ loc_str = "_".join(
+ f"{name}_{value}" for name, value in sorted(location.items())
+ )
data_out = datadir / "split_output" / f"{temp_in.stem}_{loc_str}.designspace"
temp_out = Path(tmpdir) / "out" / f"{temp_in.stem}_{loc_str}.designspace"
temp_out.parent.mkdir(exist_ok=True)
@@ -103,8 +112,6 @@ def test_split(datadir, tmpdir, test_ds, expected_interpolable_spaces):
)
-
-
@pytest.mark.parametrize(
"test_ds,expected_vfs",
[
@@ -137,7 +144,9 @@ def test_convert5to4(datadir, tmpdir, test_ds, expected_vfs):
assert variable_fonts.keys() == expected_vfs
for vf_name, vf in variable_fonts.items():
- data_out = (datadir / "convert5to4_output" / vf_name).with_suffix(".designspace")
+ data_out = (datadir / "convert5to4_output" / vf_name).with_suffix(
+ ".designspace"
+ )
temp_out = (Path(tmpdir) / "out" / vf_name).with_suffix(".designspace")
temp_out.parent.mkdir(exist_ok=True)
vf.write(temp_out)
@@ -148,3 +157,73 @@ def test_convert5to4(datadir, tmpdir, test_ds, expected_vfs):
assert data_out.read_text(encoding="utf-8") == temp_out.read_text(
encoding="utf-8"
)
+
+
+@pytest.mark.parametrize(
+ ["unbounded_condition"],
+ [
+ ({"name": "Weight", "minimum": 500},),
+ ({"name": "Weight", "maximum": 500},),
+ ({"name": "Weight", "minimum": 500, "maximum": None},),
+ ({"name": "Weight", "minimum": None, "maximum": 500},),
+ ],
+)
+def test_optional_min_max(unbounded_condition):
+ """Check that split functions can handle conditions that are partially
+ unbounded without tripping over None values and missing keys."""
+ doc = DesignSpaceDocument()
+
+ doc.addAxisDescriptor(
+ name="Weight", tag="wght", minimum=400, maximum=1000, default=400
+ )
+
+ doc.addRuleDescriptor(
+ name="unbounded",
+ conditionSets=[[unbounded_condition]],
+ )
+
+ assert len(list(splitInterpolable(doc))) == 1
+ assert len(list(splitVariableFonts(doc))) == 1
+
+
+@pytest.mark.parametrize(
+ ["condition", "expected_set"],
+ [
+ (
+ {"name": "axis", "minimum": 0.5},
+ {"axis": Range(minimum=0.5, maximum=math.inf)},
+ ),
+ (
+ {"name": "axis", "maximum": 0.5},
+ {"axis": Range(minimum=-math.inf, maximum=0.5)},
+ ),
+ (
+ {"name": "axis", "minimum": 0.5, "maximum": None},
+ {"axis": Range(minimum=0.5, maximum=math.inf)},
+ ),
+ (
+ {"name": "axis", "minimum": None, "maximum": 0.5},
+ {"axis": Range(minimum=-math.inf, maximum=0.5)},
+ ),
+ ],
+)
+def test_optional_min_max_internal(condition, expected_set: ConditionSet):
+ """Check that split's internal helper functions produce the correct output
+ for conditions that are partially unbounded."""
+ assert _conditionSetFrom([condition]) == expected_set
+
+
+def test_avar2(datadir):
+ ds = DesignSpaceDocument()
+ ds.read(datadir / "test_avar2.designspace")
+ _, subDoc = next(splitInterpolable(ds))
+ assert len(subDoc.axisMappings) == 1
+
+ subDocs = list(splitVariableFonts(ds))
+ assert len(subDocs) == 5
+ for i, (_, subDoc) in enumerate(subDocs):
+ # Only the first one should have a mapping, according to the document
+ if i == 0:
+ assert len(subDoc.axisMappings) == 1
+ else:
+ assert len(subDoc.axisMappings) == 0
diff --git a/Tests/designspaceLib/statNames_test.py b/Tests/designspaceLib/statNames_test.py
index 99d1c7fa..dd5fb105 100644
--- a/Tests/designspaceLib/statNames_test.py
+++ b/Tests/designspaceLib/statNames_test.py
@@ -61,6 +61,28 @@ def test_detect_ribbi_aktiv(datadir):
)
+def test_detect_ribbi_recursive(datadir):
+ doc = DesignSpaceDocument.fromfile(datadir / "test_v5.designspace")
+
+ assert getStatNames(doc, {"Weight": 700, "Width": 125, "Italic": 1}) == StatNames(
+ familyNames={
+ "en": "MasterFamilyName",
+ "fr": "Montserrat",
+ "ja": "モンセラート",
+ },
+ styleNames={
+ "en": "Wide Bold Italic",
+ },
+ postScriptFontName="MasterFamilyName-WideBoldItalic",
+ styleMapFamilyNames={
+ "en": "MasterFamilyName Wide",
+ "fr": "Montserrat Wide",
+ "ja": "モンセラート Wide",
+ },
+ styleMapStyleName="bold italic",
+ )
+
+
def test_getStatNames_on_ds4_doesnt_make_up_bad_names(datadir):
"""See this issue on GitHub: https://github.com/googlefonts/ufo2ft/issues/630
diff --git a/Tests/encodings/codecs_test.py b/Tests/encodings/codecs_test.py
index 9dac416a..64237563 100644
--- a/Tests/encodings/codecs_test.py
+++ b/Tests/encodings/codecs_test.py
@@ -1,24 +1,30 @@
import unittest
-import fontTools.encodings.codecs # Not to be confused with "import codecs"
+import fontTools.encodings.codecs # Not to be confused with "import codecs"
+
class ExtendedCodecsTest(unittest.TestCase):
+ def test_decode_mac_japanese(self):
+ self.assertEqual(
+ b"x\xfe\xfdy".decode("x_mac_japanese_ttx"),
+ chr(0x78) + chr(0x2122) + chr(0x00A9) + chr(0x79),
+ )
+
+ def test_encode_mac_japanese(self):
+ self.assertEqual(
+ b"x\xfe\xfdy",
+ (chr(0x78) + chr(0x2122) + chr(0x00A9) + chr(0x79)).encode(
+ "x_mac_japanese_ttx"
+ ),
+ )
- def test_decode_mac_japanese(self):
- self.assertEqual(b'x\xfe\xfdy'.decode("x_mac_japanese_ttx"),
- chr(0x78)+chr(0x2122)+chr(0x00A9)+chr(0x79))
+ def test_decode_mac_trad_chinese(self):
+ self.assertEqual(b"\x80".decode("x_mac_trad_chinese_ttx"), chr(0x5C))
- def test_encode_mac_japanese(self):
- self.assertEqual(b'x\xfe\xfdy',
- (chr(0x78)+chr(0x2122)+chr(0x00A9)+chr(0x79)).encode("x_mac_japanese_ttx"))
+ def test_decode_mac_romanian(self):
+ self.assertEqual(b"x\xfb".decode("mac_romanian"), chr(0x78) + chr(0x02DA))
- def test_decode_mac_trad_chinese(self):
- self.assertEqual(b'\x80'.decode("x_mac_trad_chinese_ttx"),
- chr(0x5C))
- def test_decode_mac_romanian(self):
- self.assertEqual(b'x\xfb'.decode("mac_romanian"),
- chr(0x78)+chr(0x02DA))
+if __name__ == "__main__":
+ import sys
-if __name__ == '__main__':
- import sys
- sys.exit(unittest.main())
+ sys.exit(unittest.main())
diff --git a/Tests/feaLib/ast_test.py b/Tests/feaLib/ast_test.py
index 4462f052..ebae3dab 100644
--- a/Tests/feaLib/ast_test.py
+++ b/Tests/feaLib/ast_test.py
@@ -22,4 +22,5 @@ class AstTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/feaLib/builder_test.py b/Tests/feaLib/builder_test.py
index 5c298e85..adcb058f 100644
--- a/Tests/feaLib/builder_test.py
+++ b/Tests/feaLib/builder_test.py
@@ -19,6 +19,7 @@ import sys
import tempfile
import logging
import unittest
+import warnings
def makeTTFont():
@@ -69,7 +70,7 @@ class BuilderTest(unittest.TestCase):
spec9a spec9b spec9c1 spec9c2 spec9c3 spec9d spec9e spec9f spec9g
spec10
bug453 bug457 bug463 bug501 bug502 bug504 bug505 bug506 bug509
- bug512 bug514 bug568 bug633 bug1307 bug1459 bug2276
+ bug512 bug514 bug568 bug633 bug1307 bug1459 bug2276 variable_bug2772
name size size2 multiple_feature_blocks omitted_GlyphClassDef
ZeroValue_SinglePos_horizontal ZeroValue_SinglePos_vertical
ZeroValue_PairPos_horizontal ZeroValue_PairPos_vertical
@@ -225,6 +226,21 @@ class BuilderTest(unittest.TestCase):
output.append(l)
return output
+ def make_mock_vf(self):
+ font = makeTTFont()
+ font["name"] = newTable("name")
+ addFvar(font, self.VARFONT_AXES, [])
+ del font["name"]
+ return font
+
+ @staticmethod
+ def get_region(var_region_axis):
+ return (
+ var_region_axis.StartCoord,
+ var_region_axis.PeakCoord,
+ var_region_axis.EndCoord,
+ )
+
def test_alternateSubst_multipleSubstitutionsForSameGlyph(self):
self.assertRaisesRegex(
FeatureLibError,
@@ -330,12 +346,10 @@ class BuilderTest(unittest.TestCase):
)
def test_feature_undefinedReference(self):
- self.assertRaisesRegex(
- FeatureLibError,
- "Feature none has not been defined",
- self.build,
- "feature aalt { feature none; } aalt;",
- )
+ with warnings.catch_warnings(record=True) as w:
+ self.build("feature aalt { feature none; } aalt;")
+ assert len(w) == 1
+ assert "Feature none has not been defined" in str(w[0].message)
def test_GlyphClassDef_conflictingClasses(self):
self.assertRaisesRegex(
@@ -954,15 +968,153 @@ class BuilderTest(unittest.TestCase):
FeatureLibError,
"Empty glyph class in mark class definition",
self.build,
- "markClass [] <anchor 150 -10> @TOPMARKS;"
+ "markClass [] <anchor 150 -10> @TOPMARKS;",
)
self.assertRaisesRegex(
FeatureLibError,
'Expected a glyph class with 1 elements after "by", but found a glyph class with 0 elements',
self.build,
- "feature test { sub a by []; test};"
+ "feature test { sub a by []; test};",
)
+ def test_unmarked_ignore_statement(self):
+ name = "bug2949"
+ logger = logging.getLogger("fontTools.feaLib.parser")
+ with CapturingLogHandler(logger, level="WARNING") as captor:
+ self.check_feature_file(name)
+ self.check_fea2fea_file(name)
+
+ for line, sub in {(3, "sub"), (8, "pos"), (13, "sub")}:
+ captor.assertRegex(
+ f'{name}.fea:{line}:12: Ambiguous "ignore {sub}", there should be least one marked glyph'
+ )
+
+ def test_condition_set_avar(self):
+ """Test that the `avar` table is consulted when normalizing user-space
+ values."""
+
+ features = """
+ languagesystem DFLT dflt;
+
+ lookup conditional_sub {
+ sub e by a;
+ } conditional_sub;
+
+ conditionset test {
+ wght 600 1000;
+ wdth 150 200;
+ } test;
+
+ variation rlig test {
+ lookup conditional_sub;
+ } rlig;
+ """
+
+ def make_mock_vf():
+ font = makeTTFont()
+ font["name"] = newTable("name")
+ addFvar(
+ font,
+ [("wght", 0, 0, 1000, "Weight"), ("wdth", 100, 100, 200, "Width")],
+ [],
+ )
+ del font["name"]
+ return font
+
+ # Without `avar`:
+ font = make_mock_vf()
+ addOpenTypeFeaturesFromString(font, features)
+ condition_table = (
+ font.tables["GSUB"]
+ .table.FeatureVariations.FeatureVariationRecord[0]
+ .ConditionSet.ConditionTable
+ )
+ # user-space wdth=150 and wght=600:
+ assert condition_table[0].FilterRangeMinValue == 0.5
+ assert condition_table[1].FilterRangeMinValue == 0.6
+
+ # With `avar`, shifting the wght axis' positive midpoint 0.5 a bit to
+ # the right, but leaving the wdth axis alone:
+ font = make_mock_vf()
+ font["avar"] = newTable("avar")
+ font["avar"].segments = {"wght": {-1.0: -1.0, 0.0: 0.0, 0.5: 0.625, 1.0: 1.0}}
+ addOpenTypeFeaturesFromString(font, features)
+ condition_table = (
+ font.tables["GSUB"]
+ .table.FeatureVariations.FeatureVariationRecord[0]
+ .ConditionSet.ConditionTable
+ )
+ # user-space wdth=150 as before and wght=600 shifted to the right:
+ assert condition_table[0].FilterRangeMinValue == 0.5
+ assert condition_table[1].FilterRangeMinValue == 0.7
+
+ def test_variable_scalar_avar(self):
+ """Test that the `avar` table is consulted when normalizing user-space
+ values."""
+
+ features = """
+ languagesystem DFLT dflt;
+
+ feature kern {
+ pos cursive one <anchor 0 (wght=200:12 wght=900:22 wdth=150,wght=900:42)> <anchor NULL>;
+ pos two <0 (wght=200:12 wght=900:22 wdth=150,wght=900:42) 0 0>;
+ } kern;
+ """
+
+ # Without `avar` (wght=200, wdth=100 is the default location):
+ font = self.make_mock_vf()
+ addOpenTypeFeaturesFromString(font, features)
+
+ var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
+ var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
+ var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
+ assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
+ assert self.get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0)
+ var_region_axis_wght = var_region_list.Region[1].VarRegionAxis[0]
+ var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
+ assert self.get_region(var_region_axis_wght) == (0.0, 0.875, 0.875)
+ assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5)
+
+ # With `avar`, shifting the wght axis' positive midpoint 0.5 a bit to
+ # the right, but leaving the wdth axis alone:
+ font = self.make_mock_vf()
+ font["avar"] = newTable("avar")
+ font["avar"].segments = {"wght": {-1.0: -1.0, 0.0: 0.0, 0.5: 0.625, 1.0: 1.0}}
+ addOpenTypeFeaturesFromString(font, features)
+
+ var_region_list = font.tables["GDEF"].table.VarStore.VarRegionList
+ var_region_axis_wght = var_region_list.Region[0].VarRegionAxis[0]
+ var_region_axis_wdth = var_region_list.Region[0].VarRegionAxis[1]
+ assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
+ assert self.get_region(var_region_axis_wdth) == (0.0, 0.0, 0.0)
+ var_region_axis_wght = var_region_list.Region[1].VarRegionAxis[0]
+ var_region_axis_wdth = var_region_list.Region[1].VarRegionAxis[1]
+ assert self.get_region(var_region_axis_wght) == (0.0, 0.90625, 0.90625)
+ assert self.get_region(var_region_axis_wdth) == (0.0, 0.5, 0.5)
+
+ def test_ligatureCaretByPos_variable_scalar(self):
+ """Test that the `avar` table is consulted when normalizing user-space
+ values."""
+
+ features = """
+ table GDEF {
+ LigatureCaretByPos f_i (wght=200:400 wght=900:1000) 380;
+ } GDEF;
+ """
+
+ font = self.make_mock_vf()
+ addOpenTypeFeaturesFromString(font, features)
+
+ table = font["GDEF"].table
+ lig_glyph = table.LigCaretList.LigGlyph[0]
+ assert lig_glyph.CaretValue[0].Format == 1
+ assert lig_glyph.CaretValue[0].Coordinate == 380
+ assert lig_glyph.CaretValue[1].Format == 3
+ assert lig_glyph.CaretValue[1].Coordinate == 400
+
+ var_region_list = table.VarStore.VarRegionList
+ var_region_axis = var_region_list.Region[0].VarRegionAxis[0]
+ assert self.get_region(var_region_axis) == (0.0, 0.875, 0.875)
def generate_feature_file_test(name):
diff --git a/Tests/feaLib/data/GPOS_1_zero.ttx b/Tests/feaLib/data/GPOS_1_zero.ttx
index b02db67c..e3162c5d 100644
--- a/Tests/feaLib/data/GPOS_1_zero.ttx
+++ b/Tests/feaLib/data/GPOS_1_zero.ttx
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
-
+
<GPOS>
<Version value="0x00010000"/>
<ScriptList>
diff --git a/Tests/feaLib/data/GSUB_2.fea b/Tests/feaLib/data/GSUB_2.fea
index d2a3cb10..21db452b 100644
--- a/Tests/feaLib/data/GSUB_2.fea
+++ b/Tests/feaLib/data/GSUB_2.fea
@@ -12,3 +12,24 @@ feature f2 {
sub f_i by f i;
sub f_f_i by f f i;
} f2;
+
+feature f3 {
+ sub [f_i f_l f_f_i f_f_l] by f [i l f_i f_l];
+} f3;
+
+feature f4 {
+ sub [f_i f_l f_f_i f_f_l] by [f f f_f f_f] [i l i l];
+} f4;
+
+@class = [f_i f_l];
+lookup l1 {
+ sub @class by f [i l];
+} l1;
+
+feature f5 {
+ sub @class' lookup l1 [i l];
+} f5;
+
+feature f6 {
+ sub [f_i f_i]' j by [f f] [i i];
+} f6;
diff --git a/Tests/feaLib/data/GSUB_2.ttx b/Tests/feaLib/data/GSUB_2.ttx
index b91c20fe..fb87a059 100644
--- a/Tests/feaLib/data/GSUB_2.ttx
+++ b/Tests/feaLib/data/GSUB_2.ttx
@@ -10,16 +10,20 @@
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
- <!-- FeatureCount=2 -->
+ <!-- FeatureCount=6 -->
<FeatureIndex index="0" value="0"/>
<FeatureIndex index="1" value="1"/>
+ <FeatureIndex index="2" value="2"/>
+ <FeatureIndex index="3" value="3"/>
+ <FeatureIndex index="4" value="4"/>
+ <FeatureIndex index="5" value="5"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
- <!-- FeatureCount=2 -->
+ <!-- FeatureCount=6 -->
<FeatureRecord index="0">
<FeatureTag value="f1 "/>
<Feature>
@@ -34,9 +38,37 @@
<LookupListIndex index="0" value="1"/>
</Feature>
</FeatureRecord>
+ <FeatureRecord index="2">
+ <FeatureTag value="f3 "/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="2"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="3">
+ <FeatureTag value="f4 "/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="3"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="4">
+ <FeatureTag value="f5 "/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="5"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="5">
+ <FeatureTag value="f6 "/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="6"/>
+ </Feature>
+ </FeatureRecord>
</FeatureList>
<LookupList>
- <!-- LookupCount=2 -->
+ <!-- LookupCount=8 -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
@@ -57,6 +89,89 @@
<Substitution in="f_i" out="f,i"/>
</MultipleSubst>
</Lookup>
+ <Lookup index="2">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MultipleSubst index="0">
+ <Substitution in="f_f_i" out="f,f_i"/>
+ <Substitution in="f_f_l" out="f,f_l"/>
+ <Substitution in="f_i" out="f,i"/>
+ <Substitution in="f_l" out="f,l"/>
+ </MultipleSubst>
+ </Lookup>
+ <Lookup index="3">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MultipleSubst index="0">
+ <Substitution in="f_f_i" out="f_f,i"/>
+ <Substitution in="f_f_l" out="f_f,l"/>
+ <Substitution in="f_i" out="f,i"/>
+ <Substitution in="f_l" out="f,l"/>
+ </MultipleSubst>
+ </Lookup>
+ <Lookup index="4">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MultipleSubst index="0">
+ <Substitution in="f_i" out="f,i"/>
+ <Substitution in="f_l" out="f,l"/>
+ </MultipleSubst>
+ </Lookup>
+ <Lookup index="5">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="f_l"/>
+ <Glyph value="f_i"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAheadCoverage index="0">
+ <Glyph value="i"/>
+ <Glyph value="l"/>
+ </LookAheadCoverage>
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="4"/>
+ </SubstLookupRecord>
+ </ChainContextSubst>
+ </Lookup>
+ <Lookup index="6">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="f_i"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAheadCoverage index="0">
+ <Glyph value="j"/>
+ </LookAheadCoverage>
+ <!-- SubstCount=1 -->
+ <SubstLookupRecord index="0">
+ <SequenceIndex value="0"/>
+ <LookupListIndex value="7"/>
+ </SubstLookupRecord>
+ </ChainContextSubst>
+ </Lookup>
+ <Lookup index="7">
+ <LookupType value="2"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MultipleSubst index="0">
+ <Substitution in="f_i" out="f,i"/>
+ </MultipleSubst>
+ </Lookup>
</LookupList>
</GSUB>
diff --git a/Tests/feaLib/data/GSUB_5_formats.fea b/Tests/feaLib/data/GSUB_5_formats.fea
index 3acb7edf..e9b3ba8b 100644
--- a/Tests/feaLib/data/GSUB_5_formats.fea
+++ b/Tests/feaLib/data/GSUB_5_formats.fea
@@ -1,16 +1,16 @@
lookup GSUB5f1 {
- ignore sub three four;
- ignore sub four five;
+ 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];
+ 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;
+ ignore sub e';
} GSUB5f3;
feature test {
diff --git a/Tests/feaLib/data/PairPosSubtable.ttx b/Tests/feaLib/data/PairPosSubtable.ttx
index 2d78f64f..d671537e 100644
--- a/Tests/feaLib/data/PairPosSubtable.ttx
+++ b/Tests/feaLib/data/PairPosSubtable.ttx
@@ -93,14 +93,14 @@
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
<ClassDef1>
- <ClassDef glyph="b" class="1"/>
- <ClassDef glyph="o" class="1"/>
- </ClassDef1>
- <ClassDef2>
- <ClassDef glyph="c" class="2"/>
- <ClassDef glyph="d" class="2"/>
<ClassDef glyph="v" class="1"/>
<ClassDef glyph="w" class="1"/>
+ </ClassDef1>
+ <ClassDef2>
+ <ClassDef glyph="c" class="1"/>
+ <ClassDef glyph="d" class="1"/>
+ <ClassDef glyph="v" class="2"/>
+ <ClassDef glyph="w" class="2"/>
</ClassDef2>
<!-- Class1Count=2 -->
<!-- Class2Count=3 -->
@@ -112,7 +112,7 @@
<Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="2">
- <Value1 XAdvance="-20"/>
+ <Value1 XAdvance="-10"/>
</Class2Record>
</Class1Record>
<Class1Record index="1">
@@ -120,7 +120,7 @@
<Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
- <Value1 XAdvance="-10"/>
+ <Value1 XAdvance="-20"/>
</Class2Record>
<Class2Record index="2">
<Value1 XAdvance="0"/>
diff --git a/Tests/feaLib/data/STAT_test.ttx b/Tests/feaLib/data/STAT_test.ttx
index d1b2b697..bab9b8ea 100644
--- a/Tests/feaLib/data/STAT_test.ttx
+++ b/Tests/feaLib/data/STAT_test.ttx
@@ -8,9 +8,6 @@
<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>
@@ -68,6 +65,9 @@
<namerecord nameID="275" platformID="3" platEncID="1" langID="0x409">
Caption
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x411">
+ ローマン
+ </namerecord>
</name>
<STAT>
diff --git a/Tests/feaLib/data/bug2949.fea b/Tests/feaLib/data/bug2949.fea
new file mode 100644
index 00000000..cd0b7a86
--- /dev/null
+++ b/Tests/feaLib/data/bug2949.fea
@@ -0,0 +1,20 @@
+lookup lookup1 {
+#test-fea2fea: ignore sub three' four;
+ ignore sub three four;
+} lookup1;
+
+lookup lookup2 {
+#test-fea2fea: ignore pos three' four;
+ ignore pos three four;
+} lookup2;
+
+lookup lookup3 {
+#test-fea2fea: ignore sub one' two, three' four;
+ ignore sub one two, three four;
+} lookup3;
+
+feature test {
+ lookup lookup1;
+ lookup lookup2;
+ lookup lookup3;
+} test;
diff --git a/Tests/feaLib/data/bug2949.ttx b/Tests/feaLib/data/bug2949.ttx
new file mode 100644
index 00000000..8ad9ccb4
--- /dev/null
+++ b/Tests/feaLib/data/bug2949.ttx
@@ -0,0 +1,133 @@
+<?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=2 -->
+ <LookupListIndex index="0" value="0"/>
+ <LookupListIndex index="1" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=2 -->
+ <Lookup index="0">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="three"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAheadCoverage index="0">
+ <Glyph value="four"/>
+ </LookAheadCoverage>
+ <!-- SubstCount=0 -->
+ </ChainContextSubst>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="6"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextSubst index="0" Format="1">
+ <Coverage>
+ <Glyph value="one"/>
+ <Glyph value="three"/>
+ </Coverage>
+ <!-- ChainSubRuleSetCount=2 -->
+ <ChainSubRuleSet index="0">
+ <!-- ChainSubRuleCount=1 -->
+ <ChainSubRule index="0">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="two"/>
+ <!-- SubstCount=0 -->
+ </ChainSubRule>
+ </ChainSubRuleSet>
+ <ChainSubRuleSet index="1">
+ <!-- ChainSubRuleCount=1 -->
+ <ChainSubRule index="0">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAhead index="0" value="four"/>
+ <!-- SubstCount=0 -->
+ </ChainSubRule>
+ </ChainSubRuleSet>
+ </ChainContextSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <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="0"/>
+ </Feature>
+ </FeatureRecord>
+ </FeatureList>
+ <LookupList>
+ <!-- LookupCount=1 -->
+ <Lookup index="0">
+ <LookupType value="8"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ChainContextPos index="0" Format="3">
+ <!-- BacktrackGlyphCount=0 -->
+ <!-- InputGlyphCount=1 -->
+ <InputCoverage index="0">
+ <Glyph value="three"/>
+ </InputCoverage>
+ <!-- LookAheadGlyphCount=1 -->
+ <LookAheadCoverage index="0">
+ <Glyph value="four"/>
+ </LookAheadCoverage>
+ <!-- PosCount=0 -->
+ </ChainContextPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/feaLib/data/bug509.fea b/Tests/feaLib/data/bug509.fea
index b7af056f..488e769a 100644
--- a/Tests/feaLib/data/bug509.fea
+++ b/Tests/feaLib/data/bug509.fea
@@ -1,5 +1,5 @@
@LETTER_A = [A A.sc A.alt1];
feature test {
- ignore sub A;
+ ignore sub A';
sub @LETTER_A' by a;
} test;
diff --git a/Tests/feaLib/data/bug512.ttx b/Tests/feaLib/data/bug512.ttx
index 693ebeb7..4ad3af2f 100644
--- a/Tests/feaLib/data/bug512.ttx
+++ b/Tests/feaLib/data/bug512.ttx
@@ -73,7 +73,7 @@
<!-- SubstCount=1 -->
<SubstLookupRecord index="0">
<SequenceIndex value="0"/>
- <LookupListIndex value="2"/>
+ <LookupListIndex value="1"/>
</SubstLookupRecord>
</SubRule>
</SubRuleSet>
@@ -94,7 +94,6 @@
<!-- SubTableCount=1 -->
<SingleSubst index="0">
<Substitution in="G" out="g"/>
- <Substitution in="H" out="H.swash"/>
</SingleSubst>
</Lookup>
</LookupList>
diff --git a/Tests/feaLib/data/bug633.ttx b/Tests/feaLib/data/bug633.ttx
index 075c1777..8be745cd 100644
--- a/Tests/feaLib/data/bug633.ttx
+++ b/Tests/feaLib/data/bug633.ttx
@@ -43,10 +43,10 @@
<ClassDef1>
</ClassDef1>
<ClassDef2>
- <ClassDef glyph="C" class="2"/>
- <ClassDef glyph="O" class="2"/>
- <ClassDef glyph="V" class="1"/>
- <ClassDef glyph="W" class="1"/>
+ <ClassDef glyph="C" class="1"/>
+ <ClassDef glyph="O" class="1"/>
+ <ClassDef glyph="V" class="2"/>
+ <ClassDef glyph="W" class="2"/>
</ClassDef2>
<!-- Class1Count=1 -->
<!-- Class2Count=3 -->
@@ -55,10 +55,10 @@
<Value1 XAdvance="0"/>
</Class2Record>
<Class2Record index="1">
- <Value1 XAdvance="0"/>
+ <Value1 XAdvance="-20"/>
</Class2Record>
<Class2Record index="2">
- <Value1 XAdvance="-20"/>
+ <Value1 XAdvance="0"/>
</Class2Record>
</Class1Record>
</PairPos>
diff --git a/Tests/feaLib/data/name.ttx b/Tests/feaLib/data/name.ttx
index 5014b251..51ecf324 100644
--- a/Tests/feaLib/data/name.ttx
+++ b/Tests/feaLib/data/name.ttx
@@ -2,6 +2,15 @@
<ttFont>
<name>
+ <namerecord nameID="8" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Test8
+ </namerecord>
+ <namerecord nameID="10" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Test10
+ </namerecord>
+ <namerecord nameID="11" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ Test11
+ </namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
Test1
</namerecord>
@@ -23,18 +32,9 @@
<namerecord nameID="7" platformID="3" platEncID="1" langID="0x409">
Test7
</namerecord>
- <namerecord nameID="8" platformID="1" platEncID="0" langID="0x0" unicode="True">
- Test8
- </namerecord>
<namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
Test9
</namerecord>
- <namerecord nameID="10" platformID="1" platEncID="0" langID="0x0" unicode="True">
- Test10
- </namerecord>
- <namerecord nameID="11" platformID="1" platEncID="0" langID="0x0" unicode="True">
- Test11
- </namerecord>
</name>
</ttFont>
diff --git a/Tests/feaLib/data/spec5f_ii_3.ttx b/Tests/feaLib/data/spec5f_ii_3.ttx
index a94efcea..c03a81fb 100644
--- a/Tests/feaLib/data/spec5f_ii_3.ttx
+++ b/Tests/feaLib/data/spec5f_ii_3.ttx
@@ -66,9 +66,9 @@
<ClassDef glyph="z" class="1"/>
</BacktrackClassDef>
<InputClassDef>
- <ClassDef glyph="a" class="3"/>
+ <ClassDef glyph="a" class="1"/>
<ClassDef glyph="d" class="2"/>
- <ClassDef glyph="n" class="1"/>
+ <ClassDef glyph="n" class="3"/>
</InputClassDef>
<LookAheadClassDef>
<ClassDef glyph="a" class="1"/>
@@ -103,18 +103,12 @@
<!-- 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="0" value="3"/>
<Input index="1" value="2"/>
<!-- LookAheadGlyphCount=0 -->
<!-- SubstCount=0 -->
@@ -122,7 +116,7 @@
<ChainSubClassRule index="1">
<!-- BacktrackGlyphCount=0 -->
<!-- InputGlyphCount=3 -->
- <Input index="0" value="1"/>
+ <Input index="0" value="3"/>
<Input index="1" value="2"/>
<!-- LookAheadGlyphCount=1 -->
<LookAhead index="0" value="1"/>
@@ -131,7 +125,7 @@
<ChainSubClassRule index="2">
<!-- BacktrackGlyphCount=0 -->
<!-- InputGlyphCount=3 -->
- <Input index="0" value="1"/>
+ <Input index="0" value="3"/>
<Input index="1" value="2"/>
<!-- LookAheadGlyphCount=0 -->
<!-- SubstCount=1 -->
@@ -141,6 +135,12 @@
</SubstLookupRecord>
</ChainSubClassRule>
</ChainSubClassSet>
+ <ChainSubClassSet index="2">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
+ <ChainSubClassSet index="3">
+ <!-- ChainSubClassRuleCount=0 -->
+ </ChainSubClassSet>
</ChainContextSubst>
</Lookup>
<Lookup index="1">
diff --git a/Tests/feaLib/data/spec8b.ttx b/Tests/feaLib/data/spec8b.ttx
index 6e66c16b..5c8cba27 100644
--- a/Tests/feaLib/data/spec8b.ttx
+++ b/Tests/feaLib/data/spec8b.ttx
@@ -2,15 +2,15 @@
<ttFont>
<name>
- <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
- Win MinionPro Size Name
- </namerecord>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
Mac MinionPro Size Name
</namerecord>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x5" unicode="True">
Mac MinionPro Size Name
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Win MinionPro Size Name
+ </namerecord>
</name>
<GPOS>
@@ -37,7 +37,7 @@
<FeatureParamsSize>
<DesignSize value="10.0"/>
<SubfamilyID value="3"/>
- <SubfamilyNameID value="256"/> <!-- Win MinionPro Size Name -->
+ <SubfamilyNameID value="256"/> <!-- Mac MinionPro Size Name -->
<RangeStart value="8.0"/>
<RangeEnd value="13.9"/>
</FeatureParamsSize>
diff --git a/Tests/feaLib/data/spec8c.ttx b/Tests/feaLib/data/spec8c.ttx
index a5b55176..f17898db 100644
--- a/Tests/feaLib/data/spec8c.ttx
+++ b/Tests/feaLib/data/spec8c.ttx
@@ -2,18 +2,18 @@
<ttFont>
<name>
- <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
- Feature description for MS Platform, script Unicode, language English
- </namerecord>
- <namerecord nameID="256" platformID="3" platEncID="1" langID="0x411">
- Feature description for MS Platform, script Unicode, language Japanese
- </namerecord>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
Feature description for Apple Platform, script Roman, language unspecified
</namerecord>
<namerecord nameID="256" platformID="1" platEncID="1" langID="0xc" unicode="True">
Feature description for Apple Platform, script Japanese, language Japanese
</namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Feature description for MS Platform, script Unicode, language English
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x411">
+ Feature description for MS Platform, script Unicode, language Japanese
+ </namerecord>
</name>
<GSUB>
@@ -39,7 +39,7 @@
<Feature>
<FeatureParamsStylisticSet>
<Version value="0"/>
- <UINameID value="256"/> <!-- Feature description for MS Platform, script Unicode, language English -->
+ <UINameID value="256"/> <!-- Feature description for Apple Platform, script Roman, language unspecified -->
</FeatureParamsStylisticSet>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="0"/>
diff --git a/Tests/feaLib/data/spec8d.ttx b/Tests/feaLib/data/spec8d.ttx
index 9848a691..5ff20ef7 100644
--- a/Tests/feaLib/data/spec8d.ttx
+++ b/Tests/feaLib/data/spec8d.ttx
@@ -2,34 +2,34 @@
<ttFont>
<name>
- <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
- uilabel simple a
- </namerecord>
<namerecord nameID="256" platformID="1" platEncID="0" langID="0x0" unicode="True">
uilabel simple a
</namerecord>
- <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
- tool tip simple a
- </namerecord>
<namerecord nameID="257" platformID="1" platEncID="0" langID="0x0" unicode="True">
tool tip simple a
</namerecord>
- <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
- sample text simple a
- </namerecord>
<namerecord nameID="258" platformID="1" platEncID="0" langID="0x0" unicode="True">
sample text simple a
</namerecord>
- <namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
- param1 text simple a
- </namerecord>
<namerecord nameID="259" platformID="1" platEncID="0" langID="0x0" unicode="True">
param1 text simple a
</namerecord>
- <namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
+ <namerecord nameID="260" platformID="1" platEncID="0" langID="0x0" unicode="True">
param2 text simple a
</namerecord>
- <namerecord nameID="260" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ uilabel simple a
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ tool tip simple a
+ </namerecord>
+ <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
+ sample text simple a
+ </namerecord>
+ <namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
+ param1 text simple a
+ </namerecord>
+ <namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
param2 text simple a
</namerecord>
</name>
diff --git a/Tests/feaLib/data/spec9e.ttx b/Tests/feaLib/data/spec9e.ttx
index 5119a5fc..4c63b47a 100644
--- a/Tests/feaLib/data/spec9e.ttx
+++ b/Tests/feaLib/data/spec9e.ttx
@@ -2,10 +2,10 @@
<ttFont>
<name>
- <namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
+ <namerecord nameID="9" platformID="1" platEncID="0" langID="0x0" unicode="True">
Joachim Müller-Lancé
</namerecord>
- <namerecord nameID="9" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ <namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
Joachim Müller-Lancé
</namerecord>
</name>
diff --git a/Tests/feaLib/data/variable_bug2772.fea b/Tests/feaLib/data/variable_bug2772.fea
new file mode 100644
index 00000000..08326902
--- /dev/null
+++ b/Tests/feaLib/data/variable_bug2772.fea
@@ -0,0 +1,4 @@
+feature kern {
+ pos [p] g -5;
+ pos [p] y (wght=1000:-100 wght=200:0);
+} kern;
diff --git a/Tests/feaLib/data/variable_bug2772.ttx b/Tests/feaLib/data/variable_bug2772.ttx
new file mode 100644
index 00000000..12fda609
--- /dev/null
+++ b/Tests/feaLib/data/variable_bug2772.ttx
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.37">
+
+ <GDEF>
+ <Version value="0x00010003"/>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=2 -->
+ <!-- RegionCount=1 -->
+ <Region index="0">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ <VarRegionAxis index="1">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="0.0"/>
+ <EndCoord value="0.0"/>
+ </VarRegionAxis>
+ </Region>
+ </VarRegionList>
+ <!-- VarDataCount=1 -->
+ <VarData index="0">
+ <!-- ItemCount=1 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=1 -->
+ <VarRegionIndex index="0" value="0"/>
+ <Item index="0" value="[-100]"/>
+ </VarData>
+ </VarStore>
+ </GDEF>
+
+ <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="kern"/>
+ <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="p"/>
+ </Coverage>
+ <ValueFormat1 value="68"/>
+ <ValueFormat2 value="0"/>
+ <ClassDef1>
+ </ClassDef1>
+ <ClassDef2>
+ <ClassDef glyph="g" class="1"/>
+ <ClassDef glyph="y" class="2"/>
+ </ClassDef2>
+ <!-- Class1Count=1 -->
+ <!-- Class2Count=3 -->
+ <Class1Record index="0">
+ <Class2Record index="0">
+ <Value1 XAdvance="0"/>
+ </Class2Record>
+ <Class2Record index="1">
+ <Value1 XAdvance="-5"/>
+ </Class2Record>
+ <Class2Record index="2">
+ <Value1 XAdvance="0">
+ <XAdvDevice>
+ <StartSize value="0"/>
+ <EndSize value="0"/>
+ <DeltaFormat value="32768"/>
+ </XAdvDevice>
+ </Value1>
+ </Class2Record>
+ </Class1Record>
+ </PairPos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+</ttFont>
diff --git a/Tests/feaLib/data/variable_scalar_valuerecord.fea b/Tests/feaLib/data/variable_scalar_valuerecord.fea
index bf9a26b7..0b402654 100644
--- a/Tests/feaLib/data/variable_scalar_valuerecord.fea
+++ b/Tests/feaLib/data/variable_scalar_valuerecord.fea
@@ -2,4 +2,5 @@ languagesystem DFLT dflt;
feature kern {
pos one 1;
pos two <0 (wght=200:12 wght=900:22 wdth=150,wght=900:42) 0 0>;
+ pos three <0 (wght=200:12 wght=900:12 wdth=150,wght=900:12) 0 0>;
} kern;
diff --git a/Tests/feaLib/data/variable_scalar_valuerecord.ttx b/Tests/feaLib/data/variable_scalar_valuerecord.ttx
index 338b7221..e3251f69 100644
--- a/Tests/feaLib/data/variable_scalar_valuerecord.ttx
+++ b/Tests/feaLib/data/variable_scalar_valuerecord.ttx
@@ -76,7 +76,7 @@
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
- <!-- SubTableCount=2 -->
+ <!-- SubTableCount=3 -->
<SinglePos index="0" Format="1">
<Coverage>
<Glyph value="one"/>
@@ -97,6 +97,13 @@
</YPlaDevice>
</Value>
</SinglePos>
+ <SinglePos index="2" Format="1">
+ <Coverage>
+ <Glyph value="three"/>
+ </Coverage>
+ <ValueFormat value="2"/>
+ <Value YPlacement="12"/>
+ </SinglePos>
</Lookup>
</LookupList>
</GPOS>
diff --git a/Tests/feaLib/error_test.py b/Tests/feaLib/error_test.py
index 2ebb3e4c..2972b5f3 100644
--- a/Tests/feaLib/error_test.py
+++ b/Tests/feaLib/error_test.py
@@ -15,4 +15,5 @@ class FeatureLibErrorTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/feaLib/lexer_test.py b/Tests/feaLib/lexer_test.py
index 3df67f70..317a9a8c 100644
--- a/Tests/feaLib/lexer_test.py
+++ b/Tests/feaLib/lexer_test.py
@@ -39,68 +39,77 @@ class LexerTest(unittest.TestCase):
def test_glyphclass(self):
self.assertEqual(lex("@Vowel.sc"), [(Lexer.GLYPHCLASS, "Vowel.sc")])
self.assertEqual(lex("@Vowel-sc"), [(Lexer.GLYPHCLASS, "Vowel-sc")])
- self.assertRaisesRegex(FeatureLibError,
- "Expected glyph class", lex, "@(a)")
- self.assertRaisesRegex(FeatureLibError,
- "Expected glyph class", lex, "@ A")
- self.assertRaisesRegex(FeatureLibError,
- "not be longer than 63 characters",
- lex, "@" + ("A" * 64))
- self.assertRaisesRegex(FeatureLibError,
- "Glyph class names must consist of",
- lex, "@Ab:c")
+ self.assertRaisesRegex(FeatureLibError, "Expected glyph class", lex, "@(a)")
+ self.assertRaisesRegex(FeatureLibError, "Expected glyph class", lex, "@ A")
+ self.assertRaisesRegex(
+ FeatureLibError, "not be longer than 63 characters", lex, "@" + ("A" * 64)
+ )
+ self.assertRaisesRegex(
+ FeatureLibError, "Glyph class names must consist of", lex, "@Ab:c"
+ )
def test_include(self):
- self.assertEqual(lex("include (~/foo/bar baz.fea);"), [
- (Lexer.NAME, "include"),
- (Lexer.FILENAME, "~/foo/bar baz.fea"),
- (Lexer.SYMBOL, ";")
- ])
- self.assertEqual(lex("include # Comment\n (foo) \n;"), [
- (Lexer.NAME, "include"),
- (Lexer.COMMENT, "# Comment"),
- (Lexer.FILENAME, "foo"),
- (Lexer.SYMBOL, ";")
- ])
+ self.assertEqual(
+ lex("include (~/foo/bar baz.fea);"),
+ [
+ (Lexer.NAME, "include"),
+ (Lexer.FILENAME, "~/foo/bar baz.fea"),
+ (Lexer.SYMBOL, ";"),
+ ],
+ )
+ self.assertEqual(
+ lex("include # Comment\n (foo) \n;"),
+ [
+ (Lexer.NAME, "include"),
+ (Lexer.COMMENT, "# Comment"),
+ (Lexer.FILENAME, "foo"),
+ (Lexer.SYMBOL, ";"),
+ ],
+ )
self.assertRaises(FeatureLibError, lex, "include blah")
self.assertRaises(FeatureLibError, lex, "include (blah")
def test_number(self):
- self.assertEqual(lex("123 -456"),
- [(Lexer.NUMBER, 123), (Lexer.NUMBER, -456)])
+ self.assertEqual(lex("123 -456"), [(Lexer.NUMBER, 123), (Lexer.NUMBER, -456)])
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"),
- [(Lexer.FLOAT, 1.23), (Lexer.FLOAT, -4.5)])
+ self.assertEqual(lex("1.23 -4.5"), [(Lexer.FLOAT, 1.23), (Lexer.FLOAT, -4.5)])
def test_symbol(self):
self.assertEqual(lex("a'"), [(Lexer.NAME, "a"), (Lexer.SYMBOL, "'")])
- self.assertEqual(lex("-A-B"),
- [(Lexer.SYMBOL, "-"), (Lexer.NAME, "A-B")])
+ self.assertEqual(lex("-A-B"), [(Lexer.SYMBOL, "-"), (Lexer.NAME, "A-B")])
self.assertEqual(
lex("foo - -2"),
- [(Lexer.NAME, "foo"), (Lexer.SYMBOL, "-"), (Lexer.NUMBER, -2)])
+ [(Lexer.NAME, "foo"), (Lexer.SYMBOL, "-"), (Lexer.NUMBER, -2)],
+ )
def test_comment(self):
- self.assertEqual(lex("# Comment\n#"),
- [(Lexer.COMMENT, "# Comment"), (Lexer.COMMENT, "#")])
+ self.assertEqual(
+ lex("# Comment\n#"), [(Lexer.COMMENT, "# Comment"), (Lexer.COMMENT, "#")]
+ )
def test_string(self):
- self.assertEqual(lex('"foo" "bar"'),
- [(Lexer.STRING, "foo"), (Lexer.STRING, "bar")])
- self.assertEqual(lex('"foo \nbar\r baz \r\nqux\n\n "'),
- [(Lexer.STRING, "foo bar baz qux ")])
+ self.assertEqual(
+ lex('"foo" "bar"'), [(Lexer.STRING, "foo"), (Lexer.STRING, "bar")]
+ )
+ self.assertEqual(
+ lex('"foo \nbar\r baz \r\nqux\n\n "'), [(Lexer.STRING, "foo bar baz qux ")]
+ )
# The lexer should preserve escape sequences because they have
# different interpretations depending on context. For better
# or for worse, that is how the OpenType Feature File Syntax
# has been specified; see section 9.e (name table) for examples.
- self.assertEqual(lex(r'"M\00fcller-Lanc\00e9"'), # 'nameid 9'
- [(Lexer.STRING, r"M\00fcller-Lanc\00e9")])
- self.assertEqual(lex(r'"M\9fller-Lanc\8e"'), # 'nameid 9 1'
- [(Lexer.STRING, r"M\9fller-Lanc\8e")])
+ self.assertEqual(
+ lex(r'"M\00fcller-Lanc\00e9"'), # 'nameid 9'
+ [(Lexer.STRING, r"M\00fcller-Lanc\00e9")],
+ )
+ self.assertEqual(
+ lex(r'"M\9fller-Lanc\8e"'), # 'nameid 9 1'
+ [(Lexer.STRING, r"M\9fller-Lanc\8e")],
+ )
self.assertRaises(FeatureLibError, lex, '"foo\n bar')
def test_bad_character(self):
@@ -109,6 +118,7 @@ class LexerTest(unittest.TestCase):
def test_newline(self):
def lines(s):
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
@@ -117,10 +127,17 @@ class LexerTest(unittest.TestCase):
def test_location(self):
def locs(s):
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"
- ])
+
+ 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",
+ ],
+ )
def test_scan_over_(self):
lexer = Lexer("abbacabba12", "test.fea")
@@ -151,22 +168,27 @@ class IncludingLexerTest(unittest.TestCase):
def test_include(self):
lexer = IncludingLexer(self.getpath("include/include4.fea"))
- 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",
- "I3a include3.fea:1",
- "I2a include2.fea:1",
- "I1a include1.fea:1",
- "I0 include0.fea:1",
- "I1b include1.fea:3",
- "; include2.fea:2",
- "I2b include2.fea:3",
- "; include3.fea:2",
- "I3b include3.fea:3",
- "; include4.fea:2",
- "I4b include4.fea:3"
- ])
+ 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",
+ "I3a include3.fea:1",
+ "I2a include2.fea:1",
+ "I1a include1.fea:1",
+ "I0 include0.fea:1",
+ "I1b include1.fea:3",
+ "; include2.fea:2",
+ "I2b include2.fea:3",
+ "; include3.fea:2",
+ "I3b include3.fea:3",
+ "; include4.fea:2",
+ "I4b include4.fea:3",
+ ],
+ )
def test_include_limit(self):
lexer = IncludingLexer(self.getpath("include/include6.fea"))
@@ -178,11 +200,13 @@ class IncludingLexerTest(unittest.TestCase):
def test_include_missing_file(self):
lexer = IncludingLexer(self.getpath("include/includemissingfile.fea"))
- self.assertRaisesRegex(IncludedFeaNotFound,
- "includemissingfile.fea:1:8: The following feature file "
- "should be included but cannot be found: "
- "missingfile.fea",
- lambda: list(lexer))
+ self.assertRaisesRegex(
+ IncludedFeaNotFound,
+ "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(StringIO("# foobar"))
@@ -192,11 +216,16 @@ class IncludingLexerTest(unittest.TestCase):
def test_include_absolute_path(self):
with tempfile.NamedTemporaryFile(delete=False) as included:
- included.write(tobytes("""
+ included.write(
+ tobytes(
+ """
feature kern {
pos A B -40;
} kern;
- """, encoding="utf-8"))
+ """,
+ encoding="utf-8",
+ )
+ )
including = StringIO("include(%s);" % included.name)
try:
lexer = IncludingLexer(including)
@@ -211,13 +240,16 @@ class IncludingLexerTest(unittest.TestCase):
tmpdir = tempfile.mkdtemp()
try:
# create new feature file in a temporary directory
- with open(os.path.join(tmpdir, "included.fea"), "w",
- encoding="utf-8") as included:
- included.write("""
+ with open(
+ os.path.join(tmpdir, "included.fea"), "w", encoding="utf-8"
+ ) as included:
+ included.write(
+ """
feature kern {
pos A B -40;
} kern;
- """)
+ """
+ )
# change current folder to the temporary dir
os.chdir(tmpdir)
# instantiate a new lexer that includes the above file
@@ -237,4 +269,5 @@ class IncludingLexerTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/feaLib/parser_test.py b/Tests/feaLib/parser_test.py
index b281e8ac..c140629a 100644
--- a/Tests/feaLib/parser_test.py
+++ b/Tests/feaLib/parser_test.py
@@ -44,7 +44,7 @@ GLYPHNAMES = (
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
+ a_f_f_i o_f_f_i f_i f_l 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
@@ -316,7 +316,9 @@ class ParserTest(unittest.TestCase):
def test_strict_glyph_name_check(self):
self.parse("@bad = [a b ccc];", glyphNames=("a", "b", "ccc"))
- with self.assertRaisesRegex(FeatureLibError, "(?s)missing from the glyph set:.*ccc"):
+ with self.assertRaisesRegex(
+ FeatureLibError, "(?s)missing from the glyph set:.*ccc"
+ ):
self.parse("@bad = [a b ccc];", glyphNames=("a", "b"))
def test_glyphclass(self):
@@ -705,6 +707,17 @@ class ParserTest(unittest.TestCase):
self.assertEqual(glyphstr([s.glyphs]), "f_i")
self.assertEqual(s.carets, [400, 380])
+ def test_ligatureCaretByPos_variable_scalar(self):
+ doc = self.parse(
+ "table GDEF {LigatureCaretByPos f_i (wght=200:400 wght=900:1000) 380;} GDEF;"
+ )
+ s = doc.statements[0].statements[0]
+ self.assertIsInstance(s, ast.LigatureCaretByPosStatement)
+ self.assertEqual(glyphstr([s.glyphs]), "f_i")
+ self.assertEqual(len(s.carets), 2)
+ self.assertEqual(str(s.carets[0]), "(wght=200:400 wght=900:1000)")
+ self.assertEqual(s.carets[1], 380)
+
def test_lookup_block(self):
[lookup] = self.parse("lookup Ligatures {} Ligatures;").statements
self.assertEqual(lookup.name, "Ligatures")
@@ -1608,24 +1621,54 @@ class ParserTest(unittest.TestCase):
doc = self.parse("lookup Look {substitute f_f_i by f f i;} Look;")
sub = doc.statements[0].statements[0]
self.assertIsInstance(sub, ast.MultipleSubstStatement)
- self.assertEqual(sub.glyph, "f_f_i")
- self.assertEqual(sub.replacement, ("f", "f", "i"))
+ self.assertEqual(glyphstr([sub.glyph]), "f_f_i")
+ self.assertEqual(glyphstr(sub.replacement), "f f i")
def test_substitute_multiple_chained(self): # chain to GSUB LookupType 2
doc = self.parse("lookup L {sub [A-C] f_f_i' [X-Z] by f f i;} L;")
sub = doc.statements[0].statements[0]
self.assertIsInstance(sub, ast.MultipleSubstStatement)
- self.assertEqual(sub.glyph, "f_f_i")
- self.assertEqual(sub.replacement, ("f", "f", "i"))
+ self.assertEqual(glyphstr([sub.glyph]), "f_f_i")
+ self.assertEqual(glyphstr(sub.replacement), "f f i")
def test_substitute_multiple_force_chained(self):
doc = self.parse("lookup L {sub f_f_i' by f f i;} L;")
sub = doc.statements[0].statements[0]
self.assertIsInstance(sub, ast.MultipleSubstStatement)
- self.assertEqual(sub.glyph, "f_f_i")
- self.assertEqual(sub.replacement, ("f", "f", "i"))
+ self.assertEqual(glyphstr([sub.glyph]), "f_f_i")
+ self.assertEqual(glyphstr(sub.replacement), "f f i")
self.assertEqual(sub.asFea(), "sub f_f_i' by f f i;")
+ def test_substitute_multiple_classes(self):
+ doc = self.parse("lookup Look {substitute [f_i f_l] by [f f] [i l];} Look;")
+ sub = doc.statements[0].statements[0]
+ self.assertIsInstance(sub, ast.MultipleSubstStatement)
+ self.assertEqual(glyphstr([sub.glyph]), "[f_i f_l]")
+ self.assertEqual(glyphstr(sub.replacement), "[f f] [i l]")
+
+ def test_substitute_multiple_classes_mixed(self):
+ doc = self.parse("lookup Look {substitute [f_i f_l] by f [i l];} Look;")
+ sub = doc.statements[0].statements[0]
+ self.assertIsInstance(sub, ast.MultipleSubstStatement)
+ self.assertEqual(glyphstr([sub.glyph]), "[f_i f_l]")
+ self.assertEqual(glyphstr(sub.replacement), "f [i l]")
+
+ def test_substitute_multiple_classes_mixed_singleton(self):
+ doc = self.parse("lookup Look {substitute [f_i f_l] by [f] [i l];} Look;")
+ sub = doc.statements[0].statements[0]
+ self.assertIsInstance(sub, ast.MultipleSubstStatement)
+ self.assertEqual(glyphstr([sub.glyph]), "[f_i f_l]")
+ self.assertEqual(glyphstr(sub.replacement), "f [i l]")
+
+ def test_substitute_multiple_classes_mismatch(self):
+ self.assertRaisesRegex(
+ FeatureLibError,
+ 'Expected a glyph class with 1 or 3 elements after "by", '
+ "but found a glyph class with 2 elements",
+ self.parse,
+ "lookup Look {substitute [f_i f_l f_f_i] by [f f_f] [i l i];} Look;",
+ )
+
def test_substitute_multiple_by_mutliple(self):
self.assertRaisesRegex(
FeatureLibError,
@@ -2081,6 +2124,15 @@ class ParserTest(unittest.TestCase):
doc = Parser(fea_path, includeDir=include_dir).parse()
assert len(doc.statements) == 1 and doc.statements[0].text == "# Nothing"
+ def test_unmarked_ignore_statement(self):
+ with CapturingLogHandler("fontTools.feaLib.parser", level="WARNING") as caplog:
+ doc = self.parse("lookup foo { ignore sub A; } foo;")
+ self.assertEqual(doc.statements[0].statements[0].asFea(), "ignore sub A';")
+ self.assertEqual(len(caplog.records), 1)
+ caplog.assertRegex(
+ 'Ambiguous "ignore sub", there should be least one marked glyph'
+ )
+
def parse(self, text, glyphNames=GLYPHNAMES, followIncludes=True):
featurefile = StringIO(text)
p = Parser(featurefile, glyphNames, followIncludes=followIncludes)
diff --git a/Tests/fontBuilder/data/test_var.otf.ttx b/Tests/fontBuilder/data/test_var.otf.ttx
index ff148682..a94bf12b 100644
--- a/Tests/fontBuilder/data/test_var.otf.ttx
+++ b/Tests/fontBuilder/data/test_var.otf.ttx
@@ -273,6 +273,16 @@
</GlobalSubrs>
</CFF2>
+ <avar>
+ <version major="1" minor="0"/>
+ <segment axis="TEST">
+ <mapping from="-1.0" to="-1.0"/>
+ <mapping from="0.0" to="0.0"/>
+ <mapping from="0.4" to="0.6"/>
+ <mapping from="1.0" to="1.0"/>
+ </segment>
+ </avar>
+
<fvar>
<!-- Test Axis -->
diff --git a/Tests/fontBuilder/fontBuilder_test.py b/Tests/fontBuilder/fontBuilder_test.py
index 775e94d9..c831d02e 100644
--- a/Tests/fontBuilder/fontBuilder_test.py
+++ b/Tests/fontBuilder/fontBuilder_test.py
@@ -1,6 +1,6 @@
-
import os
import pytest
+from fontTools.designspaceLib import AxisDescriptor
from fontTools.ttLib import TTFont
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.t2CharStringPen import T2CharStringPen
@@ -33,45 +33,75 @@ def _setupFontBuilder(isTTF, unitsPerEm=1024):
familyName = "HelloTestFont"
styleName = "TotallyNormal"
- nameStrings = dict(familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
- styleName=dict(en="TotallyNormal", nl="TotaalNormaal"))
- nameStrings['psName'] = familyName + "-" + styleName
+ nameStrings = dict(
+ familyName=dict(en="HelloTestFont", nl="HalloTestFont"),
+ styleName=dict(en="TotallyNormal", nl="TotaalNormaal"),
+ )
+ nameStrings["psName"] = familyName + "-" + styleName
return fb, advanceWidths, nameStrings
def _setupFontBuilderFvar(fb):
- assert 'name' in fb.font, 'Must run setupNameTable() first.'
-
- axes = [
- ('TEST', 0, 0, 100, "Test Axis"),
- ]
+ assert "name" in fb.font, "Must run setupNameTable() first."
+
+ testAxis = AxisDescriptor()
+ testAxis.name = "Test Axis"
+ testAxis.tag = "TEST"
+ testAxis.minimum = 0
+ testAxis.default = 0
+ testAxis.maximum = 100
+ testAxis.map = [(0, 0), (40, 60), (100, 100)]
+ axes = [testAxis]
instances = [
dict(location=dict(TEST=0), stylename="TotallyNormal"),
dict(location=dict(TEST=100), stylename="TotallyTested"),
]
fb.setupFvar(axes, instances)
+ fb.setupAvar(axes)
return fb
def _setupFontBuilderCFF2(fb):
- assert 'fvar' in fb.font, 'Must run _setupFontBuilderFvar() first.'
+ assert "fvar" in fb.font, "Must run _setupFontBuilderFvar() first."
pen = T2CharStringPen(None, None, CFF2=True)
drawTestGlyph(pen)
charString = pen.getCharString()
program = [
- 200, 200, -200, -200, 2, "blend", "rmoveto",
- 400, 400, 1, "blend", "hlineto",
- 400, 400, 1, "blend", "vlineto",
- -400, -400, 1, "blend", "hlineto"
+ 200,
+ 200,
+ -200,
+ -200,
+ 2,
+ "blend",
+ "rmoveto",
+ 400,
+ 400,
+ 1,
+ "blend",
+ "hlineto",
+ 400,
+ 400,
+ 1,
+ "blend",
+ "vlineto",
+ -400,
+ -400,
+ 1,
+ "blend",
+ "hlineto",
]
charStringVariable = T2CharString(program=program)
- charStrings = {".notdef": charString, "A": charString,
- "a": charStringVariable, ".null": charString}
+ charStrings = {
+ ".notdef": charString,
+ "A": charString,
+ "a": charStringVariable,
+ ".null": charString,
+ }
fb.setupCFF2(charStrings, regions=[{"TEST": (0, 1, 1)}])
return fb
@@ -114,6 +144,29 @@ def test_build_ttf(tmpdir):
_verifyOutput(outPath)
+def test_build_cubic_ttf(tmp_path):
+ pen = TTGlyphPen(None)
+ pen.moveTo((100, 100))
+ pen.curveTo((200, 200), (300, 300), (400, 400))
+ pen.closePath()
+ glyph = pen.glyph()
+ glyphs = {"A": glyph}
+
+ # cubic outlines are not allowed in glyf table format 0
+ fb = FontBuilder(1000, isTTF=True, glyphDataFormat=0)
+ with pytest.raises(
+ ValueError, match="Glyph 'A' has cubic Bezier outlines, but glyphDataFormat=0"
+ ):
+ fb.setupGlyf(glyphs)
+ # can skip check if feeling adventurous
+ fb.setupGlyf(glyphs, validateGlyphFormat=False)
+
+ # cubics are (will be) allowed in glyf table format 1
+ fb = FontBuilder(1000, isTTF=True, glyphDataFormat=1)
+ fb.setupGlyf(glyphs)
+ assert "A" in fb.font["glyf"].glyphs
+
+
def test_build_otf(tmpdir):
outPath = os.path.join(str(tmpdir), "test.otf")
@@ -122,8 +175,15 @@ def test_build_otf(tmpdir):
pen = T2CharStringPen(600, None)
drawTestGlyph(pen)
charString = pen.getCharString()
- charStrings = {".notdef": charString, "A": charString, "a": charString, ".null": charString}
- fb.setupCFF(nameStrings['psName'], {"FullName": nameStrings['psName']}, charStrings, {})
+ charStrings = {
+ ".notdef": charString,
+ "A": charString,
+ "a": charString,
+ ".null": charString,
+ }
+ fb.setupCFF(
+ nameStrings["psName"], {"FullName": nameStrings["psName"]}, charStrings, {}
+ )
lsb = {gn: cs.calcBounds(None)[0] for gn, cs in charStrings.items()}
metrics = {}
@@ -179,10 +239,10 @@ def test_build_var(tmpdir):
fb.setupNameTable(nameStrings)
axes = [
- ('LEFT', 0, 0, 100, "Left"),
- ('RGHT', 0, 0, 100, "Right"),
- ('UPPP', 0, 0, 100, "Up"),
- ('DOWN', 0, 0, 100, "Down"),
+ ("LEFT", 0, 0, 100, "Left"),
+ ("RGHT", 0, 0, 100, "Right"),
+ ("UPPP", 0, 0, 100, "Up"),
+ ("DOWN", 0, 0, 100, "Down"),
]
instances = [
dict(location=dict(LEFT=0, RGHT=0, UPPP=0, DOWN=0), stylename="TotallyNormal"),
@@ -195,7 +255,7 @@ def test_build_var(tmpdir):
rightDeltas = [(0, 0), (0, 0), (200, 0), (200, 0), None, None, None, None]
upDeltas = [(0, 0), (0, 200), (0, 200), (0, 0), None, None, None, None]
downDeltas = [(0, -200), (0, 0), (0, 0), (0, -200), None, None, None, None]
- variations['a'] = [
+ variations["a"] = [
TupleVariation(dict(RGHT=(0, 1, 1)), rightDeltas),
TupleVariation(dict(LEFT=(0, 1, 1)), leftDeltas),
TupleVariation(dict(UPPP=(0, 1, 1)), upDeltas),
@@ -209,8 +269,8 @@ def test_build_var(tmpdir):
[
{"LEFT": (0.8, 1), "DOWN": (0.8, 1)},
{"RGHT": (0.8, 1), "UPPP": (0.8, 1)},
- ],
- {"A": "a"}
+ ],
+ {"A": "a"},
)
],
featureTag="rclt",
@@ -218,8 +278,10 @@ def test_build_var(tmpdir):
statAxes = []
for tag, minVal, defaultVal, maxVal, name in axes:
- values = [dict(name="Neutral", value=defaultVal, flags=0x2),
- dict(name=name, value=maxVal)]
+ 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)
@@ -244,7 +306,9 @@ def test_build_cff2(tmpdir):
fb.setupHorizontalMetrics(metrics)
fb.setupHorizontalHeader(ascent=824, descent=200)
- fb.setupOS2(sTypoAscender=825, sTypoDescender=200, usWinAscent=824, usWinDescent=200)
+ fb.setupOS2(
+ sTypoAscender=825, sTypoDescender=200, usWinAscent=824, usWinDescent=200
+ )
fb.setupPost()
fb.save(outPath)
@@ -258,10 +322,16 @@ def test_build_cff_to_cff2(tmpdir):
pen = T2CharStringPen(600, None)
drawTestGlyph(pen)
charString = pen.getCharString()
- charStrings = {".notdef": charString, "A": charString, "a": charString, ".null": charString}
+ charStrings = {
+ ".notdef": charString,
+ "A": charString,
+ "a": charString,
+ ".null": charString,
+ }
fb.setupCFF("TestFont", {}, charStrings, {})
from fontTools.varLib.cff import convertCFFtoCFF2
+
convertCFFtoCFF2(fb.font)
@@ -281,14 +351,17 @@ def test_setupNameTable_no_windows():
assert not any(n for n in fb.font["name"].names if n.platformID == 3)
-@pytest.mark.parametrize('is_ttf, keep_glyph_names, make_cff2, post_format', [
- (True, True, False, 2), # TTF with post table format 2.0
- (True, False, False, 3), # TTF with post table format 3.0
- (False, True, False, 3), # CFF with post table format 3.0
- (False, False, False, 3), # CFF with post table format 3.0
- (False, True, True, 2), # CFF2 with post table format 2.0
- (False, False, True, 3), # CFF2 with post table format 3.0
-])
+@pytest.mark.parametrize(
+ "is_ttf, keep_glyph_names, make_cff2, post_format",
+ [
+ (True, True, False, 2), # TTF with post table format 2.0
+ (True, False, False, 3), # TTF with post table format 3.0
+ (False, True, False, 3), # CFF with post table format 3.0
+ (False, False, False, 3), # CFF with post table format 3.0
+ (False, True, True, 2), # CFF2 with post table format 2.0
+ (False, False, True, 3), # CFF2 with post table format 3.0
+ ],
+)
def test_setupPost(is_ttf, keep_glyph_names, make_cff2, post_format):
fb, _, nameStrings = _setupFontBuilder(is_ttf)
@@ -302,7 +375,7 @@ def test_setupPost(is_ttf, keep_glyph_names, make_cff2, post_format):
fb.setupPost(keepGlyphNames=keep_glyph_names)
assert fb.isTTF is is_ttf
- assert ('CFF2' in fb.font) is make_cff2
+ assert ("CFF2" in fb.font) is make_cff2
assert fb.font["post"].formatType == post_format
@@ -310,7 +383,7 @@ def test_unicodeVariationSequences(tmpdir):
familyName = "UVSTestFont"
styleName = "Regular"
nameStrings = dict(familyName=familyName, styleName=styleName)
- nameStrings['psName'] = familyName + "-" + styleName
+ nameStrings["psName"] = familyName + "-" + styleName
glyphOrder = [".notdef", "space", "zero", "zero.slash"]
cmap = {ord(" "): "space", ord("0"): "zero"}
uvs = [
@@ -338,8 +411,57 @@ def test_unicodeVariationSequences(tmpdir):
uvs = [
(0x0030, 0xFE00, "zero.slash"),
- (0x0030, 0xFE01, "zero"), # should result in the exact same subtable data, due to cmap[0x0030] == "zero"
+ (
+ 0x0030,
+ 0xFE01,
+ "zero",
+ ), # should result in the exact same subtable data, due to cmap[0x0030] == "zero"
]
fb.setupCharacterMap(cmap, uvs)
fb.save(outPath)
_verifyOutput(outPath, tables=["cmap"])
+
+
+def test_setupPanose():
+ from fontTools.ttLib.tables.O_S_2f_2 import Panose
+
+ fb, advanceWidths, nameStrings = _setupFontBuilder(True)
+
+ pen = TTGlyphPen(None)
+ drawTestGlyph(pen)
+ glyph = pen.glyph()
+ glyphs = {".notdef": glyph, "A": glyph, "a": glyph, ".null": glyph}
+ fb.setupGlyf(glyphs)
+ metrics = {}
+ glyphTable = fb.font["glyf"]
+ for gn, advanceWidth in advanceWidths.items():
+ metrics[gn] = (advanceWidth, glyphTable[gn].xMin)
+ fb.setupHorizontalMetrics(metrics)
+
+ fb.setupHorizontalHeader(ascent=824, descent=200)
+ fb.setupNameTable(nameStrings)
+ fb.setupOS2()
+ fb.setupPost()
+
+ panoseValues = { # sample value of Times New Roman from https://www.w3.org/Printing/stevahn.html
+ "bFamilyType": 2,
+ "bSerifStyle": 2,
+ "bWeight": 6,
+ "bProportion": 3,
+ "bContrast": 5,
+ "bStrokeVariation": 4,
+ "bArmStyle": 5,
+ "bLetterForm": 2,
+ "bMidline": 3,
+ "bXHeight": 4,
+ }
+ panoseObj = Panose(**panoseValues)
+
+ for name in panoseValues:
+ assert getattr(fb.font["OS/2"].panose, name) == 0
+
+ fb.setupOS2(panose=panoseObj)
+ fb.setupPost()
+
+ for name, value in panoseValues.items():
+ assert getattr(fb.font["OS/2"].panose, name) == value
diff --git a/Tests/merge/data/CFFFont_expected.ttx b/Tests/merge/data/CFFFont_expected.ttx
index 2c4cd33e..b3d46894 100644
--- a/Tests/merge/data/CFFFont_expected.ttx
+++ b/Tests/merge/data/CFFFont_expected.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="OTTO" ttLibVersion="4.34">
+<ttFont sfntVersion="OTTO" ttLibVersion="4.39">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
@@ -788,12 +788,12 @@
<!-- Most of this table will be recalculated by the compiler -->
<tableVersion value="1.0"/>
<fontRevision value="1.003"/>
- <checkSumAdjustment value="0x9a87f91"/>
+ <checkSumAdjustment value="0x579debce"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
- <created value="Sun Aug 14 18:30:31 2022"/>
- <modified value="Sun Aug 14 18:30:31 2022"/>
+ <created value="Wed Mar 29 18:41:25 2023"/>
+ <modified value="Wed Mar 29 18:41:25 2023"/>
<xMin value="-199"/>
<yMin value="-364"/>
<xMax value="1459"/>
@@ -1381,14 +1381,14 @@
endchar
</CharString>
<CharString name=".notdef.1">
- -45 50 -200 rmoveto
+ 136 50 -200 rmoveto
400 1000 -400 -1000 hlineto
50 50 rmoveto
900 300 -900 -300 vlineto
endchar
</CharString>
<CharString name="A">
- 177 572 -10 rmoveto
+ 358 572 -10 rmoveto
15 15 2 4 15 hvcurveto
104 30 -1 26 rlineto
-1 -14 -15 -2 -14 hhcurveto
@@ -1417,7 +1417,7 @@
endchar
</CharString>
<CharString name="AE">
- 252 366 66 rmoveto
+ 433 366 66 rmoveto
-27 6 -28 20 -11 vhcurveto
388 hlineto
13 53 -5 5 rlineto
@@ -1452,7 +1452,7 @@
endchar
</CharString>
<CharString name="Aacute">
- 177 517 882 rmoveto
+ 358 517 882 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
217 -738 rmoveto
15 15 2 4 15 hvcurveto
@@ -1483,7 +1483,7 @@
endchar
</CharString>
<CharString name="Acircumflex">
- 177 189 729 rmoveto
+ 358 189 729 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
214 -920 rmoveto
15 15 2 4 15 hvcurveto
@@ -1514,7 +1514,7 @@
endchar
</CharString>
<CharString name="Adieresis">
- 177 512 813 rmoveto
+ 358 512 813 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -1547,7 +1547,7 @@
endchar
</CharString>
<CharString name="Agrave">
- 177 222 883 rmoveto
+ 358 222 883 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
303 -929 rmoveto
15 15 2 4 15 hvcurveto
@@ -1578,7 +1578,7 @@
endchar
</CharString>
<CharString name="Aring">
- 177 572 -10 rmoveto
+ 358 572 -10 rmoveto
15 15 2 4 15 hvcurveto
104 30 -1 26 rlineto
-1 -14 -15 -2 -14 hhcurveto
@@ -1611,7 +1611,7 @@
endchar
</CharString>
<CharString name="Atilde">
- 177 350 838 rmoveto
+ 358 350 838 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -1645,7 +1645,7 @@
endchar
</CharString>
<CharString name="B">
- 14 335 378 rmoveto
+ 195 335 378 rmoveto
81 34 72 44 97 vvcurveto
109 -116 24 -113 vhcurveto
-247 hlineto
@@ -1665,7 +1665,7 @@
endchar
</CharString>
<CharString name="C">
- 88 624 586 rmoveto
+ 269 624 586 rmoveto
85 -52 -76 28 -88 hhcurveto
-104 -101 -36 -73 -73 hvcurveto
-70 -70 -21 -98 -96 vvcurveto
@@ -1681,7 +1681,7 @@
endchar
</CharString>
<CharString name="Ccedilla">
- 88 285 -112 rmoveto
+ 269 285 -112 rmoveto
37 -11 50 -31 -28 -38 -14 -18 -32 -14 -26 -10 -5 -10 rcurveline
6 -15 55 10 70 18 33 44 rlinecurve
26 36 -9 36 -23 28 -19 23 -40 18 -13 5 11 26 rcurveline
@@ -1699,7 +1699,7 @@
endchar
</CharString>
<CharString name="D">
- 192 295 2 rmoveto
+ 373 295 2 rmoveto
104 131 16 73 67 hvcurveto
77 83 22 84 127 vvcurveto
177 -57 124 -310 vhcurveto
@@ -1721,7 +1721,7 @@
endchar
</CharString>
<CharString name="Delta">
- 206 46 45 rmoveto
+ 387 46 45 rmoveto
2 -42 rlineto
5 73 74 2 74 hhcurveto
125 124 -6 -8 125 hvcurveto
@@ -1736,7 +1736,7 @@
endchar
</CharString>
<CharString name="E">
- -27 417 384 rmoveto
+ 154 417 384 rmoveto
-6 7 rlineto
-4 -53 -74 -3 -53 hhcurveto
-37 -29 -3 17 hvcurveto
@@ -1761,7 +1761,7 @@
endchar
</CharString>
<CharString name="Eacute">
- -27 385 885 rmoveto
+ 154 385 885 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
194 -347 rmoveto
-6 7 rlineto
@@ -1788,7 +1788,7 @@
endchar
</CharString>
<CharString name="Ecircumflex">
- -27 107 732 rmoveto
+ 154 107 732 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
141 -529 rmoveto
-6 7 rlineto
@@ -1815,7 +1815,7 @@
endchar
</CharString>
<CharString name="Edieresis">
- -27 430 816 rmoveto
+ 154 430 816 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -1844,7 +1844,7 @@
endchar
</CharString>
<CharString name="Egrave">
- -27 140 886 rmoveto
+ 154 140 886 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
230 -538 rmoveto
-6 7 rlineto
@@ -1871,7 +1871,7 @@
endchar
</CharString>
<CharString name="Eth">
- 201 304 2 rmoveto
+ 382 304 2 rmoveto
104 131 16 73 67 hvcurveto
77 83 22 84 127 vvcurveto
177 -57 124 -310 vhcurveto
@@ -1897,7 +1897,7 @@
endchar
</CharString>
<CharString name="F">
- -104 87 360 rmoveto
+ 77 87 360 rmoveto
-121 -3 -120 -4 -121 vhcurveto
92 15 rlineto
-4 101 -1 115 101 vvcurveto
@@ -1917,7 +1917,7 @@
endchar
</CharString>
<CharString name="G">
- 116 325 270 rmoveto
+ 297 325 270 rmoveto
6 -5 54 -2 64 -3 44 -4 rlinecurve
38 -4 -10 -98 -20 vvcurveto
-30 6 -54 -26 -5 vhcurveto
@@ -1935,7 +1935,7 @@
endchar
</CharString>
<CharString name="Gamma">
- -53 464 697 rmoveto
+ 128 464 697 rmoveto
-3 -60 -61 -1 -61 hhcurveto
-237 hlineto
-3 -20 26 -22 rlineto
@@ -1951,7 +1951,7 @@
endchar
</CharString>
<CharString name="H">
- 161 699 32 rmoveto
+ 342 699 32 rmoveto
-7 -70 11 36 hvcurveto
-3 81 -1 81 81 vvcurveto
121 2 120 9 121 vhcurveto
@@ -1976,7 +1976,7 @@
endchar
</CharString>
<CharString name="I">
- -265 275 23 rmoveto
+ -84 275 23 rmoveto
-1 -43 11 6 -21 hvcurveto
-42 11 11 75 -1 43 rrcurveto
-1 61 -1 62 67 vvcurveto
@@ -1995,7 +1995,7 @@
endchar
</CharString>
<CharString name="Iacute">
- -265 252 885 rmoveto
+ -84 252 885 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
185 -708 rmoveto
-1 -43 11 6 -21 hvcurveto
@@ -2016,7 +2016,7 @@
endchar
</CharString>
<CharString name="Icircumflex">
- -265 -26 732 rmoveto
+ -84 -26 732 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
132 -890 rmoveto
-1 -43 11 6 -21 hvcurveto
@@ -2037,7 +2037,7 @@
endchar
</CharString>
<CharString name="Idieresis">
- -265 297 816 rmoveto
+ -84 297 816 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -2060,7 +2060,7 @@
endchar
</CharString>
<CharString name="Igrave">
- -265 7 886 rmoveto
+ -84 7 886 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
221 -899 rmoveto
-1 -43 11 6 -21 hvcurveto
@@ -2081,7 +2081,7 @@
endchar
</CharString>
<CharString name="J">
- -280 175 608 rmoveto
+ -99 175 608 rmoveto
18 9 17 12 3 vhcurveto
68 14 2 24 -7 6 rlineto
-250 hlineto
@@ -2096,7 +2096,7 @@
endchar
</CharString>
<CharString name="K">
- 48 178 319 rmoveto
+ 229 178 319 rmoveto
108 -109 112 -105 105 -112 85 31 rcurveline
1 11 -61 54 -165 164 -107 107 rlinecurve
-4 4 -1 2 3 vvcurveto
@@ -2116,7 +2116,7 @@
endchar
</CharString>
<CharString name="L">
- -51 482 58 rmoveto
+ 130 482 58 rmoveto
-1 -6 -182 -14 -104 hhcurveto
-5 -7 7 6 -1 hvcurveto
-4 108 -3 116 113 vvcurveto
@@ -2137,7 +2137,7 @@
endchar
</CharString>
<CharString name="Lambda">
- 203 368 673 rmoveto
+ 384 368 673 rmoveto
-115 -227 -123 -222 -127 -220 13 -10 rcurveline
27 4 26 9 25 10 28 66 146 294 95 188 rrcurveto
12 hlineto
@@ -2148,7 +2148,7 @@
endchar
</CharString>
<CharString name="M">
- 417 475 147 rmoveto
+ 598 475 147 rmoveto
-4 -2 -6 -5 -4 hhcurveto
-4 -5 10 4 -2 hvcurveto
-83 177 -94 200 -55 161 rrcurveto
@@ -2177,7 +2177,7 @@
endchar
</CharString>
<CharString name="N">
- 187 577 678 rmoveto
+ 368 577 678 rmoveto
21 -304 3 -99 -102 vvcurveto
-2 -1 -6 -5 -4 -4 5 3 -3 vhcurveto
-117 126 -162 215 -133 176 rrcurveto
@@ -2196,7 +2196,7 @@
endchar
</CharString>
<CharString name="Ntilde">
- 187 393 841 rmoveto
+ 368 393 841 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -2220,7 +2220,7 @@
endchar
</CharString>
<CharString name="O">
- 192 320 -18 rmoveto
+ 373 320 -18 rmoveto
157 129 72 98 48 hvcurveto
40 81 1 65 66 vvcurveto
93 -22 104 -62 63 vhcurveto
@@ -2235,7 +2235,7 @@
endchar
</CharString>
<CharString name="OE">
- 452 896 384 rmoveto
+ 633 896 384 rmoveto
-6 7 rlineto
-4 -53 -74 -3 -53 hhcurveto
-37 -29 -3 17 hvcurveto
@@ -2267,7 +2267,7 @@
endchar
</CharString>
<CharString name="Oacute">
- 209 557 918 rmoveto
+ 390 557 918 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
-75 -782 rmoveto
157 129 72 98 48 hvcurveto
@@ -2284,7 +2284,7 @@
endchar
</CharString>
<CharString name="Ocircumflex">
- 192 218 742 rmoveto
+ 373 218 742 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
-67 -941 rmoveto
157 129 72 98 48 hvcurveto
@@ -2301,7 +2301,7 @@
endchar
</CharString>
<CharString name="Odieresis">
- 192 541 826 rmoveto
+ 373 541 826 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -2320,7 +2320,7 @@
endchar
</CharString>
<CharString name="Ograve">
- 192 251 896 rmoveto
+ 373 251 896 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
22 -950 rmoveto
157 129 72 98 48 hvcurveto
@@ -2337,7 +2337,7 @@
endchar
</CharString>
<CharString name="Omega">
- 360 853 60 rmoveto
+ 541 853 60 rmoveto
-70 -96 -5 -4 -85 hvcurveto
12 vlineto
135 55 50 138 129 vvcurveto
@@ -2358,7 +2358,7 @@
endchar
</CharString>
<CharString name="Oslash">
- 192 320 -18 rmoveto
+ 373 320 -18 rmoveto
157 129 72 98 48 hvcurveto
40 81 1 65 66 vvcurveto
93 -22 104 -62 63 vhcurveto
@@ -2382,7 +2382,7 @@
endchar
</CharString>
<CharString name="Otilde">
- 192 379 851 rmoveto
+ 373 379 851 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -2402,7 +2402,7 @@
endchar
</CharString>
<CharString name="P">
- -18 172 22 rmoveto
+ 163 172 22 rmoveto
-1 101 -6 126 25 vvcurveto
19 65 -2 29 2 vhcurveto
65 5 73 15 44 45 rrcurveto
@@ -2421,7 +2421,7 @@
endchar
</CharString>
<CharString name="Phi">
- 187 234 657 rmoveto
+ 368 234 657 rmoveto
63 -9 rlineto
13 -2 10 -12 -14 vvcurveto
-12 vlineto
@@ -2454,7 +2454,7 @@
endchar
</CharString>
<CharString name="Pi">
- 123 668 686 rmoveto
+ 304 668 686 rmoveto
-2 -81 -119 -3 -44 hhcurveto
-198 hlineto
-75 -75 4 5 -75 hvcurveto
@@ -2478,7 +2478,7 @@
endchar
</CharString>
<CharString name="Psi">
- 191 443 251 rmoveto
+ 372 443 251 rmoveto
117 2 74 32 31 203 16 90 1 55 44 28 -3 18 rcurveline
-35 -4 -35 -8 -34 -12 -12 -43 -1 -45 -6 -44 -18 -127 -29 -114 -112 -6 -7 6 rcurveline
-2 58 -1 59 59 vvcurveto
@@ -2504,7 +2504,7 @@
endchar
</CharString>
<CharString name="Q">
- 195 722 -174 rmoveto
+ 376 722 -174 rmoveto
-6 -23 -48 -9 -16 hhcurveto
-89 -42 46 21 -15 hvcurveto
-20 26 -26 49 -16 30 4 10 rcurveline
@@ -2525,7 +2525,7 @@
endchar
</CharString>
<CharString name="R">
- 3 275 331 rmoveto
+ 184 275 331 rmoveto
119 13 96 56 139 vvcurveto
93 -81 47 -122 vhcurveto
-272 hlineto
@@ -2547,7 +2547,7 @@
endchar
</CharString>
<CharString name="S">
- -61 451 606 rmoveto
+ 120 451 606 rmoveto
83 -44 -60 13 -73 hhcurveto
-109 -103 -65 -125 -98 62 -48 121 -52 hvcurveto
60 -26 61 -54 -56 vvcurveto
@@ -2561,7 +2561,7 @@
endchar
</CharString>
<CharString name="Sigma">
- 26 299 362 rmoveto
+ 207 299 362 rmoveto
4 7 7 9 6 vvcurveto
6 -2 5 -3 5 vhcurveto
-137 222 11 13 rlineto
@@ -2581,7 +2581,7 @@
endchar
</CharString>
<CharString name="T">
- 6 543 696 rmoveto
+ 187 543 696 rmoveto
-4 -88 -99 -6 -75 hhcurveto
-177 0 8 3 -95 hvcurveto
-7 -7 rlineto
@@ -2599,7 +2599,7 @@
endchar
</CharString>
<CharString name="Theta">
- 233 348 -12 rmoveto
+ 414 348 -12 rmoveto
196 194 83 310 87 -20 93 -63 63 hvcurveto
57 -57 -84 20 -79 hhcurveto
-152 -139 -58 -100 -58 hvcurveto
@@ -2628,7 +2628,7 @@
endchar
</CharString>
<CharString name="Thorn">
- -18 168 679 rmoveto
+ 163 168 679 rmoveto
-151 hlineto
-7 -7 rlineto
-18 vlineto
@@ -2649,7 +2649,7 @@
endchar
</CharString>
<CharString name="U">
- 167 183 674 rmoveto
+ 348 183 674 rmoveto
-7 6 rlineto
-169 hlineto
-8 -7 2 -20 rlineto
@@ -2673,7 +2673,7 @@
endchar
</CharString>
<CharString name="Uacute">
- 167 482 885 rmoveto
+ 348 482 885 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
-137 -57 rmoveto
-7 6 rlineto
@@ -2699,7 +2699,7 @@
endchar
</CharString>
<CharString name="Ucircumflex">
- 167 204 732 rmoveto
+ 348 204 732 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
-190 -239 rmoveto
-7 6 rlineto
@@ -2725,7 +2725,7 @@
endchar
</CharString>
<CharString name="Udieresis">
- 167 527 816 rmoveto
+ 348 527 816 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -2753,7 +2753,7 @@
endchar
</CharString>
<CharString name="Ugrave">
- 167 237 886 rmoveto
+ 348 237 886 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
-101 -248 rmoveto
-7 6 rlineto
@@ -2779,7 +2779,7 @@
endchar
</CharString>
<CharString name="Upsilon">
- 137 672 546 rmoveto
+ 318 672 546 rmoveto
75 -21 76 -85 -155 -48 -199 -123 -21 vhcurveto
-9 hlineto
130 -13 -32 192 -146 hhcurveto
@@ -2796,7 +2796,7 @@
endchar
</CharString>
<CharString name="V">
- 162 614 680 rmoveto
+ 343 614 680 rmoveto
-25 -43 -25 -44 -19 -45 -119 -274 rcurveline
-41 -89 -23 -52 -7 -2 -2 1 -4 2 -2 4 -71 182 -55 187 -39 191 -175 -10 rcurveline
-7 -6 1 -27 56 -8 23 -5 16 -33 rlinecurve
@@ -2806,7 +2806,7 @@
endchar
</CharString>
<CharString name="W">
- 505 965 675 rmoveto
+ 686 965 675 rmoveto
-17 -32 -22 -39 -21 -46 -109 -242 rcurveline
-80 -170 rlineto
-3 -2 -2 -4 -9 hhcurveto
@@ -2829,7 +2829,7 @@
endchar
</CharString>
<CharString name="X">
- 79 612 687 rmoveto
+ 260 612 687 rmoveto
-95 -13 -9 -21 -138 -198 -33 -33 rlinecurve
-9 hlineto
-147 266 -45 -3 -76 -4 -25 -1 rlinecurve
@@ -2850,7 +2850,7 @@
endchar
</CharString>
<CharString name="Xi">
- 68 49 695 rmoveto
+ 249 49 695 rmoveto
-5 -57 -1 -58 -7 -57 27 2 rcurveline
23 64 rlineto
20 7 24 9 20 hhcurveto
@@ -2876,7 +2876,7 @@
endchar
</CharString>
<CharString name="Y">
- 47 231 -2 rmoveto
+ 228 231 -2 rmoveto
11 -9 90 29 -9 288 83 128 74 96 111 145 rlinecurve
-4 18 -85 -17 -36 -57 -84 -145 -8 -14 rlinecurve
-54 -30 -35 -53 -5 hhcurveto
@@ -2890,7 +2890,7 @@
endchar
</CharString>
<CharString name="Yacute">
- 47 451 885 rmoveto
+ 228 451 885 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
-58 -733 rmoveto
11 -9 90 29 -9 288 83 128 74 96 111 145 rlinecurve
@@ -2906,7 +2906,7 @@
endchar
</CharString>
<CharString name="Z">
- 58 552 683 rmoveto
+ 239 552 683 rmoveto
-149 -148 3 9 -148 hvcurveto
-9 -8 rlineto
-54 vlineto
@@ -2920,7 +2920,7 @@
endchar
</CharString>
<CharString name="a">
- 3 278 466 rmoveto
+ 184 278 466 rmoveto
-170 -68 -140 -141 -90 36 -107 111 58 56 31 32 49 hvcurveto
15 -4 rlineto
-34 6 14 -25 37 hhcurveto
@@ -2941,7 +2941,7 @@
endchar
</CharString>
<CharString name="aacute">
- 3 389 702 rmoveto
+ 184 389 702 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
51 -82 rmoveto
-170 -68 -140 -141 -90 36 -107 111 58 56 31 32 49 hvcurveto
@@ -2964,7 +2964,7 @@
endchar
</CharString>
<CharString name="acircumflex">
- 3 111 549 rmoveto
+ 184 111 549 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
-2 -264 rmoveto
-170 -68 -140 -141 -90 36 -107 111 58 56 31 32 49 hvcurveto
@@ -2987,17 +2987,17 @@
endchar
</CharString>
<CharString name="acute">
- -25 405 664 rmoveto
+ 156 405 664 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
endchar
</CharString>
<CharString name="acutecomb">
- -545 115 664 rmoveto
+ 115 664 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
endchar
</CharString>
<CharString name="adieresis">
- 3 434 633 rmoveto
+ 184 434 633 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -3022,7 +3022,7 @@
endchar
</CharString>
<CharString name="ae">
- 207 707 319 rmoveto
+ 388 707 319 rmoveto
11 5 10 4 12 vvcurveto
77 -56 61 -86 -67 -64 -25 -46 -48 vhcurveto
39 -22 -65 20 -42 hhcurveto
@@ -3057,7 +3057,7 @@
endchar
</CharString>
<CharString name="agrave">
- 3 144 703 rmoveto
+ 184 144 703 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
87 -273 rmoveto
-170 -68 -140 -141 -90 36 -107 111 58 56 31 32 49 hvcurveto
@@ -3579,7 +3579,7 @@
endchar
</CharString>
<CharString name="alpha">
- 64 410 307 rmoveto
+ 245 410 307 rmoveto
-10 1 rlineto
55 -15 -34 105 -89 hhcurveto
-151 -81 -161 -138 -68 25 -113 81 60 60 50 44 36 hvcurveto
@@ -3597,7 +3597,7 @@
endchar
</CharString>
<CharString name="ampersand">
- 193 326 356 rmoveto
+ 374 326 356 rmoveto
3 23 23 2 23 hhcurveto
22 23 0 -16 16 hvcurveto
21 -21 15 -28 -30 vvcurveto
@@ -3632,7 +3632,7 @@
endchar
</CharString>
<CharString name="aring">
- 3 177 613 rmoveto
+ 184 177 613 rmoveto
-54 43 -43 54 54 43 43 54 54 -43 43 -54 -54 -43 -43 -54 vhcurveto
30 hmoveto
37 30 30 37 37 30 -30 -37 -37 -30 -30 -37 -37 -30 30 37 vhcurveto
@@ -3657,7 +3657,7 @@
endchar
</CharString>
<CharString name="asciitilde">
- 127 342 291 rmoveto
+ 308 342 291 rmoveto
25 -41 -50 31 -44 hhcurveto
-59 -36 -49 -58 -29 hvcurveto
22 -12 rlineto
@@ -3672,7 +3672,7 @@
endchar
</CharString>
<CharString name="asterisk">
- -244 175 448 rmoveto
+ -63 175 448 rmoveto
-8 36 -2 33 -1 35 26 -20 24 -22 17 -19 26 29 rcurveline
-28 13 -29 20 -31 23 28 20 28 17 32 15 -25 31 rcurveline
-20 -21 -23 -21 -25 -20 2 33 2 29 7 33 rrcurveto
@@ -3684,7 +3684,7 @@
endchar
</CharString>
<CharString name="at">
- 183 472 371 rmoveto
+ 364 472 371 rmoveto
16 -25 -35 8 -25 hhcurveto
-132 -56 -97 -99 -64 29 -74 88 46 44 22 22 39 hvcurveto
12 -3 rlineto
@@ -3708,7 +3708,7 @@
endchar
</CharString>
<CharString name="atilde">
- 3 272 658 rmoveto
+ 184 272 658 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -3734,7 +3734,7 @@
endchar
</CharString>
<CharString name="b">
- -23 -14 640 rmoveto
+ 158 -14 640 rmoveto
86 -13 8 7 -35 vvcurveto
-476 vlineto
-111 66 -22 51 114 117 80 107 35 vhcurveto
@@ -3752,12 +3752,12 @@
endchar
</CharString>
<CharString name="backslash">
- -155 404 -184 rmoveto
+ 26 404 -184 rmoveto
3 13 -388 893 -31 -7 -5 -11 389 -896 rlineto
endchar
</CharString>
<CharString name="bar">
- -329 128 738 rmoveto
+ -148 128 738 rmoveto
-40 -8 rlineto
-897 vlineto
40 8 rlineto
@@ -4080,7 +4080,7 @@
endchar
</CharString>
<CharString name="beta">
- -15 325 392 rmoveto
+ 166 325 392 rmoveto
65 25 58 71 75 vvcurveto
75 -61 60 -75 -106 -71 -57 -81 -38 vhcurveto
-22 -47 -8 -78 -2 -51 -8 -190 4 -191 -5 -190 12 -15 rcurveline
@@ -4100,7 +4100,7 @@
endchar
</CharString>
<CharString name="braceleft">
- -232 303 -154 rmoveto
+ -51 303 -154 rmoveto
-97 -36 2 182 91 7 120 -92 32 hvcurveto
1 vlineto
64 23 21 88 133 vvcurveto
@@ -4117,7 +4117,7 @@
endchar
</CharString>
<CharString name="braceright">
- -232 10 -175 rmoveto
+ -51 10 -175 rmoveto
149 34 -1 213 104 -3 101 61 15 hvcurveto
17 4 19 2 18 -1 rrcurveto
26 vlineto
@@ -4133,7 +4133,7 @@
endchar
</CharString>
<CharString name="bracketleft">
- -287 247 736 rmoveto
+ -106 247 736 rmoveto
-54 -66 0 5 -39 hvcurveto
-866 vlineto
5 52 53 0 54 hhcurveto
@@ -4144,7 +4144,7 @@
endchar
</CharString>
<CharString name="bracketright">
- -287 130 -83 rmoveto
+ -106 130 -83 rmoveto
-119 -13 rlineto
-24 vlineto
54 53 0 -5 52 hvcurveto
@@ -4155,7 +4155,7 @@
endchar
</CharString>
<CharString name="brokenbar">
- -337 124 738 rmoveto
+ -156 124 738 rmoveto
-40 -8 rlineto
-403 vlineto
40 8 rlineto
@@ -4166,12 +4166,12 @@
endchar
</CharString>
<CharString name="bullet">
- -213 69 265 rmoveto
+ -32 69 265 rmoveto
-54 43 -43 54 54 43 43 54 54 -43 43 -54 -54 -43 -43 -54 vhcurveto
endchar
</CharString>
<CharString name="c">
- -90 425 116 rmoveto
+ 91 425 116 rmoveto
-38 -44 -50 -31 -59 hhcurveto
-117 -24 115 86 88 45 100 85 43 33 -39 -67 9 hvcurveto
17 -6 54 53 rlineto
@@ -4181,7 +4181,7 @@
endchar
</CharString>
<CharString name="ccedilla">
- -90 180 -122 rmoveto
+ 91 180 -122 rmoveto
37 -11 50 -31 -28 -38 -14 -18 -32 -14 -26 -10 -5 -10 rcurveline
6 -15 55 10 70 18 33 44 rlinecurve
26 36 -9 36 -23 28 -19 23 -40 18 -13 5 15 36 rcurveline
@@ -4196,7 +4196,7 @@
endchar
</CharString>
<CharString name="cedilla">
- -25 188 -112 rmoveto
+ 156 188 -112 rmoveto
37 -11 50 -31 -28 -38 -14 -18 -32 -14 -26 -10 -5 -10 rcurveline
6 -15 55 10 70 18 33 44 rlinecurve
26 36 -9 36 -23 28 -19 23 -40 18 -13 5 20 47 rcurveline
@@ -4204,7 +4204,7 @@
endchar
</CharString>
<CharString name="cedillacomb">
- -545 -72 -112 rmoveto
+ -72 -112 rmoveto
37 -11 50 -31 -28 -38 -14 -18 -32 -14 -26 -10 -5 -10 rcurveline
6 -15 55 10 70 18 33 44 rlinecurve
26 36 -9 36 -23 28 -19 23 -40 18 -13 5 20 47 rcurveline
@@ -4212,7 +4212,7 @@
endchar
</CharString>
<CharString name="cent">
- -84 418 116 rmoveto
+ 97 418 116 rmoveto
-38 -44 -50 -31 -59 hhcurveto
-2 388 hlineto
39 -5 29 -38 8 -62 17 -6 rcurveline
@@ -4236,7 +4236,7 @@
endchar
</CharString>
<CharString name="chi">
- -20 284 206 rmoveto
+ 161 284 206 rmoveto
-90 192 rlineto
30 -14 -23 38 -37 hhcurveto
-25 -25 -8 -15 -19 hvcurveto
@@ -4257,24 +4257,24 @@
endchar
</CharString>
<CharString name="circumflex">
- -45 103 511 rmoveto
+ 136 103 511 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
endchar
</CharString>
<CharString name="circumflexcomb">
- -545 -163 511 rmoveto
+ -163 511 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
endchar
</CharString>
<CharString name="colon">
- -361 35 398 rmoveto
+ -180 35 398 rmoveto
-31 26 -26 32 31 25 26 31 31 -25 26 -31 -32 -26 -26 -31 vhcurveto
-353 vmoveto
-31 26 -26 32 31 25 26 31 31 -25 26 -31 -32 -26 -26 -31 vhcurveto
endchar
</CharString>
<CharString name="comma">
- -312 77 -210 rmoveto
+ -131 77 -210 rmoveto
71 82 33 51 50 vvcurveto
35 -35 130 -53 -25 -34 -22 -26 -10 3 -11 4 -5 vhcurveto
70 -83 rlineto
@@ -4301,7 +4301,7 @@
endchar
</CharString>
<CharString name="copyright">
- 87 45 437 rmoveto
+ 268 45 437 rmoveto
-150 121 -121 150 150 121 121 150 150 -121 121 -150 -150 -121 -121 -150 vhcurveto
25 hmoveto
136 110 110 136 136 110 -110 -136 -136 -110 -110 -136 -136 -110 110 136 vhcurveto
@@ -4323,7 +4323,7 @@
endchar
</CharString>
<CharString name="currency">
- 45 75 484 rmoveto
+ 226 75 484 rmoveto
49 -49 rlineto
-30 -34 -12 -36 -52 vvcurveto
-50 12 -35 30 -38 vhcurveto
@@ -4342,7 +4342,7 @@
endchar
</CharString>
<CharString name="d">
- 6 269 638 rmoveto
+ 187 269 638 rmoveto
33 47 -1 -35 11 hvcurveto
7 -26 -1 -72 1 -53 -7 -4 rcurveline
9 -27 -27 6 -29 hhcurveto
@@ -4485,14 +4485,14 @@
endchar
</CharString>
<CharString name="degree">
- -213 69 575 rmoveto
+ -32 69 575 rmoveto
-54 43 -43 54 54 43 43 54 54 -43 43 -54 -54 -43 -43 -54 vhcurveto
30 hmoveto
37 30 30 37 37 30 -30 -37 -37 -30 -30 -37 -37 -30 30 37 vhcurveto
endchar
</CharString>
<CharString name="delta">
- -122 349 687 rmoveto
+ 59 349 687 rmoveto
4 -29 -24 3 -27 hhcurveto
-73 -63 -21 -71 -35 hvcurveto
-6 -12 -5 -12 -13 vvcurveto
@@ -4509,21 +4509,21 @@
endchar
</CharString>
<CharString name="dieresis">
- -25 420 595 rmoveto
+ 156 420 595 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
endchar
</CharString>
<CharString name="dieresiscomb">
- -545 160 595 rmoveto
+ 160 595 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
endchar
</CharString>
<CharString name="divide">
- 279 471 514 rmoveto
+ 460 471 514 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
273 -238 rmoveto
-657 hlineto
@@ -4534,7 +4534,7 @@
endchar
</CharString>
<CharString name="dollar">
- -69 439 593 rmoveto
+ 112 439 593 rmoveto
79 -42 -57 12 -69 -1 -1 0 -1 hhcurveto
114 vlineto
-40 -8 rlineto
@@ -4578,7 +4578,7 @@
endchar
</CharString>
<CharString name="dotlessi">
- -222 192 392 rmoveto
+ -41 192 392 rmoveto
44 -17 35 -45 -39 -56 -39 -36 -45 vhcurveto
13 -18 rlineto
15 24 34 17 22 hhcurveto
@@ -4592,7 +4592,7 @@
endchar
</CharString>
<CharString name="dotlessj">
- -257 18 375 rmoveto
+ -76 18 375 rmoveto
13 25 35 20 16 hhcurveto
36 8 -12 -242 -44 -3 -124 -17 -62 hvcurveto
-14 -50 -40 -45 -74 -44 16 -16 rcurveline
@@ -4615,7 +4615,7 @@
endchar
</CharString>
<CharString name="e">
- -83 438 340 rmoveto
+ 98 438 340 rmoveto
77 -56 61 -86 -76 -72 -33 -57 -49 vhcurveto
-39 -45 -12 -59 -58 vvcurveto
-113 32 -125 134 86 80 58 58 62 vhcurveto
@@ -4633,7 +4633,7 @@
endchar
</CharString>
<CharString name="eacute">
- -107 354 714 rmoveto
+ 74 354 714 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
246 -220 rmoveto
77 -56 61 -86 -76 -72 -33 -57 -49 vhcurveto
@@ -4653,7 +4653,7 @@
endchar
</CharString>
<CharString name="ecircumflex">
- -107 76 561 rmoveto
+ 74 76 561 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
193 -402 rmoveto
77 -56 61 -86 -76 -72 -33 -57 -49 vhcurveto
@@ -4673,7 +4673,7 @@
endchar
</CharString>
<CharString name="edieresis">
- -107 399 615 rmoveto
+ 74 399 615 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -4695,7 +4695,7 @@
endchar
</CharString>
<CharString name="egrave">
- -107 109 716 rmoveto
+ 74 109 716 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
282 -412 rmoveto
77 -56 61 -86 -76 -72 -33 -57 -49 vhcurveto
@@ -4715,7 +4715,7 @@
endchar
</CharString>
<CharString name="eight">
- -45 312 396 rmoveto
+ 136 312 396 rmoveto
60 37 63 63 72 vvcurveto
82 -70 56 -102 vhcurveto
-9 hlineto
@@ -4757,7 +4757,7 @@
endchar
</CharString>
<CharString name="eight.dnom">
- 230 231 rmoveto
+ 12 230 231 rmoveto
7 vlineto
40 22 44 38 43 vvcurveto
50 -49 33 -68 vhcurveto
@@ -4779,7 +4779,7 @@
endchar
</CharString>
<CharString name="eight.numr">
- 230 506 rmoveto
+ 12 230 506 rmoveto
7 vlineto
40 22 44 38 43 vvcurveto
50 -49 33 -68 vhcurveto
@@ -4850,7 +4850,7 @@
endchar
</CharString>
<CharString name="epsilon">
- -175 335 430 rmoveto
+ 6 335 430 rmoveto
22 -31 -35 19 -39 hhcurveto
-85 -82 -55 -96 -30 20 -34 23 -14 hvcurveto
-7 vlineto
@@ -4866,7 +4866,7 @@
endchar
</CharString>
<CharString name="epsilon1">
- -125 213 258 rmoveto
+ 56 213 258 rmoveto
-2 -1 -64 1 -37 hhcurveto
-6 6 rlineto
78 2 35 87 96 hhcurveto
@@ -4886,7 +4886,7 @@
endchar
</CharString>
<CharString name="equal">
- 278 745 369 rmoveto
+ 459 745 369 rmoveto
-661 hlineto
-7 -40 rlineto
662 hlineto
@@ -4897,7 +4897,7 @@
endchar
</CharString>
<CharString name="eta">
- -5 498 -164 rmoveto
+ 176 498 -164 rmoveto
-15 122 -2 123 123 vvcurveto
177 11 85 -104 -64 -71 -38 -37 -56 vhcurveto
-11 2 rlineto
@@ -4916,7 +4916,7 @@
endchar
</CharString>
<CharString name="eth">
- -84 144 662 rmoveto
+ 97 144 662 rmoveto
27 -16 23 -17 20 -22 -108 -57 rcurveline
13 -39 122 65 22 -31 20 -36 18 -43 rlinecurve
-3 -5 -37 4 -23 -1 -21 -3 rlinecurve
@@ -4927,7 +4927,7 @@
endchar
</CharString>
<CharString name="euro">
- 93 425 270 rmoveto
+ 274 425 270 rmoveto
10 40 rlineto
-225 hlineto
-1 11 -1 12 11 vvcurveto
@@ -4963,7 +4963,7 @@
endchar
</CharString>
<CharString name="exclam.1">
- -329 112 186 rmoveto
+ -148 112 186 rmoveto
36 371 rlineto
3 29 2 29 29 vvcurveto
20 -7 26 -30 -32 -18 -14 -51 vhcurveto
@@ -4973,7 +4973,7 @@
endchar
</CharString>
<CharString name="exclamdown">
- -329 54 442 rmoveto
+ -148 54 442 rmoveto
-31 26 -26 32 31 25 26 31 31 -25 26 -31 -32 -26 -26 -31 vhcurveto
58 -140 rmoveto
-16 2 -27 -383 -1 -19 -3 -22 1 -17 rlinecurve
@@ -4981,7 +4981,7 @@
endchar
</CharString>
<CharString name="f">
- -216 3 428 rmoveto
+ -35 3 428 rmoveto
-4 -22 5 -7 rlineto
112 hlineto
6 -8 rlineto
@@ -5005,7 +5005,7 @@
endchar
</CharString>
<CharString name="f.alt">
- -228 314 641 rmoveto
+ -47 314 641 rmoveto
28 -22 vlineto
-63 -60 -37 -70 -30 hvcurveto
-16 -36 -2 -51 -35 vvcurveto
@@ -5411,7 +5411,7 @@
endchar
</CharString>
<CharString name="five">
- -45 125 594 rmoveto
+ 136 125 594 rmoveto
6 6 4 6 48 126 3 2 87 vhcurveto
27 78 rlineto
-335 hlineto
@@ -5469,7 +5469,7 @@
endchar
</CharString>
<CharString name="five.dnom">
- 103 356 rmoveto
+ 12 103 356 rmoveto
4 5 2 4 32 86 2 1 59 vhcurveto
18 47 rlineto
-227 hlineto
@@ -5485,7 +5485,7 @@
endchar
</CharString>
<CharString name="five.numr">
- 103 631 rmoveto
+ 12 103 631 rmoveto
4 5 2 4 32 86 2 1 59 vhcurveto
18 47 rlineto
-227 hlineto
@@ -5501,7 +5501,7 @@
endchar
</CharString>
<CharString name="four">
- -45 285 669 rmoveto
+ 136 285 669 rmoveto
-286 -434 rlineto
-44 284 vlineto
7 -9 rlineto
@@ -5594,7 +5594,7 @@
endchar
</CharString>
<CharString name="four.dnom">
- 263 422 rmoveto
+ 12 263 422 rmoveto
-51 -21 -194 -260 rlineto
-26 192 vlineto
5 -6 rlineto
@@ -5618,7 +5618,7 @@
endchar
</CharString>
<CharString name="four.numr">
- 263 697 rmoveto
+ 12 263 697 rmoveto
-51 -21 -194 -260 rlineto
-26 192 vlineto
5 -6 rlineto
@@ -5642,7 +5642,7 @@
endchar
</CharString>
<CharString name="foursuperior">
- -69 263 697 rmoveto
+ 112 263 697 rmoveto
-51 -21 -194 -260 rlineto
-26 192 vlineto
5 -6 rlineto
@@ -5666,7 +5666,7 @@
endchar
</CharString>
<CharString name="fraction">
- -475 245 704 rmoveto
+ -294 245 704 rmoveto
-11 -1 -433 -680 rlineto
-14 vlineto
31 -13 437 688 -3 11 rlineto
@@ -5678,7 +5678,7 @@
endchar
</CharString>
<CharString name="g">
- -34 365 45 rmoveto
+ 147 365 45 rmoveto
-130 -4 -45 -107 -106 hhcurveto
-36 -46 41 36 -18 hvcurveto
-14 hlineto
@@ -5715,7 +5715,7 @@
endchar
</CharString>
<CharString name="gamma">
- 44 525 465 rmoveto
+ 225 525 465 rmoveto
-37 -95 -82 -160 -65 -120 -8 2 rcurveline
146 -24 231 -149 -77 -42 -34 -77 -50 vhcurveto
16 -12 23 27 rlineto
@@ -5731,7 +5731,7 @@
endchar
</CharString>
<CharString name="germandbls">
- -92 3 428 rmoveto
+ 89 3 428 rmoveto
-4 -22 5 -7 rlineto
68 hlineto
6 -8 rlineto
@@ -5770,17 +5770,17 @@
endchar
</CharString>
<CharString name="grave">
- -25 70 665 rmoveto
+ 156 70 665 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
endchar
</CharString>
<CharString name="gravecomb">
- -545 -130 665 rmoveto
+ -130 665 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
endchar
</CharString>
<CharString name="greater">
- 166 574 254 rmoveto
+ 347 574 254 rmoveto
-9 vlineto
-518 -243 -4 -9 8 -24 10 -5 600 280 rlineto
12 vlineto
@@ -5788,31 +5788,31 @@
endchar
</CharString>
<CharString name="guillemotleft">
- -129 374 36 rmoveto
+ 52 374 36 rmoveto
14 28 -150 170 149 146 -31 46 -192 -188 rlineto
58 -202 rmoveto
14 28 -150 170 149 146 -31 46 -192 -188 rlineto
endchar
</CharString>
<CharString name="guillemotright">
- -129 42 36 rmoveto
+ 52 42 36 rmoveto
210 202 -192 188 -31 -46 149 -146 -150 -170 rlineto
166 -28 rmoveto
210 202 -192 188 -31 -46 149 -146 -150 -170 rlineto
endchar
</CharString>
<CharString name="guilsinglleft">
- -281 12 238 rmoveto
+ -100 12 238 rmoveto
210 -202 14 28 -150 170 149 146 -31 46 rlineto
endchar
</CharString>
<CharString name="guilsinglright">
- -281 252 238 rmoveto
+ -100 252 238 rmoveto
-192 188 -31 -46 149 -146 -150 -170 14 -28 rlineto
endchar
</CharString>
<CharString name="h">
- 47 499 62 rmoveto
+ 228 499 62 rmoveto
-53 9 83 41 hvcurveto
74 vlineto
80 6 70 -30 28 vhcurveto
@@ -7098,14 +7098,14 @@
endchar
</CharString>
<CharString name="hyphen">
- -192 297 276 rmoveto
+ -11 297 276 rmoveto
-235 hlineto
-7 -40 rlineto
236 hlineto
endchar
</CharString>
<CharString name="i">
- -222 206 625 rmoveto
+ -41 206 625 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-14 -233 rmoveto
44 -17 35 -45 -39 -56 -39 -36 -45 vhcurveto
@@ -7121,7 +7121,7 @@
endchar
</CharString>
<CharString name="iacute">
- -287 279 707 rmoveto
+ -106 279 707 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
75 -161 rmoveto
44 -17 35 -45 -39 -56 -39 -36 -45 vhcurveto
@@ -7137,7 +7137,7 @@
endchar
</CharString>
<CharString name="icircumflex">
- -287 -51 562 rmoveto
+ -106 -51 562 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
74 -351 rmoveto
44 -17 35 -45 -39 -56 -39 -36 -45 vhcurveto
@@ -7153,7 +7153,7 @@
endchar
</CharString>
<CharString name="idieresis">
- -287 272 607 rmoveto
+ -106 272 607 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -7171,7 +7171,7 @@
endchar
</CharString>
<CharString name="igrave">
- -287 -70 708 rmoveto
+ -106 -70 708 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
215 -352 rmoveto
44 -17 35 -45 -39 -56 -39 -36 -45 vhcurveto
@@ -7187,7 +7187,7 @@
endchar
</CharString>
<CharString name="iota">
- -267 266 108 rmoveto
+ -86 266 108 rmoveto
-20 -20 -27 -24 -28 hhcurveto
-61 2 86 45 90 2 90 15 89 hvcurveto
-11 10 -81 -25 rlineto
@@ -7197,7 +7197,7 @@
endchar
</CharString>
<CharString name="j">
- -254 159 683 rmoveto
+ -73 159 683 rmoveto
-32 -27 -26 -32 -32 23 -28 33 35 26 30 34 32 -28 22 -30 hvcurveto
-141 -308 rmoveto
13 25 35 20 16 hhcurveto
@@ -7231,7 +7231,7 @@
endchar
</CharString>
<CharString name="k">
- -29 154 266 rmoveto
+ 152 154 266 rmoveto
413 vlineto
3 7 -160 -23 -5 -4 rlineto
-21 vlineto
@@ -7664,7 +7664,7 @@
endchar
</CharString>
<CharString name="kappa">
- -86 399 472 rmoveto
+ 95 399 472 rmoveto
-94 -60 -88 -68 -76 -82 -6 2 rcurveline
1 31 4 106 2 58 -5 6 rcurveline
-79 -26 rlineto
@@ -7768,7 +7768,7 @@
endchar
</CharString>
<CharString name="l">
- -244 289 112 rmoveto
+ -63 289 112 rmoveto
-22 -25 -28 -25 -33 hhcurveto
-60 4 12 91 99 vvcurveto
140 10 140 4 140 vhcurveto
@@ -8077,7 +8077,7 @@
endchar
</CharString>
<CharString name="lambda">
- -64 408 -11 rmoveto
+ 117 408 -11 rmoveto
73 26 rlineto
0 2 4 4 2 -1 1 1 vvcurveto
-101 220 -35 97 -18 52 -24 65 -30 107 -22 44 rrcurveto
@@ -8093,7 +8093,7 @@
endchar
</CharString>
<CharString name="less">
- 165 653 528 rmoveto
+ 346 653 528 rmoveto
-11 3 -601 -275 rlineto
-12 vlineto
600 -280 10 5 11 23 -5 10 -519 241 rlineto
@@ -8102,12 +8102,12 @@
endchar
</CharString>
<CharString name="logicalnot">
- 232 57 409 rmoveto
+ 413 57 409 rmoveto
-40 563 -163 40 203 vlineto
endchar
</CharString>
<CharString name="lscript">
- -165 296 84 rmoveto
+ 16 296 84 rmoveto
-10 -8 -28 -23 -19 hhcurveto
-68 -11 108 91 -7 hvcurveto
80 101 93 133 124 vvcurveto
@@ -8125,7 +8125,7 @@
endchar
</CharString>
<CharString name="m">
- 328 393 -10 rmoveto
+ 509 393 -10 rmoveto
74 28 rlineto
-4 69 -2 71 70 vvcurveto
46 1 46 1 46 vhcurveto
@@ -8158,14 +8158,14 @@
endchar
</CharString>
<CharString name="macron">
- -25 381 595 rmoveto
+ 156 381 595 rmoveto
-235 hlineto
-7 -40 rlineto
236 hlineto
endchar
</CharString>
<CharString name="macroncomb">
- -545 121 595 rmoveto
+ 121 595 rmoveto
-235 hlineto
-7 -40 rlineto
236 hlineto
@@ -8377,14 +8377,14 @@
endchar
</CharString>
<CharString name="minus">
- 347 778 276 rmoveto
+ 528 778 276 rmoveto
-657 hlineto
-7 -40 rlineto
658 hlineto
endchar
</CharString>
<CharString name="mu">
- 137 415 434 rmoveto
+ 318 415 434 rmoveto
2 -57 2 -57 -58 vvcurveto
-21 -2 -73 -1 -48 vhcurveto
-39 -36 -56 -24 -36 hhcurveto
@@ -8411,12 +8411,12 @@
endchar
</CharString>
<CharString name="multiply">
- 58 81 502 rmoveto
+ 239 81 502 rmoveto
-24 -32 215 -215 -214 -215 23 -33 220 219 219 -219 23 33 -214 215 218 217 -25 32 -221 -221 rlineto
endchar
</CharString>
<CharString name="n">
- 86 195 18 rmoveto
+ 267 195 18 rmoveto
-2 40 -1 30 28 vvcurveto
73 3 88 3 72 vhcurveto
29 36 47 32 46 hhcurveto
@@ -8439,7 +8439,7 @@
endchar
</CharString>
<CharString name="nine">
- -45 214 11 rmoveto
+ 136 214 11 rmoveto
13 83 69 97 54 79 rrcurveto
52 77 60 96 85 vvcurveto
107 -71 67 -145 vhcurveto
@@ -8485,7 +8485,7 @@
endchar
</CharString>
<CharString name="nine.dnom">
- 164 7 rmoveto
+ 12 164 7 rmoveto
10 50 46 58 36 47 rrcurveto
36 46 40 59 50 vvcurveto
65 -49 39 -98 vhcurveto
@@ -8501,7 +8501,7 @@
endchar
</CharString>
<CharString name="nine.numr">
- 164 282 rmoveto
+ 12 164 282 rmoveto
10 50 46 58 36 47 rrcurveto
36 46 40 59 50 vvcurveto
65 -49 39 -98 vhcurveto
@@ -8618,7 +8618,7 @@
endchar
</CharString>
<CharString name="ntilde">
- 86 315 620 rmoveto
+ 267 315 620 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -8646,7 +8646,7 @@
endchar
</CharString>
<CharString name="nu">
- -16 531 452 rmoveto
+ 165 531 452 rmoveto
-57 16 -23 -10 -33 -129 -36 -110 -56 -118 rlinecurve
-5 hlineto
-26 119 -37 164 -55 55 rrcurveto
@@ -8661,7 +8661,7 @@
endchar
</CharString>
<CharString name="numbersign">
- 170 10 228 rmoveto
+ 351 10 228 rmoveto
6 -10 rlineto
165 hlineto
-54 -195 4 -14 33 -3 59 212 rlineto
@@ -8690,7 +8690,7 @@
endchar
</CharString>
<CharString name="o">
- -8 431 59 rmoveto
+ 173 431 59 rmoveto
44 49 14 66 65 vvcurveto
112 -51 114 -147 -70 -71 -22 -52 -47 vhcurveto
-48 -52 -7 -74 -69 vvcurveto
@@ -8702,7 +8702,7 @@
endchar
</CharString>
<CharString name="oacute">
- -44 366 694 rmoveto
+ 137 366 694 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
227 -481 rmoveto
44 49 14 66 65 vvcurveto
@@ -8716,7 +8716,7 @@
endchar
</CharString>
<CharString name="ocircumflex">
- -44 88 555 rmoveto
+ 137 88 555 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
174 -677 rmoveto
44 49 14 66 65 vvcurveto
@@ -8730,7 +8730,7 @@
endchar
</CharString>
<CharString name="odieresis">
- -8 429 595 rmoveto
+ 173 429 595 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -8746,7 +8746,7 @@
endchar
</CharString>
<CharString name="oe">
- 270 770 319 rmoveto
+ 451 770 319 rmoveto
11 5 10 4 12 vvcurveto
77 -56 61 -86 -76 -72 -33 -57 -49 vhcurveto
-2 -2 rlineto
@@ -8786,7 +8786,7 @@
endchar
</CharString>
<CharString name="ograve">
- -44 121 695 rmoveto
+ 137 121 695 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
263 -672 rmoveto
44 49 14 66 65 vvcurveto
@@ -8800,7 +8800,7 @@
endchar
</CharString>
<CharString name="omega">
- 226 609 409 rmoveto
+ 407 609 409 rmoveto
48 -41 20 -63 -61 vvcurveto
-87 -45 -96 -94 -94 -14 118 85 65 0 71 9 59 vhcurveto
-6 7 -75 -28 rlineto
@@ -8818,7 +8818,7 @@
endchar
</CharString>
<CharString name="one">
- -45 92 639 rmoveto
+ 136 92 639 rmoveto
-27 83 vlineto
62 -15 -82 -111 -140 -3 -140 -5 -139 hvcurveto
12 -5 84 27 rlineto
@@ -8849,7 +8849,7 @@
endchar
</CharString>
<CharString name="one.dnom">
- 227 427 rmoveto
+ 12 227 427 rmoveto
-146 -44 rlineto
-16 56 vlineto
42 -10 -49 -67 -84 -2 -84 -3 -83 hvcurveto
@@ -8859,7 +8859,7 @@
endchar
</CharString>
<CharString name="one.numr">
- 227 702 rmoveto
+ 12 227 702 rmoveto
-146 -44 rlineto
-16 56 vlineto
42 -10 -49 -67 -84 -2 -84 -3 -83 hvcurveto
@@ -8869,7 +8869,7 @@
endchar
</CharString>
<CharString name="onehalf">
- 245 589 704 rmoveto
+ 426 589 704 rmoveto
-11 -1 -433 -680 rlineto
-14 vlineto
31 -13 437 688 -3 11 rlineto
@@ -8894,7 +8894,7 @@
endchar
</CharString>
<CharString name="onequarter">
- 245 593 704 rmoveto
+ 426 593 704 rmoveto
-11 -1 -433 -680 rlineto
-14 vlineto
31 -13 437 688 -3 11 rlineto
@@ -8929,7 +8929,7 @@
endchar
</CharString>
<CharString name="onesuperior">
- -367 -28 685 rmoveto
+ -186 -28 685 rmoveto
-17 61 vlineto
46 -12 -53 -73 -91 -3 -91 -3 -90 hvcurveto
9 -3 62 17 rlineto
@@ -8974,7 +8974,7 @@
endchar
</CharString>
<CharString name="ordfeminine">
- -179 269 550 rmoveto
+ 2 269 550 rmoveto
15 -23 -33 8 -23 hhcurveto
-123 -52 -90 -92 -59 28 -70 81 43 41 20 21 36 hvcurveto
11 -2 rlineto
@@ -8996,7 +8996,7 @@
endchar
</CharString>
<CharString name="ordmasculine">
- -189 159 261 rmoveto
+ -8 159 261 rmoveto
51 53 13 34 35 hvcurveto
32 32 10 43 42 vvcurveto
43 -13 48 -37 30 vhcurveto
@@ -9010,7 +9010,7 @@
endchar
</CharString>
<CharString name="oslash">
- -25 431 59 rmoveto
+ 156 431 59 rmoveto
44 49 14 66 65 vvcurveto
88 -31 88 -84 35 vhcurveto
28 65 -6 11 -22 5 -10 -2 -29 -68 rlineto
@@ -9034,7 +9034,7 @@
endchar
</CharString>
<CharString name="otilde">
- -8 267 620 rmoveto
+ 173 267 620 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -9051,7 +9051,7 @@
endchar
</CharString>
<CharString name="p">
- 17 203 -206 rmoveto
+ 198 203 -206 rmoveto
-6 52 -3 97 -1 46 10 8 rcurveline
-5 19 20 -3 20 hhcurveto
155 105 162 147 75 -35 92 -102 -69 -61 -31 -43 -54 hvcurveto
@@ -9071,7 +9071,7 @@
endchar
</CharString>
<CharString name="paragraph">
- -92 294 13 rmoveto
+ 89 294 13 rmoveto
62 -19 7 7 rlineto
-2 140 -5 141 141 vvcurveto
59 2 59 3 59 vhcurveto
@@ -9084,7 +9084,7 @@
endchar
</CharString>
<CharString name="parenleft">
- -288 246 -163 rmoveto
+ -107 246 -163 rmoveto
-129 25 -7 243 169 vvcurveto
151 9 254 127 32 vhcurveto
27 vlineto
@@ -9167,7 +9167,7 @@
endchar
</CharString>
<CharString name="parenright">
- -286 11 709 rmoveto
+ -105 11 709 rmoveto
81 -29 23 -94 15 -75 rrcurveto
15 -77 4 -80 -78 vvcurveto
-114 2 -122 -36 -107 vhcurveto
@@ -9250,7 +9250,7 @@
endchar
</CharString>
<CharString name="partialdiff">
- 22 100 548 rmoveto
+ 203 100 548 rmoveto
48 21 34 46 64 hhcurveto
93 52 -94 -100 25 hvcurveto
-4 -4 rlineto
@@ -9282,7 +9282,7 @@
endchar
</CharString>
<CharString name="percent">
- 282 228 795 rmoveto
+ 463 228 795 rmoveto
-85 -43 -83 -71 -92 vvcurveto
-71 44 -77 88 91 94 101 90 76 -73 67 -76 20 vhcurveto
475 -18 rmoveto
@@ -9309,17 +9309,17 @@
endchar
</CharString>
<CharString name="period">
- -273 69 52 rmoveto
+ -92 69 52 rmoveto
-37 30 -30 37 37 30 30 37 37 -30 30 -37 -37 -30 -30 -37 vhcurveto
endchar
</CharString>
<CharString name="periodcentered">
- -273 69 257 rmoveto
+ -92 69 257 rmoveto
-37 30 -30 37 37 30 30 37 37 -30 30 -37 -37 -30 -30 -37 vhcurveto
endchar
</CharString>
<CharString name="phi">
- 99 365 -156 rmoveto
+ 280 365 -156 rmoveto
-7 49 -4 50 49 vvcurveto
9 10 70 2 67 21 50 50 rlinecurve
48 48 21 69 67 vvcurveto
@@ -9348,7 +9348,7 @@
endchar
</CharString>
<CharString name="phi1">
- 97 265 -197 rmoveto
+ 278 265 -197 rmoveto
69 21 rlineto
-5 54 -3 53 54 vvcurveto
7 8 81 11 77 28 58 58 rlinecurve
@@ -9372,7 +9372,7 @@
endchar
</CharString>
<CharString name="pi">
- 51 171 460 rmoveto
+ 232 171 460 rmoveto
-58 -61 -1 -58 -18 hvcurveto
-28 -113 21 -9 18 46 rlineto
59 23 26 10 49 hhcurveto
@@ -9394,7 +9394,7 @@
endchar
</CharString>
<CharString name="pi1">
- 230 356 302 rmoveto
+ 411 356 302 rmoveto
2 -22 3 -22 -22 vvcurveto
-81 -26 -108 -104 -83 -29 104 73 53 17 52 38 37 vhcurveto
-10 21 rlineto
@@ -9413,7 +9413,7 @@
endchar
</CharString>
<CharString name="plus">
- 202 392 586 rmoveto
+ 383 392 586 rmoveto
-40 -6 rlineto
-304 -303 vlineto
-7 -40 rlineto
@@ -9425,7 +9425,7 @@
endchar
</CharString>
<CharString name="plusminus">
- 218 399 586 rmoveto
+ 399 399 586 rmoveto
-40 -6 rlineto
-304 -303 vlineto
-7 -40 rlineto
@@ -9439,7 +9439,7 @@
endchar
</CharString>
<CharString name="psi">
- 159 358 -189 rmoveto
+ 340 358 -189 rmoveto
76 35 rlineto
-7 49 -2 51 50 vvcurveto
7 8 rlineto
@@ -9469,7 +9469,7 @@
endchar
</CharString>
<CharString name="q">
- -37 451 -214 rmoveto
+ 144 451 -214 rmoveto
-8 89 -4 116 122 vvcurveto
132 4 139 10 116 vhcurveto
-21 4 -44 -59 -9 -2 rlineto
@@ -9550,7 +9550,7 @@
endchar
</CharString>
<CharString name="question">
- -192 162 222 rmoveto
+ -11 162 222 rmoveto
-16 12 -11 20 20 vvcurveto
62 122 61 47 79 vhcurveto
17 29 13 27 33 vvcurveto
@@ -9577,7 +9577,7 @@
endchar
</CharString>
<CharString name="questiondown">
- -192 271 442 rmoveto
+ -11 271 442 rmoveto
31 -25 26 -31 -32 -26 -26 -31 -31 26 -26 32 31 25 26 31 vhcurveto
-80 -176 rmoveto
16 -12 11 -20 -20 vvcurveto
@@ -9593,7 +9593,7 @@
endchar
</CharString>
<CharString name="quotedbl">
- -212 118 432 rmoveto
+ -31 118 432 rmoveto
21 191 rlineto
52 vlineto
14 -19 6 -14 -25 -9 -17 -21 -24 2 -25 3 -24 vhcurveto
@@ -9688,7 +9688,7 @@
endchar
</CharString>
<CharString name="quoteleft.1">
- -345 115 532 rmoveto
+ -164 115 532 rmoveto
-9 14 -5 15 16 vvcurveto
35 28 47 21 36 vhcurveto
-14 14 rlineto
@@ -9710,7 +9710,7 @@
endchar
</CharString>
<CharString name="quoteright.1">
- -348 66 395 rmoveto
+ -167 66 395 rmoveto
35 53 54 54 62 vvcurveto
42 -43 89 -28 -16 -32 -26 -15 -7 8 -16 6 -10 vhcurveto
35 -57 rlineto
@@ -9732,7 +9732,7 @@
endchar
</CharString>
<CharString name="quotesingle">
- -335 110 436 rmoveto
+ -154 110 436 rmoveto
26 186 rlineto
2 13 2 16 11 vvcurveto
19 -9 14 -21 -19 -17 -10 -19 -13 1 -12 1 -12 vhcurveto
@@ -9740,7 +9740,7 @@
endchar
</CharString>
<CharString name="r">
- -129 406 388 rmoveto
+ 52 406 388 rmoveto
52 -5 -25 27 -49 hhcurveto
-36 -45 -42 -28 -29 hvcurveto
-10 4 rlineto
@@ -9758,7 +9758,7 @@
endchar
</CharString>
<CharString name="registered">
- 87 45 437 rmoveto
+ 268 45 437 rmoveto
-150 121 -121 150 150 121 121 150 150 -121 121 -150 -150 -121 -121 -150 vhcurveto
25 hmoveto
136 110 110 136 136 110 -110 -136 -136 -110 -110 -136 -136 -110 110 136 vhcurveto
@@ -9808,7 +9808,7 @@
endchar
</CharString>
<CharString name="rho">
- -61 133 -169 rmoveto
+ 120 133 -169 rmoveto
2 4 4 8 1 vvcurveto
-20 78 -4 72 -3 80 7 1 rcurveline
-50 33 45 -28 54 hhcurveto
@@ -9823,14 +9823,14 @@
endchar
</CharString>
<CharString name="ring">
- -45 153 575 rmoveto
+ 136 153 575 rmoveto
-54 43 -43 54 54 43 43 54 54 -43 43 -54 -54 -43 -43 -54 vhcurveto
30 hmoveto
37 30 30 37 37 30 -30 -37 -37 -30 -30 -37 -37 -30 30 37 vhcurveto
endchar
</CharString>
<CharString name="ringcomb">
- -545 -97 575 rmoveto
+ -97 575 rmoveto
-54 43 -43 54 54 43 43 54 54 -43 43 -54 -54 -43 -43 -54 vhcurveto
30 hmoveto
37 30 30 37 37 30 -30 -37 -37 -30 -30 -37 -37 -30 30 37 vhcurveto
@@ -9864,7 +9864,7 @@
endchar
</CharString>
<CharString name="s">
- -166 342 383 rmoveto
+ 15 342 383 rmoveto
61 -3 -58 22 -52 hhcurveto
-83 -90 -53 -90 -66 62 -36 63 -26 hvcurveto
47 -20 52 -27 -51 vvcurveto
@@ -10351,7 +10351,7 @@
endchar
</CharString>
<CharString name="section">
- -80 35 23 rmoveto
+ 101 35 23 rmoveto
-99 9 91 -31 81 hhcurveto
99 81 83 99 26 -5 27 -13 21 hvcurveto
33 37 19 48 51 vvcurveto
@@ -10756,7 +10756,7 @@
endchar
</CharString>
<CharString name="semicolon">
- -352 29 400 rmoveto
+ -171 29 400 rmoveto
-31 26 -26 32 31 25 26 31 31 -25 26 -31 -32 -26 -26 -31 vhcurveto
41 -590 rmoveto
40 49 50 58 61 vvcurveto
@@ -10798,7 +10798,7 @@
endchar
</CharString>
<CharString name="seven">
- -45 494 686 rmoveto
+ 136 494 686 rmoveto
-208 hlineto
-45 -153 2 6 -18 hvcurveto
-21 -79 7 -9 rlineto
@@ -10860,7 +10860,7 @@
endchar
</CharString>
<CharString name="seven.dnom">
- 353 404 rmoveto
+ 12 353 404 rmoveto
8 -140 vlineto
-31 -103 0 4 -13 hvcurveto
-14 -47 4 -5 rlineto
@@ -10872,7 +10872,7 @@
endchar
</CharString>
<CharString name="seven.numr">
- 353 679 rmoveto
+ 12 353 679 rmoveto
8 -140 vlineto
-31 -103 0 4 -13 hvcurveto
-14 -47 4 -5 rlineto
@@ -10918,7 +10918,7 @@
endchar
</CharString>
<CharString name="sigma">
- -2 210 462 rmoveto
+ 179 210 462 rmoveto
-126 -5 -53 -118 -118 vvcurveto
-109 46 -117 129 145 87 101 132 83 -35 70 -67 45 vhcurveto
1 6 66 -8 69 -8 64 -13 rlinecurve
@@ -10930,7 +10930,7 @@
endchar
</CharString>
<CharString name="six">
- -45 471 688 rmoveto
+ 136 471 688 rmoveto
6 -15 -25 6 -15 hhcurveto
-89 -86 -59 -65 -62 hvcurveto
-89 -89 -45 -124 -125 vvcurveto
@@ -10998,7 +10998,7 @@
endchar
</CharString>
<CharString name="six.dnom">
- 338 413 rmoveto
+ 12 338 413 rmoveto
4 -10 -17 3 -10 hhcurveto
-61 -57 -35 -39 -43 hvcurveto
-60 -55 -31 -72 -76 vvcurveto
@@ -11013,7 +11013,7 @@
endchar
</CharString>
<CharString name="six.numr">
- 338 688 rmoveto
+ 12 338 688 rmoveto
4 -10 -17 3 -10 hhcurveto
-61 -57 -35 -39 -43 hvcurveto
-60 -55 -31 -72 -76 vvcurveto
@@ -11028,21 +11028,21 @@
endchar
</CharString>
<CharString name="slash">
- -155 380 720 rmoveto
+ 26 380 720 rmoveto
-11 -2 -386 -888 3 -14 32 -8 389 896 -5 11 rlineto
endchar
</CharString>
<CharString name="softhyphen">
- -545 endchar
+ endchar
</CharString>
<CharString name="space">
-218 endchar
</CharString>
<CharString name="space.1">
- -212 endchar
+ -31 endchar
</CharString>
<CharString name="sterling">
- 104 301 134 rmoveto
+ 285 301 134 rmoveto
0 -50 36 -80 vhcurveto
-3 6 31 16 49 50 17 61 rlinecurve
2 5 1 4 2 5 59 -1 36 -1 89 -3 4 48 rcurveline
@@ -11091,7 +11091,7 @@
endchar
</CharString>
<CharString name="t">
- -160 121 582 rmoveto
+ 21 121 582 rmoveto
2 -26 0 -64 -51 vvcurveto
-6 -6 rlineto
-104 hlineto
@@ -11555,7 +11555,7 @@
endchar
</CharString>
<CharString name="tau">
- -59 130 461 rmoveto
+ 122 130 461 rmoveto
-29 -39 -5 -30 -20 hvcurveto
-13 -20 -22 -102 -4 -19 22 -3 rcurveline
13 34 17 46 13 14 rrcurveto
@@ -11634,7 +11634,7 @@
endchar
</CharString>
<CharString name="theta">
- -45 214 -11 rmoveto
+ 136 214 -11 rmoveto
109 91 99 103 32 hvcurveto
18 56 5 61 60 vvcurveto
134 -16 193 -153 -115 -89 -82 -108 -34 vhcurveto
@@ -11653,7 +11653,7 @@
endchar
</CharString>
<CharString name="theta1">
- -19 114 118 rmoveto
+ 162 114 118 rmoveto
-76 31 -56 73 104 91 94 100 35 vhcurveto
17 50 7 47 53 vvcurveto
4 4 rlineto
@@ -11676,7 +11676,7 @@
endchar
</CharString>
<CharString name="thorn">
- -2 199 -206 rmoveto
+ 179 199 -206 rmoveto
-5 52 0 13 -2 46 10 8 rcurveline
-5 19 17 -3 20 hhcurveto
155 105 162 147 75 -35 92 -102 -69 -59 -31 -43 -54 hvcurveto
@@ -11697,7 +11697,7 @@
endchar
</CharString>
<CharString name="three">
- -45 241 382 rmoveto
+ 136 241 382 rmoveto
87 25 71 65 96 vvcurveto
87 -65 47 -81 -62 -68 -51 -43 -51 vhcurveto
14 -22 rlineto
@@ -11738,7 +11738,7 @@
endchar
</CharString>
<CharString name="three.dnom">
- 182 229 rmoveto
+ 12 182 229 rmoveto
59 15 48 39 58 vvcurveto
53 -46 27 -53 -42 -46 -30 -26 -35 vhcurveto
10 -13 rlineto
@@ -11755,7 +11755,7 @@
endchar
</CharString>
<CharString name="three.numr">
- 182 504 rmoveto
+ 12 182 504 rmoveto
59 15 48 39 58 vvcurveto
53 -46 27 -53 -42 -46 -30 -26 -35 vhcurveto
10 -13 rlineto
@@ -11786,7 +11786,7 @@
endchar
</CharString>
<CharString name="threequarters">
- 245 633 704 rmoveto
+ 426 633 704 rmoveto
-11 -1 -433 -680 rlineto
-14 vlineto
31 -13 437 688 -3 11 rlineto
@@ -11828,7 +11828,7 @@
endchar
</CharString>
<CharString name="threesuperior">
- -246 144 518 rmoveto
+ -65 144 518 rmoveto
64 16 52 43 62 vvcurveto
58 -50 29 -57 -46 -49 -32 -29 -38 vhcurveto
10 -14 rlineto
@@ -11845,7 +11845,7 @@
endchar
</CharString>
<CharString name="tilde">
- -45 249 620 rmoveto
+ 136 249 620 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -11853,7 +11853,7 @@
endchar
</CharString>
<CharString name="tildecomb">
- -545 -2 620 rmoveto
+ -2 620 rmoveto
-26 12 -31 13 -26 -4 -35 -5 -19 -58 -8 -35 14 -5 rcurveline
12 33 10 12 24 3 17 2 18 -4 15 -7 63 -30 rcurveline
20 -9 22 -6 22 3 32 4 20 56 3 29 -13 5 rcurveline
@@ -11879,7 +11879,7 @@
endchar
</CharString>
<CharString name="two">
- -45 467 88 rmoveto
+ 136 467 88 rmoveto
-309 hlineto
-5 -8 1 6 2 1 1 1 1 hvcurveto
84 98 88 101 59 107 rrcurveto
@@ -11945,7 +11945,7 @@
endchar
</CharString>
<CharString name="two.dnom">
- 338 48 rmoveto
+ 12 338 48 rmoveto
-3 5 rlineto
-209 hlineto
-4 -5 0 4 1 0 0 1 1 hvcurveto
@@ -11959,7 +11959,7 @@
endchar
</CharString>
<CharString name="two.numr">
- 338 323 rmoveto
+ 12 338 323 rmoveto
-3 5 rlineto
-209 hlineto
-4 -5 0 4 1 0 0 1 1 hvcurveto
@@ -11985,7 +11985,7 @@
endchar
</CharString>
<CharString name="twosuperior">
- -241 308 327 rmoveto
+ -60 308 327 rmoveto
-227 hlineto
-5 -4 1 4 1 0 0 1 1 hvcurveto
88 90 117 114 91 vvcurveto
@@ -11999,7 +11999,7 @@
endchar
</CharString>
<CharString name="u">
- 93 627 107 rmoveto
+ 274 627 107 rmoveto
-19 -26 -34 -23 -27 hhcurveto
-49 0 55 61 92 4 93 8 93 hvcurveto
-13 11 -77 -35 rlineto
@@ -12018,7 +12018,7 @@
endchar
</CharString>
<CharString name="uacute">
- 88 442 704 rmoveto
+ 269 442 704 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
347 -443 rmoveto
-19 -26 -34 -23 -27 hhcurveto
@@ -12039,7 +12039,7 @@
endchar
</CharString>
<CharString name="ucircumflex">
- 88 154 561 rmoveto
+ 269 154 561 rmoveto
23 -12 143 131 121 -124 39 26 -157 160 rlineto
304 -635 rmoveto
-19 -26 -34 -23 -27 hhcurveto
@@ -12060,7 +12060,7 @@
endchar
</CharString>
<CharString name="udieresis">
- 88 477 606 rmoveto
+ 269 477 606 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -12083,7 +12083,7 @@
endchar
</CharString>
<CharString name="ugrave">
- 88 187 705 rmoveto
+ 269 187 705 rmoveto
162 -154 18 31 -120 157 -13 2 rlineto
393 -634 rmoveto
-19 -26 -34 -23 -27 hhcurveto
@@ -12104,12 +12104,12 @@
endchar
</CharString>
<CharString name="underscore">
- 211 756 -74 rmoveto
+ 392 756 -74 rmoveto
-756 -40 756 hlineto
endchar
</CharString>
<CharString name="upsilon">
- -2 387 398 rmoveto
+ 179 387 398 rmoveto
39 -38 18 -53 -54 vvcurveto
-76 -35 -126 -129 -67 -27 58 72 vhcurveto
69 5 60 66 vvcurveto
@@ -12123,7 +12123,7 @@
endchar
</CharString>
<CharString name="v">
- -52 363 425 rmoveto
+ 129 363 425 rmoveto
-2 -2 -2 -3 -3 vvcurveto
-2 0 -2 1 -1 vhcurveto
17 -32 20 -36 -32 vvcurveto
@@ -12174,7 +12174,7 @@
endchar
</CharString>
<CharString name="w">
- 256 668 416 rmoveto
+ 437 668 416 rmoveto
18 -31 20 -40 -33 vvcurveto
-81 -63 -76 -51 -64 vhcurveto
-4 -3 -8 -6 -4 hhcurveto
@@ -12241,7 +12241,7 @@
endchar
</CharString>
<CharString name="weierstrass">
- 46 182 464 rmoveto
+ 227 182 464 rmoveto
-51 -37 -42 -55 -66 vvcurveto
-43 21 -42 20 -37 vhcurveto
-28 -52 -58 -121 -76 vvcurveto
@@ -12265,7 +12265,7 @@
endchar
</CharString>
<CharString name="x">
- -55 410 462 rmoveto
+ 126 410 462 rmoveto
2 -2 -2 0 -2 hhcurveto
-3 -2 0 -2 -1 hvcurveto
-44 -51 -59 -70 -36 -48 -6 1 rcurveline
@@ -12291,7 +12291,7 @@
endchar
</CharString>
<CharString name="xi">
- -73 411 -137 rmoveto
+ 108 411 -137 rmoveto
32 43 42 54 47 vvcurveto
42 -55 9 -32 vhcurveto
-40 -84 -11 -56 hhcurveto
@@ -12315,7 +12315,7 @@
endchar
</CharString>
<CharString name="y">
- 27 104 -182 rmoveto
+ 208 104 -182 rmoveto
-28 27 53 -23 47 hhcurveto
107 103 54 130 36 hvcurveto
22 95 -15 212 14 204 -12 4 rcurveline
@@ -12340,7 +12340,7 @@
endchar
</CharString>
<CharString name="yacute">
- 7 441 699 rmoveto
+ 188 441 699 rmoveto
-47 36 -13 -2 -120 -157 18 -31 rlineto
-175 -727 rmoveto
-28 27 53 -23 47 hhcurveto
@@ -12367,7 +12367,7 @@
endchar
</CharString>
<CharString name="ydieresis">
- 7 456 605 rmoveto
+ 188 456 605 rmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
-202 hmoveto
30 -20 28 -34 -29 -35 -22 -34 -32 26 -26 32 32 26 23 33 2 vhcurveto
@@ -12471,7 +12471,7 @@
endchar
</CharString>
<CharString name="yen">
- 78 250 299 rmoveto
+ 259 250 299 rmoveto
-21 0 -20 -21 vvcurveto
-149 hlineto
-7 -40 rlineto
@@ -12500,7 +12500,7 @@
endchar
</CharString>
<CharString name="z">
- -82 402 462 rmoveto
+ 99 402 462 rmoveto
-17 -21 -23 -14 -29 -56 -68 31 -51 hhcurveto
-29 -13 -10 -31 -20 hvcurveto
-50 -89 20 -13 rlineto
@@ -12541,7 +12541,7 @@
endchar
</CharString>
<CharString name="zero">
- -45 290 704 rmoveto
+ 136 290 704 rmoveto
-44 -18 -49 -24 -33 -33 rrcurveto
-85 -85 -45 -121 -120 vvcurveto
-115 28 -202 145 184 69 211 159 133 -49 138 -121 77 vhcurveto
@@ -12599,7 +12599,7 @@
endchar
</CharString>
<CharString name="zero.dnom">
- 215 422 rmoveto
+ 12 215 422 rmoveto
-29 -10 -34 -15 -22 -20 rrcurveto
-58 -51 -31 -72 -72 vvcurveto
-71 21 -119 97 122 50 123 99 79 -33 84 -83 45 vhcurveto
@@ -12612,7 +12612,7 @@
endchar
</CharString>
<CharString name="zero.numr">
- 215 697 rmoveto
+ 12 215 697 rmoveto
-29 -10 -34 -15 -22 -20 rrcurveto
-58 -51 -31 -72 -72 vvcurveto
-71 21 -119 97 122 50 123 99 79 -33 84 -83 45 vhcurveto
@@ -12625,7 +12625,7 @@
endchar
</CharString>
<CharString name="zeta">
- -124 363 -136 rmoveto
+ 57 363 -136 rmoveto
32 40 40 54 48 vvcurveto
36 -39 11 -41 vhcurveto
-24 -37 -6 -40 hhcurveto
diff --git a/Tests/merge/merge_test.py b/Tests/merge/merge_test.py
index 5ff12d1c..5558a2e3 100644
--- a/Tests/merge/merge_test.py
+++ b/Tests/merge/merge_test.py
@@ -16,222 +16,237 @@ import pytest
class MergeIntegrationTest(unittest.TestCase):
- def setUp(self):
- self.tempdir = None
- self.num_tempfiles = 0
-
- def tearDown(self):
- if self.tempdir:
- shutil.rmtree(self.tempdir)
-
- @staticmethod
- def getpath(testfile):
- path, _ = os.path.split(__file__)
- return os.path.join(path, "data", testfile)
-
- def temp_path(self, suffix):
- if not self.tempdir:
- self.tempdir = tempfile.mkdtemp()
- self.num_tempfiles += 1
- return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
-
- IGNORED_LINES_RE = re.compile(
- "^(<ttFont | <(checkSumAdjustment|created|modified) ).*"
- )
- def read_ttx(self, path):
- lines = []
- with open(path, "r", encoding="utf-8") as ttx:
- for line in ttx.readlines():
- # Elide lines with data that often change.
- if self.IGNORED_LINES_RE.match(line):
- lines.append("\n")
- else:
- lines.append(line.rstrip() + "\n")
- return lines
-
- 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)
- expected = self.read_ttx(expected_ttx)
- if actual != expected:
- for line in difflib.unified_diff(
- expected, actual, fromfile=expected_ttx, tofile=path):
- sys.stdout.write(line)
- self.fail("TTX output is different from expected")
-
- def compile_font(self, path, suffix):
- savepath = self.temp_path(suffix=suffix)
- font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- font.importXML(path)
- font.save(savepath, reorderTables=None)
- return font, savepath
-
-# -----
-# Tests
-# -----
-
- def test_merge_cff(self):
- _, fontpath1 = self.compile_font(self.getpath("CFFFont1.ttx"), ".otf")
- _, fontpath2 = self.compile_font(self.getpath("CFFFont2.ttx"), ".otf")
- mergedpath = self.temp_path(".otf")
- merge_main([fontpath1, fontpath2, "--output-file=%s" % mergedpath])
- mergedfont = ttLib.TTFont(mergedpath)
- self.expect_ttx(mergedfont, self.getpath("CFFFont_expected.ttx"))
+ def setUp(self):
+ self.tempdir = None
+ self.num_tempfiles = 0
+
+ def tearDown(self):
+ if self.tempdir:
+ shutil.rmtree(self.tempdir)
+
+ @staticmethod
+ def getpath(testfile):
+ path, _ = os.path.split(__file__)
+ return os.path.join(path, "data", testfile)
+
+ def temp_path(self, suffix):
+ if not self.tempdir:
+ self.tempdir = tempfile.mkdtemp()
+ self.num_tempfiles += 1
+ return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
+
+ IGNORED_LINES_RE = re.compile(
+ "^(<ttFont | <(checkSumAdjustment|created|modified) ).*"
+ )
+
+ def read_ttx(self, path):
+ lines = []
+ with open(path, "r", encoding="utf-8") as ttx:
+ for line in ttx.readlines():
+ # Elide lines with data that often change.
+ if self.IGNORED_LINES_RE.match(line):
+ lines.append("\n")
+ else:
+ lines.append(line.rstrip() + "\n")
+ return lines
+
+ 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)
+ expected = self.read_ttx(expected_ttx)
+ if actual != expected:
+ for line in difflib.unified_diff(
+ expected, actual, fromfile=expected_ttx, tofile=path
+ ):
+ sys.stdout.write(line)
+ self.fail("TTX output is different from expected")
+
+ def compile_font(self, path, suffix):
+ savepath = self.temp_path(suffix=suffix)
+ font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ font.importXML(path)
+ font.save(savepath, reorderTables=None)
+ return font, savepath
+
+ # -----
+ # Tests
+ # -----
+
+ def test_merge_cff(self):
+ _, fontpath1 = self.compile_font(self.getpath("CFFFont1.ttx"), ".otf")
+ _, fontpath2 = self.compile_font(self.getpath("CFFFont2.ttx"), ".otf")
+ mergedpath = self.temp_path(".otf")
+ merge_main([fontpath1, fontpath2, "--output-file=%s" % mergedpath])
+ mergedfont = ttLib.TTFont(mergedpath)
+ self.expect_ttx(mergedfont, self.getpath("CFFFont_expected.ttx"))
class gaspMergeUnitTest(unittest.TestCase):
- def setUp(self):
- self.merger = Merger()
+ def setUp(self):
+ self.merger = Merger()
- self.table1 = ttLib.newTable('gasp')
- self.table1.version = 1
- self.table1.gaspRange = {
- 0x8: 0xA ,
- 0x10: 0x5,
- }
+ self.table1 = ttLib.newTable("gasp")
+ self.table1.version = 1
+ self.table1.gaspRange = {
+ 0x8: 0xA,
+ 0x10: 0x5,
+ }
- self.table2 = ttLib.newTable('gasp')
- self.table2.version = 1
- self.table2.gaspRange = {
- 0x6: 0xB ,
- 0xFF: 0x4,
- }
+ self.table2 = ttLib.newTable("gasp")
+ self.table2.version = 1
+ self.table2.gaspRange = {
+ 0x6: 0xB,
+ 0xFF: 0x4,
+ }
- self.result = ttLib.newTable('gasp')
+ self.result = ttLib.newTable("gasp")
- def test_gasp_merge_basic(self):
- result = self.result.merge(self.merger, [self.table1, self.table2])
- self.assertEqual(result, self.table1)
+ def test_gasp_merge_basic(self):
+ result = self.result.merge(self.merger, [self.table1, self.table2])
+ self.assertEqual(result, self.table1)
- result = self.result.merge(self.merger, [self.table2, self.table1])
- self.assertEqual(result, self.table2)
+ result = self.result.merge(self.merger, [self.table2, self.table1])
+ self.assertEqual(result, self.table2)
- def test_gasp_merge_notImplemented(self):
- result = self.result.merge(self.merger, [NotImplemented, self.table1])
- self.assertEqual(result, NotImplemented)
+ def test_gasp_merge_notImplemented(self):
+ result = self.result.merge(self.merger, [NotImplemented, self.table1])
+ self.assertEqual(result, NotImplemented)
- result = self.result.merge(self.merger, [self.table1, NotImplemented])
- self.assertEqual(result, self.table1)
+ result = self.result.merge(self.merger, [self.table1, NotImplemented])
+ self.assertEqual(result, self.table1)
class CmapMergeUnitTest(unittest.TestCase):
- def setUp(self):
- self.merger = Merger()
- self.table1 = ttLib.newTable('cmap')
- self.table2 = ttLib.newTable('cmap')
- self.mergedTable = ttLib.newTable('cmap')
- pass
-
- def tearDown(self):
- pass
-
-
- def makeSubtable(self, format, platformID, platEncID, cmap):
- module = ttLib.getTableModule('cmap')
- subtable = module.cmap_classes[format](format)
- (subtable.platformID,
- subtable.platEncID,
- subtable.language,
- subtable.cmap) = (platformID, platEncID, 0, cmap)
- return subtable
-
- # 4-3-1 table merged with 12-3-10 table with no dupes with codepoints outside BMP
- def test_cmap_merge_no_dupes(self):
- table1 = self.table1
- table2 = self.table2
- mergedTable = self.mergedTable
-
- cmap1 = {0x2603: 'SNOWMAN'}
- table1.tables = [self.makeSubtable(4,3,1, cmap1)]
-
- cmap2 = {0x26C4: 'SNOWMAN WITHOUT SNOW'}
- cmap2Extended = {0x1F93C: 'WRESTLERS'}
- cmap2Extended.update(cmap2)
- table2.tables = [self.makeSubtable(4,3,1, cmap2), self.makeSubtable(12,3,10, cmap2Extended)]
-
- self.merger.alternateGlyphsPerFont = [{},{}]
- mergedTable.merge(self.merger, [table1, table2])
-
- expectedCmap = cmap2.copy()
- expectedCmap.update(cmap1)
- expectedCmapExtended = cmap2Extended.copy()
- expectedCmapExtended.update(cmap1)
- self.assertEqual(mergedTable.numSubTables, 2)
- self.assertEqual([(table.format, table.platformID, table.platEncID, table.language) for table in mergedTable.tables],
- [(4,3,1,0),(12,3,10,0)])
- self.assertEqual(mergedTable.tables[0].cmap, expectedCmap)
- self.assertEqual(mergedTable.tables[1].cmap, expectedCmapExtended)
-
- # Tests Issue #322
- def test_cmap_merge_three_dupes(self):
- table1 = self.table1
- table2 = self.table2
- mergedTable = self.mergedTable
-
- cmap1 = {0x20: 'space#0', 0xA0: 'space#0'}
- table1.tables = [self.makeSubtable(4,3,1,cmap1)]
- cmap2 = {0x20: 'space#1', 0xA0: 'uni00A0#1'}
- table2.tables = [self.makeSubtable(4,3,1,cmap2)]
-
- self.merger.duplicateGlyphsPerFont = [{},{}]
- mergedTable.merge(self.merger, [table1, table2])
-
- expectedCmap = cmap1.copy()
- self.assertEqual(mergedTable.numSubTables, 1)
- table = mergedTable.tables[0]
- self.assertEqual((table.format, table.platformID, table.platEncID, table.language), (4,3,1,0))
- self.assertEqual(table.cmap, expectedCmap)
- self.assertEqual(self.merger.duplicateGlyphsPerFont, [{}, {'space#0': 'space#1'}])
+ def setUp(self):
+ self.merger = Merger()
+ self.table1 = ttLib.newTable("cmap")
+ self.table2 = ttLib.newTable("cmap")
+ self.mergedTable = ttLib.newTable("cmap")
+ pass
+
+ def tearDown(self):
+ pass
+
+ def makeSubtable(self, format, platformID, platEncID, cmap):
+ module = ttLib.getTableModule("cmap")
+ subtable = module.cmap_classes[format](format)
+ (subtable.platformID, subtable.platEncID, subtable.language, subtable.cmap) = (
+ platformID,
+ platEncID,
+ 0,
+ cmap,
+ )
+ return subtable
+
+ # 4-3-1 table merged with 12-3-10 table with no dupes with codepoints outside BMP
+ def test_cmap_merge_no_dupes(self):
+ table1 = self.table1
+ table2 = self.table2
+ mergedTable = self.mergedTable
+
+ cmap1 = {0x2603: "SNOWMAN"}
+ table1.tables = [self.makeSubtable(4, 3, 1, cmap1)]
+
+ cmap2 = {0x26C4: "SNOWMAN WITHOUT SNOW"}
+ cmap2Extended = {0x1F93C: "WRESTLERS"}
+ cmap2Extended.update(cmap2)
+ table2.tables = [
+ self.makeSubtable(4, 3, 1, cmap2),
+ self.makeSubtable(12, 3, 10, cmap2Extended),
+ ]
+
+ self.merger.alternateGlyphsPerFont = [{}, {}]
+ mergedTable.merge(self.merger, [table1, table2])
+
+ expectedCmap = cmap2.copy()
+ expectedCmap.update(cmap1)
+ expectedCmapExtended = cmap2Extended.copy()
+ expectedCmapExtended.update(cmap1)
+ self.assertEqual(mergedTable.numSubTables, 2)
+ self.assertEqual(
+ [
+ (table.format, table.platformID, table.platEncID, table.language)
+ for table in mergedTable.tables
+ ],
+ [(4, 3, 1, 0), (12, 3, 10, 0)],
+ )
+ self.assertEqual(mergedTable.tables[0].cmap, expectedCmap)
+ self.assertEqual(mergedTable.tables[1].cmap, expectedCmapExtended)
+
+ # Tests Issue #322
+ def test_cmap_merge_three_dupes(self):
+ table1 = self.table1
+ table2 = self.table2
+ mergedTable = self.mergedTable
+
+ cmap1 = {0x20: "space#0", 0xA0: "space#0"}
+ table1.tables = [self.makeSubtable(4, 3, 1, cmap1)]
+ cmap2 = {0x20: "space#1", 0xA0: "uni00A0#1"}
+ table2.tables = [self.makeSubtable(4, 3, 1, cmap2)]
+
+ self.merger.duplicateGlyphsPerFont = [{}, {}]
+ mergedTable.merge(self.merger, [table1, table2])
+
+ expectedCmap = cmap1.copy()
+ self.assertEqual(mergedTable.numSubTables, 1)
+ table = mergedTable.tables[0]
+ self.assertEqual(
+ (table.format, table.platformID, table.platEncID, table.language),
+ (4, 3, 1, 0),
+ )
+ self.assertEqual(table.cmap, expectedCmap)
+ self.assertEqual(
+ self.merger.duplicateGlyphsPerFont, [{}, {"space#0": "space#1"}]
+ )
def _compile(ttFont):
- buf = io.BytesIO()
- ttFont.save(buf)
- buf.seek(0)
- return buf
+ 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"}
+ 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)
+ 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)
+ 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)
+ 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))
-)
+@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)
+ # 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())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/misc/arrayTools_test.py b/Tests/misc/arrayTools_test.py
index 45b186fe..c8de7bda 100644
--- a/Tests/misc/arrayTools_test.py
+++ b/Tests/misc/arrayTools_test.py
@@ -1,24 +1,38 @@
from fontTools.misc.arrayTools import (
- calcBounds, calcIntBounds, updateBounds, pointInRect, pointsInRect,
- vectorLength, asInt16, normRect, scaleRect, offsetRect, insetRect,
- sectRect, unionRect, rectCenter, intRect)
+ calcBounds,
+ calcIntBounds,
+ updateBounds,
+ pointInRect,
+ pointsInRect,
+ vectorLength,
+ asInt16,
+ normRect,
+ scaleRect,
+ offsetRect,
+ insetRect,
+ sectRect,
+ unionRect,
+ rectCenter,
+ intRect,
+)
import math
def test_calcBounds():
assert calcBounds([]) == (0, 0, 0, 0)
- assert calcBounds(
- [(0, 40), (0, 100), (50, 50), (80, 10)]) == (0, 10, 80, 100)
+ assert calcBounds([(0, 40), (0, 100), (50, 50), (80, 10)]) == (0, 10, 80, 100)
def test_calcIntBounds():
- assert calcIntBounds(
- [(0.1, 40.1), (0.1, 100.1), (49.9, 49.9), (78.5, 9.5)]
- ) == (0, 10, 79, 100)
+ assert calcIntBounds([(0.1, 40.1), (0.1, 100.1), (49.9, 49.9), (78.5, 9.5)]) == (
+ 0,
+ 10,
+ 79,
+ 100,
+ )
assert calcIntBounds(
- [(0.1, 40.1), (0.1, 100.1), (49.9, 49.9), (78.5, 9.5)],
- round=round
+ [(0.1, 40.1), (0.1, 100.1), (49.9, 49.9), (78.5, 9.5)], round=round
) == (0, 10, 78, 100)
@@ -36,8 +50,8 @@ def test_pointInRect():
def test_pointsInRect():
assert pointsInRect([], (0, 0, 100, 100)) == []
assert pointsInRect(
- [(50, 50), (0, 0), (100, 100), (101, 100)],
- (0, 0, 100, 100)) == [True, True, True, False]
+ [(50, 50), (0, 0), (100, 100), (101, 100)], (0, 0, 100, 100)
+ ) == [True, True, True, False]
def test_vectorLength():
diff --git a/Tests/misc/bezierTools_test.py b/Tests/misc/bezierTools_test.py
index da73375d..8a3e2ecd 100644
--- a/Tests/misc/bezierTools_test.py
+++ b/Tests/misc/bezierTools_test.py
@@ -1,50 +1,67 @@
import fontTools.misc.bezierTools as bezierTools
from fontTools.misc.bezierTools import (
- calcQuadraticBounds, calcCubicBounds, curveLineIntersections,
- segmentPointAtT, splitLine, splitQuadratic, splitCubic, splitQuadraticAtT,
- splitCubicAtT, solveCubic)
+ calcQuadraticBounds,
+ calcQuadraticArcLength,
+ calcCubicBounds,
+ curveLineIntersections,
+ segmentPointAtT,
+ splitLine,
+ splitQuadratic,
+ splitCubic,
+ splitQuadraticAtT,
+ splitCubicAtT,
+ solveCubic,
+)
import pytest
def test_calcQuadraticBounds():
- assert calcQuadraticBounds(
- (0, 0), (50, 100), (100, 0)) == (0, 0, 100, 50.0)
- assert calcQuadraticBounds(
- (0, 0), (100, 0), (100, 100)) == (0.0, 0.0, 100, 100)
+ assert calcQuadraticBounds((0, 0), (50, 100), (100, 0)) == (0, 0, 100, 50.0)
+ assert calcQuadraticBounds((0, 0), (100, 0), (100, 100)) == (0.0, 0.0, 100, 100)
def test_calcCubicBounds():
- assert calcCubicBounds(
- (0, 0), (25, 100), (75, 100), (100, 0)) == ((0, 0, 100, 75.0))
- assert calcCubicBounds(
- (0, 0), (50, 0), (100, 50), (100, 100)) == (0.0, 0.0, 100, 100)
- assert calcCubicBounds(
- (50, 0), (0, 100), (100, 100), (50, 0)
- ) == pytest.approx((35.566243, 0.000000, 64.433757, 75.000000))
+ assert calcCubicBounds((0, 0), (25, 100), (75, 100), (100, 0)) == (
+ (0, 0, 100, 75.0)
+ )
+ assert calcCubicBounds((0, 0), (50, 0), (100, 50), (100, 100)) == (
+ 0.0,
+ 0.0,
+ 100,
+ 100,
+ )
+ assert calcCubicBounds((50, 0), (0, 100), (100, 100), (50, 0)) == pytest.approx(
+ (35.566243, 0.000000, 64.433757, 75.000000)
+ )
def test_splitLine():
- assert splitLine(
- (0, 0), (100, 100), where=50, isHorizontal=True
- ) == [((0, 0), (50.0, 50.0)), ((50.0, 50.0), (100, 100))]
- assert splitLine(
- (0, 0), (100, 100), where=100, isHorizontal=True
- ) == [((0, 0), (100, 100))]
- assert splitLine(
- (0, 0), (100, 100), where=0, isHorizontal=True
- ) == [((0, 0), (0, 0)), ((0, 0), (100, 100))]
- assert splitLine(
- (0, 0), (100, 100), where=0, isHorizontal=False
- ) == [((0, 0), (0, 0)), ((0, 0), (100, 100))]
- assert splitLine(
- (100, 0), (0, 0), where=50, isHorizontal=False
- ) == [((100, 0), (50, 0)), ((50, 0), (0, 0))]
- assert splitLine(
- (0, 100), (0, 0), where=50, isHorizontal=True
- ) == [((0, 100), (0, 50)), ((0, 50), (0, 0))]
- assert splitLine(
- (0, 100), (100, 100), where=50, isHorizontal=True
- ) == [((0, 100), (100, 100))]
+ assert splitLine((0, 0), (100, 100), where=50, isHorizontal=True) == [
+ ((0, 0), (50.0, 50.0)),
+ ((50.0, 50.0), (100, 100)),
+ ]
+ assert splitLine((0, 0), (100, 100), where=100, isHorizontal=True) == [
+ ((0, 0), (100, 100))
+ ]
+ assert splitLine((0, 0), (100, 100), where=0, isHorizontal=True) == [
+ ((0, 0), (0, 0)),
+ ((0, 0), (100, 100)),
+ ]
+ assert splitLine((0, 0), (100, 100), where=0, isHorizontal=False) == [
+ ((0, 0), (0, 0)),
+ ((0, 0), (100, 100)),
+ ]
+ assert splitLine((100, 0), (0, 0), where=50, isHorizontal=False) == [
+ ((100, 0), (50, 0)),
+ ((50, 0), (0, 0)),
+ ]
+ assert splitLine((0, 100), (0, 0), where=50, isHorizontal=True) == [
+ ((0, 100), (0, 50)),
+ ((0, 50), (0, 0)),
+ ]
+ assert splitLine((0, 100), (100, 100), where=50, isHorizontal=True) == [
+ ((0, 100), (100, 100))
+ ]
def assert_curves_approx_equal(actual_curves, expected_curves):
@@ -61,24 +78,24 @@ def test_splitQuadratic():
) == [((0, 0), (50, 100), (100, 0))]
assert splitQuadratic(
(0, 0), (50, 100), (100, 0), where=50, isHorizontal=False
- ) == [((0, 0), (25, 50), (50, 50)),
- ((50, 50), (75, 50), (100, 0))]
+ ) == [((0, 0), (25, 50), (50, 50)), ((50, 50), (75, 50), (100, 0))]
assert splitQuadratic(
(0, 0), (50, 100), (100, 0), where=25, isHorizontal=False
- ) == [((0, 0), (12.5, 25), (25, 37.5)),
- ((25, 37.5), (62.5, 75), (100, 0))]
+ ) == [((0, 0), (12.5, 25), (25, 37.5)), ((25, 37.5), (62.5, 75), (100, 0))]
assert_curves_approx_equal(
- splitQuadratic(
- (0, 0), (50, 100), (100, 0), where=25, isHorizontal=True),
- [((0, 0), (7.32233, 14.64466), (14.64466, 25)),
- ((14.64466, 25), (50, 75), (85.3553, 25)),
- ((85.3553, 25), (92.6777, 14.64466), (100, -7.10543e-15))])
+ splitQuadratic((0, 0), (50, 100), (100, 0), where=25, isHorizontal=True),
+ [
+ ((0, 0), (7.32233, 14.64466), (14.64466, 25)),
+ ((14.64466, 25), (50, 75), (85.3553, 25)),
+ ((85.3553, 25), (92.6777, 14.64466), (100, -7.10543e-15)),
+ ],
+ )
# XXX I'm not at all sure if the following behavior is desirable
- assert splitQuadratic(
- (0, 0), (50, 100), (100, 0), where=50, isHorizontal=True
- ) == [((0, 0), (25, 50), (50, 50)),
- ((50, 50), (50, 50), (50, 50)),
- ((50, 50), (75, 50), (100, 0))]
+ assert splitQuadratic((0, 0), (50, 100), (100, 0), where=50, isHorizontal=True) == [
+ ((0, 0), (25, 50), (50, 50)),
+ ((50, 50), (50, 50), (50, 50)),
+ ((50, 50), (75, 50), (100, 0)),
+ ]
def test_splitCubic():
@@ -87,41 +104,42 @@ def test_splitCubic():
) == [((0, 0), (25, 100), (75, 100), (100, 0))]
assert splitCubic(
(0, 0), (25, 100), (75, 100), (100, 0), where=50, isHorizontal=False
- ) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)),
- ((50, 75), (68.75, 75), (87.5, 50), (100, 0))]
+ ) == [
+ ((0, 0), (12.5, 50), (31.25, 75), (50, 75)),
+ ((50, 75), (68.75, 75), (87.5, 50), (100, 0)),
+ ]
assert_curves_approx_equal(
- splitCubic(
- (0, 0), (25, 100), (75, 100), (100, 0), where=25,
- isHorizontal=True),
- [((0, 0), (2.293792, 9.17517), (4.798045, 17.5085), (7.47414, 25)),
- ((7.47414, 25), (31.2886, 91.6667), (68.7114, 91.6667),
- (92.5259, 25)),
- ((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517),
- (100, 1.77636e-15))])
+ splitCubic((0, 0), (25, 100), (75, 100), (100, 0), where=25, isHorizontal=True),
+ [
+ ((0, 0), (2.293792, 9.17517), (4.798045, 17.5085), (7.47414, 25)),
+ ((7.47414, 25), (31.2886, 91.6667), (68.7114, 91.6667), (92.5259, 25)),
+ ((92.5259, 25), (95.202, 17.5085), (97.7062, 9.17517), (100, 1.77636e-15)),
+ ],
+ )
def test_splitQuadraticAtT():
- assert splitQuadraticAtT(
- (0, 0), (50, 100), (100, 0), 0.5
- ) == [((0, 0), (25, 50), (50, 50)),
- ((50, 50), (75, 50), (100, 0))]
- assert splitQuadraticAtT(
- (0, 0), (50, 100), (100, 0), 0.5, 0.75
- ) == [((0, 0), (25, 50), (50, 50)),
- ((50, 50), (62.5, 50), (75, 37.5)),
- ((75, 37.5), (87.5, 25), (100, 0))]
+ assert splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5) == [
+ ((0, 0), (25, 50), (50, 50)),
+ ((50, 50), (75, 50), (100, 0)),
+ ]
+ assert splitQuadraticAtT((0, 0), (50, 100), (100, 0), 0.5, 0.75) == [
+ ((0, 0), (25, 50), (50, 50)),
+ ((50, 50), (62.5, 50), (75, 37.5)),
+ ((75, 37.5), (87.5, 25), (100, 0)),
+ ]
def test_splitCubicAtT():
- assert splitCubicAtT(
- (0, 0), (25, 100), (75, 100), (100, 0), 0.5
- ) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)),
- ((50, 75), (68.75, 75), (87.5, 50), (100, 0))]
- assert splitCubicAtT(
- (0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75
- ) == [((0, 0), (12.5, 50), (31.25, 75), (50, 75)),
- ((50, 75), (59.375, 75), (68.75, 68.75), (77.34375, 56.25)),
- ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0))]
+ assert splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5) == [
+ ((0, 0), (12.5, 50), (31.25, 75), (50, 75)),
+ ((50, 75), (68.75, 75), (87.5, 50), (100, 0)),
+ ]
+ assert splitCubicAtT((0, 0), (25, 100), (75, 100), (100, 0), 0.5, 0.75) == [
+ ((0, 0), (12.5, 50), (31.25, 75), (50, 75)),
+ ((50, 75), (59.375, 75), (68.75, 68.75), (77.34375, 56.25)),
+ ((77.34375, 56.25), (85.9375, 43.75), (93.75, 25), (100, 0)),
+ ]
def test_solveCubic():
@@ -164,3 +182,10 @@ def test_intersections_straight_line():
e = (110, 0)
pt = (109.05194805194802, 0.0)
assert bezierTools._line_t_of_pt(s, e, pt) == pytest.approx(0.98958184)
+
+
+def test_calcQuadraticArcLength():
+ # https://github.com/fonttools/fonttools/issues/3287
+ assert calcQuadraticArcLength(
+ (210, 333), (289, 333), (326.5, 290.5)
+ ) == pytest.approx(127.9225)
diff --git a/Tests/misc/classifyTools_test.py b/Tests/misc/classifyTools_test.py
index 72a97523..8f2b9d61 100644
--- a/Tests/misc/classifyTools_test.py
+++ b/Tests/misc/classifyTools_test.py
@@ -6,23 +6,28 @@ def test_classify():
assert classify([[]]) == ([], {})
assert classify([[], []]) == ([], {})
assert classify([[1]]) == ([{1}], {1: {1}})
- assert classify([[1,2]]) == ([{1, 2}], {1: {1, 2}, 2: {1, 2}})
- assert classify([[1],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
- assert classify([[1,2],[2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
- assert classify([[1,2],[2,4]]) == (
- [{1}, {2}, {4}], {1: {1}, 2: {2}, 4: {4}})
- assert classify([[1,2],[2,4,5]]) == (
- [{4, 5}, {1}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
- assert classify([[1,2],[2,4,5]], sort=False) == (
- [{1}, {4, 5}, {2}], {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}})
- assert classify([[1,2,9],[2,4,5]], sort=False) == (
+ assert classify([[1, 2]]) == ([{1, 2}], {1: {1, 2}, 2: {1, 2}})
+ assert classify([[1], [2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
+ assert classify([[1, 2], [2]]) == ([{1}, {2}], {1: {1}, 2: {2}})
+ assert classify([[1, 2], [2, 4]]) == ([{1}, {2}, {4}], {1: {1}, 2: {2}, 4: {4}})
+ assert classify([[1, 2], [2, 4, 5]]) == (
+ [{4, 5}, {1}, {2}],
+ {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}},
+ )
+ assert classify([[1, 2], [2, 4, 5]], sort=False) == (
+ [{1}, {4, 5}, {2}],
+ {1: {1}, 2: {2}, 4: {4, 5}, 5: {4, 5}},
+ )
+ assert classify([[1, 2, 9], [2, 4, 5]], sort=False) == (
[{1, 9}, {4, 5}, {2}],
- {1: {1, 9}, 2: {2}, 4: {4, 5}, 5: {4, 5}, 9: {1, 9}})
- assert classify([[1,2,9,15],[2,4,5]], sort=False) == (
+ {1: {1, 9}, 2: {2}, 4: {4, 5}, 5: {4, 5}, 9: {1, 9}},
+ )
+ assert classify([[1, 2, 9, 15], [2, 4, 5]], sort=False) == (
[{1, 9, 15}, {4, 5}, {2}],
- {1: {1, 9, 15}, 2: {2}, 4: {4, 5}, 5: {4, 5}, 9: {1, 9, 15},
- 15: {1, 9, 15}})
- classes, mapping = classify([[1,2,9,15],[2,4,5],[15,5]], sort=False)
+ {1: {1, 9, 15}, 2: {2}, 4: {4, 5}, 5: {4, 5}, 9: {1, 9, 15}, 15: {1, 9, 15}},
+ )
+ classes, mapping = classify([[1, 2, 9, 15], [2, 4, 5], [15, 5]], sort=False)
assert set([frozenset(c) for c in classes]) == set(
- [frozenset(s) for s in ({1, 9}, {4}, {2}, {5}, {15})])
+ [frozenset(s) for s in ({1, 9}, {4}, {2}, {5}, {15})]
+ )
assert mapping == {1: {1, 9}, 2: {2}, 4: {4}, 5: {5}, 9: {1, 9}, 15: {15}}
diff --git a/Tests/misc/eexec_test.py b/Tests/misc/eexec_test.py
index f72760a7..b02bbfe3 100644
--- a/Tests/misc/eexec_test.py
+++ b/Tests/misc/eexec_test.py
@@ -4,12 +4,12 @@ from fontTools.misc.eexec import decrypt, encrypt
def test_decrypt():
testStr = b"\0\0asdadads asds\265"
decryptedStr, R = decrypt(testStr, 12321)
- assert decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
+ assert decryptedStr == b"0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1"
assert R == 36142
def test_encrypt():
- testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
+ testStr = b"0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1"
encryptedStr, R = encrypt(testStr, 12321)
assert encryptedStr == b"\0\0asdadads asds\265"
assert R == 36142
diff --git a/Tests/misc/encodingTools_test.py b/Tests/misc/encodingTools_test.py
index 1a131f61..7c4e1435 100644
--- a/Tests/misc/encodingTools_test.py
+++ b/Tests/misc/encodingTools_test.py
@@ -1,30 +1,33 @@
import unittest
from fontTools.misc.encodingTools import getEncoding
-class EncodingTest(unittest.TestCase):
- def test_encoding_unicode(self):
+class EncodingTest(unittest.TestCase):
+ def test_encoding_unicode(self):
+ self.assertEqual(
+ getEncoding(3, 0, None), "utf_16_be"
+ ) # MS Symbol is Unicode as well
+ self.assertEqual(getEncoding(3, 1, None), "utf_16_be")
+ self.assertEqual(getEncoding(3, 10, None), "utf_16_be")
+ self.assertEqual(getEncoding(0, 3, None), "utf_16_be")
- self.assertEqual(getEncoding(3, 0, None), "utf_16_be") # MS Symbol is Unicode as well
- self.assertEqual(getEncoding(3, 1, None), "utf_16_be")
- self.assertEqual(getEncoding(3, 10, None), "utf_16_be")
- self.assertEqual(getEncoding(0, 3, None), "utf_16_be")
+ def test_encoding_macroman_misc(self):
+ self.assertEqual(getEncoding(1, 0, 17), "mac_turkish")
+ self.assertEqual(getEncoding(1, 0, 37), "mac_romanian")
+ self.assertEqual(getEncoding(1, 0, 45), "mac_roman")
- def test_encoding_macroman_misc(self):
- self.assertEqual(getEncoding(1, 0, 17), "mac_turkish")
- self.assertEqual(getEncoding(1, 0, 37), "mac_romanian")
- self.assertEqual(getEncoding(1, 0, 45), "mac_roman")
+ def test_extended_mac_encodings(self):
+ encoding = getEncoding(1, 1, 0) # Mac Japanese
+ decoded = b"\xfe".decode(encoding)
+ self.assertEqual(decoded, chr(0x2122))
- def test_extended_mac_encodings(self):
- encoding = getEncoding(1, 1, 0) # Mac Japanese
- decoded = b'\xfe'.decode(encoding)
- self.assertEqual(decoded, chr(0x2122))
+ def test_extended_unknown(self):
+ self.assertEqual(getEncoding(10, 11, 12), None)
+ self.assertEqual(getEncoding(10, 11, 12, "ascii"), "ascii")
+ self.assertEqual(getEncoding(10, 11, 12, default="ascii"), "ascii")
- def test_extended_unknown(self):
- self.assertEqual(getEncoding(10, 11, 12), None)
- self.assertEqual(getEncoding(10, 11, 12, "ascii"), "ascii")
- self.assertEqual(getEncoding(10, 11, 12, default="ascii"), "ascii")
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/misc/filenames_test.py b/Tests/misc/filenames_test.py
index bb7b63c2..f96156c0 100644
--- a/Tests/misc/filenames_test.py
+++ b/Tests/misc/filenames_test.py
@@ -1,136 +1,123 @@
import unittest
-from fontTools.misc.filenames import (
- userNameToFileName, handleClash1, handleClash2)
+from fontTools.misc.filenames import userNameToFileName, handleClash1, handleClash2
class UserNameToFilenameTest(unittest.TestCase):
+ def test_names(self):
+ self.assertEqual(userNameToFileName("a"), "a")
+ self.assertEqual(userNameToFileName("A"), "A_")
+ self.assertEqual(userNameToFileName("AE"), "A_E_")
+ self.assertEqual(userNameToFileName("Ae"), "A_e")
+ self.assertEqual(userNameToFileName("ae"), "ae")
+ self.assertEqual(userNameToFileName("aE"), "aE_")
+ self.assertEqual(userNameToFileName("a.alt"), "a.alt")
+ self.assertEqual(userNameToFileName("A.alt"), "A_.alt")
+ self.assertEqual(userNameToFileName("A.Alt"), "A_.A_lt")
+ self.assertEqual(userNameToFileName("A.aLt"), "A_.aL_t")
+ self.assertEqual(userNameToFileName("A.alT"), "A_.alT_")
+ self.assertEqual(userNameToFileName("T_H"), "T__H_")
+ self.assertEqual(userNameToFileName("T_h"), "T__h")
+ self.assertEqual(userNameToFileName("t_h"), "t_h")
+ self.assertEqual(userNameToFileName("F_F_I"), "F__F__I_")
+ self.assertEqual(userNameToFileName("f_f_i"), "f_f_i")
+ self.assertEqual(userNameToFileName("Aacute_V.swash"), "A_acute_V_.swash")
+ self.assertEqual(userNameToFileName(".notdef"), "_notdef")
+ self.assertEqual(userNameToFileName("con"), "_con")
+ self.assertEqual(userNameToFileName("CON"), "C_O_N_")
+ self.assertEqual(userNameToFileName("con.alt"), "_con.alt")
+ self.assertEqual(userNameToFileName("alt.con"), "alt._con")
+
+ def test_prefix_suffix(self):
+ prefix = "TEST_PREFIX"
+ suffix = "TEST_SUFFIX"
+ name = "NAME"
+ name_file = "N_A_M_E_"
+ self.assertEqual(
+ userNameToFileName(name, prefix=prefix, suffix=suffix),
+ prefix + name_file + suffix,
+ )
+
+ def test_collide(self):
+ prefix = "TEST_PREFIX"
+ suffix = "TEST_SUFFIX"
+ name = "NAME"
+ name_file = "N_A_M_E_"
+ collision_avoidance1 = "000000000000001"
+ collision_avoidance2 = "000000000000002"
+ exist = set()
+ generated = userNameToFileName(name, exist, prefix=prefix, suffix=suffix)
+ exist.add(generated.lower())
+ self.assertEqual(generated, prefix + name_file + suffix)
+ generated = userNameToFileName(name, exist, prefix=prefix, suffix=suffix)
+ exist.add(generated.lower())
+ self.assertEqual(generated, prefix + name_file + collision_avoidance1 + suffix)
+ generated = userNameToFileName(name, exist, prefix=prefix, suffix=suffix)
+ self.assertEqual(generated, prefix + name_file + collision_avoidance2 + suffix)
+
+ def test_ValueError(self):
+ with self.assertRaises(ValueError):
+ userNameToFileName(b"a")
+ with self.assertRaises(ValueError):
+ userNameToFileName({"a"})
+ with self.assertRaises(ValueError):
+ userNameToFileName(("a",))
+ with self.assertRaises(ValueError):
+ userNameToFileName(["a"])
+ with self.assertRaises(ValueError):
+ userNameToFileName(["a"])
+ with self.assertRaises(ValueError):
+ userNameToFileName(b"\xd8\x00")
+
+ def test_handleClash1(self):
+ prefix = ("0" * 5) + "."
+ suffix = "." + ("0" * 10)
+ existing = ["a" * 5]
+
+ e = list(existing)
+ self.assertEqual(
+ handleClash1(userName="A" * 5, existing=e, prefix=prefix, suffix=suffix),
+ "00000.AAAAA000000000000001.0000000000",
+ )
+
+ e = list(existing)
+ e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
+ self.assertEqual(
+ handleClash1(userName="A" * 5, existing=e, prefix=prefix, suffix=suffix),
+ "00000.AAAAA000000000000002.0000000000",
+ )
+
+ e = list(existing)
+ e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
+ self.assertEqual(
+ handleClash1(userName="A" * 5, existing=e, prefix=prefix, suffix=suffix),
+ "00000.AAAAA000000000000001.0000000000",
+ )
+
+ def test_handleClash2(self):
+ prefix = ("0" * 5) + "."
+ suffix = "." + ("0" * 10)
+ existing = [prefix + str(i) + suffix for i in range(100)]
+
+ e = list(existing)
+ self.assertEqual(
+ handleClash2(existing=e, prefix=prefix, suffix=suffix),
+ "00000.100.0000000000",
+ )
+
+ e = list(existing)
+ e.remove(prefix + "1" + suffix)
+ self.assertEqual(
+ handleClash2(existing=e, prefix=prefix, suffix=suffix), "00000.1.0000000000"
+ )
+
+ e = list(existing)
+ e.remove(prefix + "2" + suffix)
+ self.assertEqual(
+ handleClash2(existing=e, prefix=prefix, suffix=suffix), "00000.2.0000000000"
+ )
- def test_names(self):
- self.assertEqual(userNameToFileName("a"),"a")
- self.assertEqual(userNameToFileName("A"), "A_")
- self.assertEqual(userNameToFileName("AE"), "A_E_")
- self.assertEqual(userNameToFileName("Ae"), "A_e")
- self.assertEqual(userNameToFileName("ae"), "ae")
- self.assertEqual(userNameToFileName("aE"), "aE_")
- self.assertEqual(userNameToFileName("a.alt"), "a.alt")
- self.assertEqual(userNameToFileName("A.alt"), "A_.alt")
- self.assertEqual(userNameToFileName("A.Alt"), "A_.A_lt")
- self.assertEqual(userNameToFileName("A.aLt"), "A_.aL_t")
- self.assertEqual(userNameToFileName(u"A.alT"), "A_.alT_")
- self.assertEqual(userNameToFileName("T_H"), "T__H_")
- self.assertEqual(userNameToFileName("T_h"), "T__h")
- self.assertEqual(userNameToFileName("t_h"), "t_h")
- self.assertEqual(userNameToFileName("F_F_I"), "F__F__I_")
- self.assertEqual(userNameToFileName("f_f_i"), "f_f_i")
- self.assertEqual(
- userNameToFileName("Aacute_V.swash"),
- "A_acute_V_.swash")
- self.assertEqual(userNameToFileName(".notdef"), "_notdef")
- self.assertEqual(userNameToFileName("con"), "_con")
- self.assertEqual(userNameToFileName("CON"), "C_O_N_")
- self.assertEqual(userNameToFileName("con.alt"), "_con.alt")
- self.assertEqual(userNameToFileName("alt.con"), "alt._con")
-
- def test_prefix_suffix(self):
- prefix = "TEST_PREFIX"
- suffix = "TEST_SUFFIX"
- name = "NAME"
- name_file = "N_A_M_E_"
- self.assertEqual(
- userNameToFileName(name, prefix=prefix, suffix=suffix),
- prefix + name_file + suffix)
-
- def test_collide(self):
- prefix = "TEST_PREFIX"
- suffix = "TEST_SUFFIX"
- name = "NAME"
- name_file = "N_A_M_E_"
- collision_avoidance1 = "000000000000001"
- collision_avoidance2 = "000000000000002"
- exist = set()
- generated = userNameToFileName(
- name, exist, prefix=prefix, suffix=suffix)
- exist.add(generated.lower())
- self.assertEqual(generated, prefix + name_file + suffix)
- generated = userNameToFileName(
- name, exist, prefix=prefix, suffix=suffix)
- exist.add(generated.lower())
- self.assertEqual(
- generated,
- prefix + name_file + collision_avoidance1 + suffix)
- generated = userNameToFileName(
- name, exist, prefix=prefix, suffix=suffix)
- self.assertEqual(
- generated,
- prefix + name_file + collision_avoidance2+ suffix)
-
- def test_ValueError(self):
- with self.assertRaises(ValueError):
- userNameToFileName(b"a")
- with self.assertRaises(ValueError):
- userNameToFileName({"a"})
- with self.assertRaises(ValueError):
- userNameToFileName(("a",))
- with self.assertRaises(ValueError):
- userNameToFileName(["a"])
- with self.assertRaises(ValueError):
- userNameToFileName(["a"])
- with self.assertRaises(ValueError):
- userNameToFileName(b"\xd8\x00")
-
- def test_handleClash1(self):
- prefix = ("0" * 5) + "."
- suffix = "." + ("0" * 10)
- existing = ["a" * 5]
-
- e = list(existing)
- self.assertEqual(
- handleClash1(userName="A" * 5, existing=e, prefix=prefix,
- suffix=suffix),
- '00000.AAAAA000000000000001.0000000000'
- )
-
- e = list(existing)
- e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
- self.assertEqual(
- handleClash1(userName="A" * 5, existing=e, prefix=prefix,
- suffix=suffix),
- '00000.AAAAA000000000000002.0000000000'
- )
-
- e = list(existing)
- e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
- self.assertEqual(
- handleClash1(userName="A" * 5, existing=e, prefix=prefix,
- suffix=suffix),
- '00000.AAAAA000000000000001.0000000000'
- )
-
- def test_handleClash2(self):
- prefix = ("0" * 5) + "."
- suffix = "." + ("0" * 10)
- existing = [prefix + str(i) + suffix for i in range(100)]
-
- e = list(existing)
- self.assertEqual(
- handleClash2(existing=e, prefix=prefix, suffix=suffix),
- '00000.100.0000000000'
- )
-
- e = list(existing)
- e.remove(prefix + "1" + suffix)
- self.assertEqual(
- handleClash2(existing=e, prefix=prefix, suffix=suffix),
- '00000.1.0000000000'
- )
-
- e = list(existing)
- e.remove(prefix + "2" + suffix)
- self.assertEqual(
- handleClash2(existing=e, prefix=prefix, suffix=suffix),
- '00000.2.0000000000'
- )
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/misc/fixedTools_test.py b/Tests/misc/fixedTools_test.py
index dea61b90..3cabf3ab 100644
--- a/Tests/misc/fixedTools_test.py
+++ b/Tests/misc/fixedTools_test.py
@@ -10,10 +10,9 @@ import unittest
class FixedToolsTest(unittest.TestCase):
-
def test_roundtrip(self):
for bits in range(0, 15):
- for value in range(-(2**(bits+1)), 2**(bits+1)):
+ for value in range(-(2 ** (bits + 1)), 2 ** (bits + 1)):
self.assertEqual(value, floatToFixed(fixedToFloat(value, bits), bits))
def test_fixedToFloat_precision14(self):
@@ -31,18 +30,18 @@ class FixedToolsTest(unittest.TestCase):
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))
+ 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))
+ 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))
@@ -53,28 +52,28 @@ class FixedToolsTest(unittest.TestCase):
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))
+ 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))
+ 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))
+ 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)
@@ -83,4 +82,5 @@ class FixedToolsTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/misc/loggingTools_test.py b/Tests/misc/loggingTools_test.py
index fd13044c..feccd7b3 100644
--- a/Tests/misc/loggingTools_test.py
+++ b/Tests/misc/loggingTools_test.py
@@ -17,9 +17,10 @@ def logger_name_generator():
basename = "fontTools.test#"
num = 1
while True:
- yield basename+str(num)
+ yield basename + str(num)
num += 1
+
unique_logger_name = logger_name_generator()
@@ -35,10 +36,11 @@ def test_LevelFormatter():
handler = logging.StreamHandler(stream)
formatter = LevelFormatter(
fmt={
- '*': '[%(levelname)s] %(message)s',
- 'DEBUG': '%(name)s [%(levelname)s] %(message)s',
- 'INFO': '%(message)s',
- })
+ "*": "[%(levelname)s] %(message)s",
+ "DEBUG": "%(name)s [%(levelname)s] %(message)s",
+ "INFO": "%(message)s",
+ }
+ )
handler.setFormatter(formatter)
name = next(unique_logger_name)
log = logging.getLogger(name)
@@ -49,19 +51,21 @@ def test_LevelFormatter():
log.info("this also uses a custom format string")
log.warning("this one uses the default format string")
- assert stream.getvalue() == textwrap.dedent("""\
+ assert stream.getvalue() == textwrap.dedent(
+ """\
%s [DEBUG] this uses a custom format string
this also uses a custom format string
[WARNING] this one uses the default format string
- """ % name)
+ """
+ % name
+ )
class TimerTest(object):
-
def test_split(self):
timer = Timer()
time.sleep(0.01)
- fist_lap = timer.split()
+ fist_lap = timer.split()
assert timer.elapsed == fist_lap
time.sleep(0.1)
second_lap = timer.split()
@@ -80,12 +84,13 @@ class TimerTest(object):
assert t.elapsed > 0
def test_using_logger(self, logger):
- with Timer(logger, 'do something'):
+ with Timer(logger, "do something"):
time.sleep(0.01)
assert re.match(
r"Took [0-9]\.[0-9]{3}s to do something",
- logger.handlers[0].stream.getvalue())
+ logger.handlers[0].stream.getvalue(),
+ )
def test_using_logger_calling_instance(self, logger):
timer = Timer(logger)
@@ -93,16 +98,17 @@ class TimerTest(object):
time.sleep(0.01)
assert re.match(
- r"elapsed time: [0-9]\.[0-9]{3}s",
- logger.handlers[0].stream.getvalue())
+ r"elapsed time: [0-9]\.[0-9]{3}s", logger.handlers[0].stream.getvalue()
+ )
# do it again but with custom level
- with timer('redo it', level=logging.WARNING):
+ with timer("redo it", level=logging.WARNING):
time.sleep(0.02)
assert re.search(
r"WARNING: Took [0-9]\.[0-9]{3}s to redo it",
- logger.handlers[0].stream.getvalue())
+ logger.handlers[0].stream.getvalue(),
+ )
def test_function_decorator(self, logger):
timer = Timer(logger)
@@ -110,7 +116,8 @@ class TimerTest(object):
@timer()
def test1():
time.sleep(0.01)
- @timer('run test 2', level=logging.INFO)
+
+ @timer("run test 2", level=logging.INFO)
def test2():
time.sleep(0.02)
@@ -118,44 +125,44 @@ class TimerTest(object):
assert re.match(
r"Took [0-9]\.[0-9]{3}s to run 'test1'",
- logger.handlers[0].stream.getvalue())
+ logger.handlers[0].stream.getvalue(),
+ )
test2()
assert re.search(
- r"Took [0-9]\.[0-9]{3}s to run test 2",
- logger.handlers[0].stream.getvalue())
+ r"Took [0-9]\.[0-9]{3}s to run test 2", logger.handlers[0].stream.getvalue()
+ )
def test_ChannelsFilter(logger):
n = logger.name
- filtr = ChannelsFilter(n+".A.B", n+".C.D")
+ filtr = ChannelsFilter(n + ".A.B", n + ".C.D")
handler = logger.handlers[0]
handler.addFilter(filtr)
stream = handler.stream
- logging.getLogger(n+".A.B").debug('this record passes through')
- assert 'this record passes through' in stream.getvalue()
+ logging.getLogger(n + ".A.B").debug("this record passes through")
+ assert "this record passes through" in stream.getvalue()
- logging.getLogger(n+'.A.B.C').debug('records from children also pass')
- assert 'records from children also pass' in stream.getvalue()
+ logging.getLogger(n + ".A.B.C").debug("records from children also pass")
+ assert "records from children also pass" in stream.getvalue()
- logging.getLogger(n+'.C.D').debug('this one as well')
- assert 'this one as well' in stream.getvalue()
+ logging.getLogger(n + ".C.D").debug("this one as well")
+ assert "this one as well" in stream.getvalue()
- logging.getLogger(n+'.A.B.').debug('also this one')
- assert 'also this one' in stream.getvalue()
+ logging.getLogger(n + ".A.B.").debug("also this one")
+ assert "also this one" in stream.getvalue()
before = stream.getvalue()
- logging.getLogger(n+'.A.F').debug('but this one does not!')
+ logging.getLogger(n + ".A.F").debug("but this one does not!")
assert before == stream.getvalue()
- logging.getLogger(n+'.C.DE').debug('neither this one!')
+ logging.getLogger(n + ".C.DE").debug("neither this one!")
assert before == stream.getvalue()
def test_LogMixin():
-
class Base(object):
pass
@@ -168,8 +175,8 @@ def test_LogMixin():
a = A()
b = B()
- assert hasattr(a, 'log')
- assert hasattr(b, 'log')
+ assert hasattr(a, "log")
+ assert hasattr(b, "log")
assert isinstance(a.log, logging.Logger)
assert isinstance(b.log, logging.Logger)
assert a.log.name == "loggingTools_test.A"
diff --git a/Tests/misc/macRes_test.py b/Tests/misc/macRes_test.py
index a6a8e9d4..deac29bf 100644
--- a/Tests/misc/macRes_test.py
+++ b/Tests/misc/macRes_test.py
@@ -17,80 +17,78 @@ data 'test' (130, "name3") { $"486F 7720 6172 6520 796F 753F" }; /* How are you
# $ /usr/bin/Rez testdata.rez -o compiled
# $ hexdump -v compiled/..namedfork/rsrc
TEST_RSRC_FORK = deHexStr(
- "00 00 01 00 00 00 01 22 00 00 00 22 00 00 00 64 " # 0x00000000
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000010
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000020
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000030
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000040
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000050
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000060
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000070
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000080
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000090
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000A0
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000B0
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000C0
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000D0
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000E0
- "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000F0
- "00 00 00 05 48 65 6c 6c 6f 00 00 00 05 57 6f 72 " # 0x00000100
- "6c 64 00 00 00 0c 48 6f 77 20 61 72 65 20 79 6f " # 0x00000110
- "75 3f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000120
- "00 00 00 00 00 00 00 00 00 00 00 1c 00 52 00 01 " # 0x00000130
- "54 45 53 54 00 01 00 12 74 65 73 74 00 00 00 2a " # 0x00000140
- "00 80 00 00 00 00 00 00 00 00 00 00 00 81 00 06 " # 0x00000150
- "00 00 00 09 00 00 00 00 00 82 00 0c 00 00 00 12 " # 0x00000160
- "00 00 00 00 05 6e 61 6d 65 31 05 6e 61 6d 65 32 " # 0x00000170
- "05 6e 61 6d 65 33 " # 0x00000180
+ "00 00 01 00 00 00 01 22 00 00 00 22 00 00 00 64 " # 0x00000000
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000010
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000020
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000030
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000040
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000050
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000060
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000070
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000080
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000090
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000A0
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000B0
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000C0
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000D0
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000E0
+ "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x000000F0
+ "00 00 00 05 48 65 6c 6c 6f 00 00 00 05 57 6f 72 " # 0x00000100
+ "6c 64 00 00 00 0c 48 6f 77 20 61 72 65 20 79 6f " # 0x00000110
+ "75 3f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " # 0x00000120
+ "00 00 00 00 00 00 00 00 00 00 00 1c 00 52 00 01 " # 0x00000130
+ "54 45 53 54 00 01 00 12 74 65 73 74 00 00 00 2a " # 0x00000140
+ "00 80 00 00 00 00 00 00 00 00 00 00 00 81 00 06 " # 0x00000150
+ "00 00 00 09 00 00 00 00 00 82 00 0c 00 00 00 12 " # 0x00000160
+ "00 00 00 00 05 6e 61 6d 65 31 05 6e 61 6d 65 32 " # 0x00000170
+ "05 6e 61 6d 65 33 " # 0x00000180
)
class ResourceReaderTest(unittest.TestCase):
+ def test_read_file(self):
+ infile = BytesIO(TEST_RSRC_FORK)
+ reader = ResourceReader(infile)
+ resources = [res for typ in reader.keys() for res in reader[typ]]
+ self.assertExpected(resources)
- def test_read_file(self):
- infile = BytesIO(TEST_RSRC_FORK)
- reader = ResourceReader(infile)
- resources = [res for typ in reader.keys() for res in reader[typ]]
- self.assertExpected(resources)
+ def test_read_datafork(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
+ tmp.write(TEST_RSRC_FORK)
+ try:
+ reader = ResourceReader(tmp.name)
+ resources = [res for typ in reader.keys() for res in reader[typ]]
+ reader.close()
+ self.assertExpected(resources)
+ finally:
+ os.remove(tmp.name)
- def test_read_datafork(self):
- with tempfile.NamedTemporaryFile(delete=False) as tmp:
- tmp.write(TEST_RSRC_FORK)
- try:
- reader = ResourceReader(tmp.name)
- resources = [res for typ in reader.keys() for res in reader[typ]]
- reader.close()
- self.assertExpected(resources)
- finally:
- os.remove(tmp.name)
+ def test_read_namedfork_rsrc(self):
+ if sys.platform != "darwin":
+ self.skipTest('Not supported on "%s"' % sys.platform)
+ tmp = tempfile.NamedTemporaryFile(delete=False)
+ tmp.close()
+ try:
+ with open(tmp.name + "/..namedfork/rsrc", "wb") as fork:
+ fork.write(TEST_RSRC_FORK)
+ reader = ResourceReader(tmp.name)
+ resources = [res for typ in reader.keys() for res in reader[typ]]
+ reader.close()
+ self.assertExpected(resources)
+ finally:
+ os.remove(tmp.name)
- def test_read_namedfork_rsrc(self):
- if sys.platform != 'darwin':
- self.skipTest('Not supported on "%s"' % sys.platform)
- tmp = tempfile.NamedTemporaryFile(delete=False)
- tmp.close()
- try:
- with open(tmp.name + '/..namedfork/rsrc', 'wb') as fork:
- fork.write(TEST_RSRC_FORK)
- reader = ResourceReader(tmp.name)
- resources = [res for typ in reader.keys() for res in reader[typ]]
- reader.close()
- self.assertExpected(resources)
- finally:
- os.remove(tmp.name)
+ def assertExpected(self, resources):
+ self.assertRezEqual(resources[0], "TEST", b"Hello", 128, "name1")
+ self.assertRezEqual(resources[1], "TEST", b"World", 129, "name2")
+ self.assertRezEqual(resources[2], "test", b"How are you?", 130, "name3")
- def assertExpected(self, resources):
- self.assertRezEqual(resources[0], 'TEST', b'Hello', 128, 'name1')
- self.assertRezEqual(resources[1], 'TEST', b'World', 129, 'name2')
- self.assertRezEqual(
- resources[2], 'test', b'How are you?', 130, 'name3')
+ def assertRezEqual(self, res, type_, data, id, name):
+ self.assertEqual(res.type, type_)
+ self.assertEqual(res.data, data)
+ self.assertEqual(res.id, id)
+ self.assertEqual(res.name, name)
- def assertRezEqual(self, res, type_, data, id, name):
- self.assertEqual(res.type, type_)
- self.assertEqual(res.data, data)
- self.assertEqual(res.id, id)
- self.assertEqual(res.name, name)
-
-if __name__ == '__main__':
- sys.exit(unittest.main())
+if __name__ == "__main__":
+ sys.exit(unittest.main())
diff --git a/Tests/misc/plistlib_test.py b/Tests/misc/plistlib_test.py
index 5659d690..057df64a 100644
--- a/Tests/misc/plistlib_test.py
+++ b/Tests/misc/plistlib_test.py
@@ -8,9 +8,6 @@ from numbers import Integral
from fontTools.misc import etree
from fontTools.misc import plistlib
from fontTools.misc.textTools import tostr
-from fontTools.ufoLib.plistlib import (
- readPlist, readPlistFromString, writePlist, writePlistToString,
-)
import pytest
from collections.abc import Mapping
@@ -30,8 +27,8 @@ def _test_pl(use_builtin_types):
aList=["A", "B", 12, 32.5, [1, 2, 3]],
aFloat=0.5,
anInt=728,
- aBigInt=2 ** 63 - 44,
- aBigInt2=2 ** 63 + 44,
+ aBigInt=2**63 - 44,
+ aBigInt2=2**63 + 44,
aNegativeInt=-5,
aNegativeBigInt=-80000000000,
aDict=dict(
@@ -112,16 +109,16 @@ def test_invalid_type():
"pl",
[
0,
- 2 ** 8 - 1,
- 2 ** 8,
- 2 ** 16 - 1,
- 2 ** 16,
- 2 ** 32 - 1,
- 2 ** 32,
- 2 ** 63 - 1,
- 2 ** 64 - 1,
+ 2**8 - 1,
+ 2**8,
+ 2**16 - 1,
+ 2**16,
+ 2**32 - 1,
+ 2**32,
+ 2**63 - 1,
+ 2**64 - 1,
1,
- -2 ** 63,
+ -(2**63),
],
)
def test_int(pl):
@@ -133,9 +130,7 @@ def test_int(pl):
assert data == data2
-@pytest.mark.parametrize(
- "pl", [2 ** 64 + 1, 2 ** 127 - 1, -2 ** 64, -2 ** 127]
-)
+@pytest.mark.parametrize("pl", [2**64 + 1, 2**127 - 1, -(2**64), -(2**127)])
def test_int_overflow(pl):
with pytest.raises(OverflowError):
plistlib.dumps(pl)
@@ -186,9 +181,7 @@ def test_indentation_array():
def test_indentation_dict():
- data = {
- "1": {"2": {"3": {"4": {"5": {"6": {"7": {"8": {"9": "aaaaaa"}}}}}}}}
- }
+ data = {"1": {"2": {"3": {"4": {"5": {"6": {"7": {"8": {"9": "aaaaaa"}}}}}}}}}
assert plistlib.loads(plistlib.dumps(data)) == data
@@ -226,9 +219,7 @@ def test_bytesio(parametrized_pl):
pl, use_builtin_types = parametrized_pl
b = BytesIO()
plistlib.dump(pl, b, use_builtin_types=use_builtin_types)
- pl2 = plistlib.load(
- BytesIO(b.getvalue()), use_builtin_types=use_builtin_types
- )
+ pl2 = plistlib.load(BytesIO(b.getvalue()), use_builtin_types=use_builtin_types)
assert pl == pl2
@@ -242,9 +233,7 @@ def test_keysort_bytesio(sort_keys):
b = BytesIO()
plistlib.dump(pl, b, sort_keys=sort_keys)
- pl2 = plistlib.load(
- BytesIO(b.getvalue()), dict_type=collections.OrderedDict
- )
+ pl2 = plistlib.load(BytesIO(b.getvalue()), dict_type=collections.OrderedDict)
assert dict(pl) == dict(pl2)
if sort_keys:
@@ -362,9 +351,7 @@ def test_invalidarray():
"<true/><key>key inside an array3</key>",
]:
with pytest.raises(ValueError):
- plistlib.loads(
- ("<plist><array>%s</array></plist>" % i).encode("utf-8")
- )
+ plistlib.loads(("<plist><array>%s</array></plist>" % i).encode("utf-8"))
def test_invaliddict():
@@ -447,9 +434,7 @@ def test_no_pretty_print(use_builtin_types):
use_builtin_types=use_builtin_types,
)
assert data == (
- plistlib.XML_DECLARATION
- + plistlib.PLIST_DOCTYPE
- + b'<plist version="1.0">'
+ plistlib.XML_DECLARATION + plistlib.PLIST_DOCTYPE + b'<plist version="1.0">'
b"<dict>"
b"<key>data</key>"
b"<data>aGVsbG8=</data>"
@@ -459,45 +444,51 @@ def test_no_pretty_print(use_builtin_types):
def test_readPlist_from_path(pl):
+ old_plistlib = pytest.importorskip("fontTools.ufoLib.plistlib")
path = os.path.join(datadir, "test.plist")
- pl2 = readPlist(path)
+ pl2 = old_plistlib.readPlist(path)
assert isinstance(pl2["someData"], plistlib.Data)
assert pl2 == pl
def test_readPlist_from_file(pl):
+ old_plistlib = pytest.importorskip("fontTools.ufoLib.plistlib")
with open(os.path.join(datadir, "test.plist"), "rb") as f:
- pl2 = readPlist(f)
+ pl2 = old_plistlib.readPlist(f)
assert isinstance(pl2["someData"], plistlib.Data)
assert pl2 == pl
assert not f.closed
def test_readPlistFromString(pl):
- pl2 = readPlistFromString(TESTDATA)
+ old_plistlib = pytest.importorskip("fontTools.ufoLib.plistlib")
+ pl2 = old_plistlib.readPlistFromString(TESTDATA)
assert isinstance(pl2["someData"], plistlib.Data)
assert pl2 == pl
def test_writePlist_to_path(tmpdir, pl_no_builtin_types):
+ old_plistlib = pytest.importorskip("fontTools.ufoLib.plistlib")
testpath = tmpdir / "test.plist"
- writePlist(pl_no_builtin_types, str(testpath))
+ old_plistlib.writePlist(pl_no_builtin_types, str(testpath))
with testpath.open("rb") as fp:
pl2 = plistlib.load(fp, use_builtin_types=False)
assert pl2 == pl_no_builtin_types
def test_writePlist_to_file(tmpdir, pl_no_builtin_types):
+ old_plistlib = pytest.importorskip("fontTools.ufoLib.plistlib")
testpath = tmpdir / "test.plist"
with testpath.open("wb") as fp:
- writePlist(pl_no_builtin_types, fp)
+ old_plistlib.writePlist(pl_no_builtin_types, fp)
with testpath.open("rb") as fp:
pl2 = plistlib.load(fp, use_builtin_types=False)
assert pl2 == pl_no_builtin_types
def test_writePlistToString(pl_no_builtin_types):
- data = writePlistToString(pl_no_builtin_types)
+ old_plistlib = pytest.importorskip("fontTools.ufoLib.plistlib")
+ data = old_plistlib.writePlistToString(pl_no_builtin_types)
pl2 = plistlib.loads(data)
assert pl2 == pl_no_builtin_types
diff --git a/Tests/misc/psCharStrings_test.py b/Tests/misc/psCharStrings_test.py
index 5e36fe73..5eb2f774 100644
--- a/Tests/misc/psCharStrings_test.py
+++ b/Tests/misc/psCharStrings_test.py
@@ -13,11 +13,10 @@ import unittest
def hexenc(s):
- return ' '.join('%02x' % x for x in s)
+ return " ".join("%02x" % x for x in s)
class T2CharStringTest(unittest.TestCase):
-
@classmethod
def stringToT2CharString(cls, string):
return T2CharString(program=stringToProgram(string), private=PrivateDict())
@@ -28,50 +27,69 @@ class T2CharStringTest(unittest.TestCase):
self.assertEqual(bounds, None)
def test_calcBounds_line(self):
- cs = self.stringToT2CharString("100 100 rmoveto 40 10 rlineto -20 50 rlineto endchar")
+ cs = self.stringToT2CharString(
+ "100 100 rmoveto 40 10 rlineto -20 50 rlineto endchar"
+ )
bounds = cs.calcBounds(None)
self.assertEqual(bounds, (100, 100, 140, 160))
def test_calcBounds_curve(self):
- cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto endchar")
+ cs = self.stringToT2CharString(
+ "100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto endchar"
+ )
bounds = cs.calcBounds(None)
self.assertEqual(bounds, (91.90524980688875, -12.5, 208.09475019311125, 100))
def test_charstring_bytecode_optimization(self):
cs = self.stringToT2CharString(
- "100.0 100 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto endchar")
+ "100.0 100 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto endchar"
+ )
cs.isCFF2 = False
cs.private._isCFF2 = False
cs.compile()
cs.decompile()
self.assertEqual(
- cs.program, [100, 100, 'rmoveto', -50, -150, 200.5, 0, -50, 150,
- 'rrcurveto', 'endchar'])
+ cs.program,
+ [
+ 100,
+ 100,
+ "rmoveto",
+ -50,
+ -150,
+ 200.5,
+ 0,
+ -50,
+ 150,
+ "rrcurveto",
+ "endchar",
+ ],
+ )
cs2 = self.stringToT2CharString(
- "100.0 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto")
+ "100.0 rmoveto -50.0 -150 200.5 0.0 -50 150 rrcurveto"
+ )
cs2.isCFF2 = True
cs2.private._isCFF2 = True
cs2.compile(isCFF2=True)
cs2.decompile()
self.assertEqual(
- cs2.program, [100, 'rmoveto', -50, -150, 200.5, 0, -50, 150,
- 'rrcurveto'])
+ cs2.program, [100, "rmoveto", -50, -150, 200.5, 0, -50, 150, "rrcurveto"]
+ )
def test_encodeFloat(self):
testNums = [
# value expected result
- (-9.399999999999999, '1e e9 a4 ff'), # -9.4
- (9.399999999999999999, '1e 9a 4f'), # 9.4
- (456.8, '1e 45 6a 8f'), # 456.8
- (0.0, '1e 0f'), # 0
- (-0.0, '1e 0f'), # 0
- (1.0, '1e 1f'), # 1
- (-1.0, '1e e1 ff'), # -1
- (98765.37e2, '1e 98 76 53 7f'), # 9876537
- (1234567890.0, '1e 1a 23 45 67 9b 09 ff'), # 1234567890
- (9.876537e-4, '1e a0 00 98 76 53 7f'), # 9.876537e-24
- (9.876537e+4, '1e 98 76 5a 37 ff'), # 9.876537e+24
+ (-9.399999999999999, "1e e9 a4 ff"), # -9.4
+ (9.399999999999999999, "1e 9a 4f"), # 9.4
+ (456.8, "1e 45 6a 8f"), # 456.8
+ (0.0, "1e 0f"), # 0
+ (-0.0, "1e 0f"), # 0
+ (1.0, "1e 1f"), # 1
+ (-1.0, "1e e1 ff"), # -1
+ (98765.37e2, "1e 98 76 53 7f"), # 9876537
+ (1234567890.0, "1e 1a 23 45 67 9b 09 ff"), # 1234567890
+ (9.876537e-4, "1e a0 00 98 76 53 7f"), # 9.876537e-24
+ (9.876537e4, "1e 98 76 5a 37 ff"), # 9.876537e+24
]
for sample in testNums:
@@ -87,22 +105,22 @@ class T2CharStringTest(unittest.TestCase):
encoded_result,
1,
)
- self.assertEqual(decoded_result[0], float('%.8g' % sample[0]))
+ self.assertEqual(decoded_result[0], float("%.8g" % sample[0]))
# 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),
+ (-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:
+ for value, expected_hex, expected_float in testNums:
encoded_result = encodeFixed(value)
# check to see if we got the expected bytes
@@ -119,11 +137,11 @@ class T2CharStringTest(unittest.TestCase):
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'
+ "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))
@@ -133,21 +151,31 @@ class T2CharStringTest(unittest.TestCase):
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>'
+ '<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'
+ 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))
@@ -162,12 +190,15 @@ class T2CharStringTest(unittest.TestCase):
def test_pen_closePath(self):
# Test CFF2/T2 charstring: it does NOT end in "endchar"
# https://github.com/fonttools/fonttools/issues/2455
- cs = self.stringToT2CharString("100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto")
+ cs = self.stringToT2CharString(
+ "100 100 rmoveto -50 -150 200 0 -50 150 rrcurveto"
+ )
pen = RecordingPen()
cs.draw(pen)
- self.assertEqual(pen.value[-1], ('closePath', ()))
+ self.assertEqual(pen.value[-1], ("closePath", ()))
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/misc/py23_test.py b/Tests/misc/py23_test.py
index 61274cc2..30382455 100644
--- a/Tests/misc/py23_test.py
+++ b/Tests/misc/py23_test.py
@@ -9,7 +9,12 @@ import os
import unittest
from fontTools.misc.py23 import (
- round2, round3, isclose, redirect_stdout, redirect_stderr)
+ round2,
+ round3,
+ isclose,
+ redirect_stdout,
+ redirect_stderr,
+)
PIPE_SCRIPT = """\
@@ -21,377 +26,374 @@ binary_stdout.write(binary_stdin.read())
# the string contains a mix of line endings, plus the Win "EOF" charater (0x1A)
# 'hello\rworld\r\n\x1a\r\n'
-TEST_BIN_DATA = deHexStr(
- "68 65 6c 6c 6f 0d 77 6f 72 6c 64 0d 0a 1a 0d 0a"
-)
+TEST_BIN_DATA = deHexStr("68 65 6c 6c 6f 0d 77 6f 72 6c 64 0d 0a 1a 0d 0a")
-class OpenFuncWrapperTest(unittest.TestCase):
- @staticmethod
- def make_temp(data):
- with tempfile.NamedTemporaryFile(delete=False) as f:
- f.write(tobytes(data))
- return f.name
-
- def diff_piped(self, data, import_statement):
- script = self.make_temp("\n".join([import_statement, PIPE_SCRIPT]))
- datafile = self.make_temp(data)
- try:
- with open(datafile, 'rb') as infile, \
- tempfile.NamedTemporaryFile(delete=False) as outfile:
- env = dict(os.environ)
- env["PYTHONPATH"] = os.pathsep.join(sys.path)
- check_call(
- [sys.executable, script], stdin=infile, stdout=outfile,
- env=env)
- result = not filecmp.cmp(infile.name, outfile.name, shallow=False)
- finally:
- os.remove(script)
- os.remove(datafile)
- os.remove(outfile.name)
- return result
-
- def test_binary_pipe_py23_open_wrapper(self):
- if self.diff_piped(
- TEST_BIN_DATA, "from fontTools.misc.py23 import open"):
- self.fail("Input and output data differ!")
-
- def test_binary_pipe_built_in_io_open(self):
- if sys.version_info.major < 3 and sys.platform == 'win32':
- # On Windows Python 2.x, the piped input and output data are
- # expected to be different when using io.open, because of issue
- # https://bugs.python.org/issue10841.
- expected = True
- else:
- expected = False
- result = self.diff_piped(TEST_BIN_DATA, "from io import open")
- self.assertEqual(result, expected)
+class OpenFuncWrapperTest(unittest.TestCase):
+ @staticmethod
+ def make_temp(data):
+ with tempfile.NamedTemporaryFile(delete=False) as f:
+ f.write(tobytes(data))
+ return f.name
+
+ def diff_piped(self, data, import_statement):
+ script = self.make_temp("\n".join([import_statement, PIPE_SCRIPT]))
+ datafile = self.make_temp(data)
+ try:
+ with open(datafile, "rb") as infile, tempfile.NamedTemporaryFile(
+ delete=False
+ ) as outfile:
+ env = dict(os.environ)
+ env["PYTHONPATH"] = os.pathsep.join(sys.path)
+ check_call(
+ [sys.executable, script], stdin=infile, stdout=outfile, env=env
+ )
+ result = not filecmp.cmp(infile.name, outfile.name, shallow=False)
+ finally:
+ os.remove(script)
+ os.remove(datafile)
+ os.remove(outfile.name)
+ return result
+
+ def test_binary_pipe_py23_open_wrapper(self):
+ if self.diff_piped(TEST_BIN_DATA, "from fontTools.misc.py23 import open"):
+ self.fail("Input and output data differ!")
+
+ def test_binary_pipe_built_in_io_open(self):
+ if sys.version_info.major < 3 and sys.platform == "win32":
+ # On Windows Python 2.x, the piped input and output data are
+ # expected to be different when using io.open, because of issue
+ # https://bugs.python.org/issue10841.
+ expected = True
+ else:
+ expected = False
+ result = self.diff_piped(TEST_BIN_DATA, "from io import open")
+ self.assertEqual(result, expected)
class Round2Test(unittest.TestCase):
- """
- Test cases taken from cpython 2.7 test suite:
-
- https://github.com/python/cpython/blob/2.7/Lib/test/test_float.py#L748
-
- Excludes the test cases that are not supported when using the `decimal`
- module's `quantize` method.
- """
-
- def test_second_argument_type(self):
- # floats should be illegal
- self.assertRaises(TypeError, round2, 3.14159, 2.0)
-
- def test_halfway_cases(self):
- # Halfway cases need special attention, since the current
- # implementation has to deal with them specially. Note that
- # 2.x rounds halfway values up (i.e., away from zero) while
- # 3.x does round-half-to-even.
- self.assertAlmostEqual(round2(0.125, 2), 0.13)
- self.assertAlmostEqual(round2(0.375, 2), 0.38)
- self.assertAlmostEqual(round2(0.625, 2), 0.63)
- self.assertAlmostEqual(round2(0.875, 2), 0.88)
- self.assertAlmostEqual(round2(-0.125, 2), -0.13)
- self.assertAlmostEqual(round2(-0.375, 2), -0.38)
- self.assertAlmostEqual(round2(-0.625, 2), -0.63)
- self.assertAlmostEqual(round2(-0.875, 2), -0.88)
-
- self.assertAlmostEqual(round2(0.25, 1), 0.3)
- self.assertAlmostEqual(round2(0.75, 1), 0.8)
- self.assertAlmostEqual(round2(-0.25, 1), -0.3)
- self.assertAlmostEqual(round2(-0.75, 1), -0.8)
-
- self.assertEqual(round2(-6.5, 0), -7.0)
- self.assertEqual(round2(-5.5, 0), -6.0)
- self.assertEqual(round2(-1.5, 0), -2.0)
- self.assertEqual(round2(-0.5, 0), -1.0)
- self.assertEqual(round2(0.5, 0), 1.0)
- self.assertEqual(round2(1.5, 0), 2.0)
- self.assertEqual(round2(2.5, 0), 3.0)
- self.assertEqual(round2(3.5, 0), 4.0)
- self.assertEqual(round2(4.5, 0), 5.0)
- self.assertEqual(round2(5.5, 0), 6.0)
- self.assertEqual(round2(6.5, 0), 7.0)
-
- # same but without an explicit second argument; in 3.x these
- # will give integers
- self.assertEqual(round2(-6.5), -7.0)
- self.assertEqual(round2(-5.5), -6.0)
- self.assertEqual(round2(-1.5), -2.0)
- self.assertEqual(round2(-0.5), -1.0)
- self.assertEqual(round2(0.5), 1.0)
- self.assertEqual(round2(1.5), 2.0)
- self.assertEqual(round2(2.5), 3.0)
- self.assertEqual(round2(3.5), 4.0)
- self.assertEqual(round2(4.5), 5.0)
- self.assertEqual(round2(5.5), 6.0)
- self.assertEqual(round2(6.5), 7.0)
-
- self.assertEqual(round2(-25.0, -1), -30.0)
- self.assertEqual(round2(-15.0, -1), -20.0)
- self.assertEqual(round2(-5.0, -1), -10.0)
- self.assertEqual(round2(5.0, -1), 10.0)
- self.assertEqual(round2(15.0, -1), 20.0)
- self.assertEqual(round2(25.0, -1), 30.0)
- self.assertEqual(round2(35.0, -1), 40.0)
- self.assertEqual(round2(45.0, -1), 50.0)
- self.assertEqual(round2(55.0, -1), 60.0)
- self.assertEqual(round2(65.0, -1), 70.0)
- self.assertEqual(round2(75.0, -1), 80.0)
- self.assertEqual(round2(85.0, -1), 90.0)
- self.assertEqual(round2(95.0, -1), 100.0)
- self.assertEqual(round2(12325.0, -1), 12330.0)
- self.assertEqual(round2(0, -1), 0.0)
-
- self.assertEqual(round2(350.0, -2), 400.0)
- self.assertEqual(round2(450.0, -2), 500.0)
-
- self.assertAlmostEqual(round2(0.5e21, -21), 1e21)
- self.assertAlmostEqual(round2(1.5e21, -21), 2e21)
- self.assertAlmostEqual(round2(2.5e21, -21), 3e21)
- self.assertAlmostEqual(round2(5.5e21, -21), 6e21)
- self.assertAlmostEqual(round2(8.5e21, -21), 9e21)
-
- self.assertAlmostEqual(round2(-1.5e22, -22), -2e22)
- self.assertAlmostEqual(round2(-0.5e22, -22), -1e22)
- self.assertAlmostEqual(round2(0.5e22, -22), 1e22)
- self.assertAlmostEqual(round2(1.5e22, -22), 2e22)
+ """
+ Test cases taken from cpython 2.7 test suite:
+
+ https://github.com/python/cpython/blob/2.7/Lib/test/test_float.py#L748
+
+ Excludes the test cases that are not supported when using the `decimal`
+ module's `quantize` method.
+ """
+
+ def test_second_argument_type(self):
+ # floats should be illegal
+ self.assertRaises(TypeError, round2, 3.14159, 2.0)
+
+ def test_halfway_cases(self):
+ # Halfway cases need special attention, since the current
+ # implementation has to deal with them specially. Note that
+ # 2.x rounds halfway values up (i.e., away from zero) while
+ # 3.x does round-half-to-even.
+ self.assertAlmostEqual(round2(0.125, 2), 0.13)
+ self.assertAlmostEqual(round2(0.375, 2), 0.38)
+ self.assertAlmostEqual(round2(0.625, 2), 0.63)
+ self.assertAlmostEqual(round2(0.875, 2), 0.88)
+ self.assertAlmostEqual(round2(-0.125, 2), -0.13)
+ self.assertAlmostEqual(round2(-0.375, 2), -0.38)
+ self.assertAlmostEqual(round2(-0.625, 2), -0.63)
+ self.assertAlmostEqual(round2(-0.875, 2), -0.88)
+
+ self.assertAlmostEqual(round2(0.25, 1), 0.3)
+ self.assertAlmostEqual(round2(0.75, 1), 0.8)
+ self.assertAlmostEqual(round2(-0.25, 1), -0.3)
+ self.assertAlmostEqual(round2(-0.75, 1), -0.8)
+
+ self.assertEqual(round2(-6.5, 0), -7.0)
+ self.assertEqual(round2(-5.5, 0), -6.0)
+ self.assertEqual(round2(-1.5, 0), -2.0)
+ self.assertEqual(round2(-0.5, 0), -1.0)
+ self.assertEqual(round2(0.5, 0), 1.0)
+ self.assertEqual(round2(1.5, 0), 2.0)
+ self.assertEqual(round2(2.5, 0), 3.0)
+ self.assertEqual(round2(3.5, 0), 4.0)
+ self.assertEqual(round2(4.5, 0), 5.0)
+ self.assertEqual(round2(5.5, 0), 6.0)
+ self.assertEqual(round2(6.5, 0), 7.0)
+
+ # same but without an explicit second argument; in 3.x these
+ # will give integers
+ self.assertEqual(round2(-6.5), -7.0)
+ self.assertEqual(round2(-5.5), -6.0)
+ self.assertEqual(round2(-1.5), -2.0)
+ self.assertEqual(round2(-0.5), -1.0)
+ self.assertEqual(round2(0.5), 1.0)
+ self.assertEqual(round2(1.5), 2.0)
+ self.assertEqual(round2(2.5), 3.0)
+ self.assertEqual(round2(3.5), 4.0)
+ self.assertEqual(round2(4.5), 5.0)
+ self.assertEqual(round2(5.5), 6.0)
+ self.assertEqual(round2(6.5), 7.0)
+
+ self.assertEqual(round2(-25.0, -1), -30.0)
+ self.assertEqual(round2(-15.0, -1), -20.0)
+ self.assertEqual(round2(-5.0, -1), -10.0)
+ self.assertEqual(round2(5.0, -1), 10.0)
+ self.assertEqual(round2(15.0, -1), 20.0)
+ self.assertEqual(round2(25.0, -1), 30.0)
+ self.assertEqual(round2(35.0, -1), 40.0)
+ self.assertEqual(round2(45.0, -1), 50.0)
+ self.assertEqual(round2(55.0, -1), 60.0)
+ self.assertEqual(round2(65.0, -1), 70.0)
+ self.assertEqual(round2(75.0, -1), 80.0)
+ self.assertEqual(round2(85.0, -1), 90.0)
+ self.assertEqual(round2(95.0, -1), 100.0)
+ self.assertEqual(round2(12325.0, -1), 12330.0)
+ self.assertEqual(round2(0, -1), 0.0)
+
+ self.assertEqual(round2(350.0, -2), 400.0)
+ self.assertEqual(round2(450.0, -2), 500.0)
+
+ self.assertAlmostEqual(round2(0.5e21, -21), 1e21)
+ self.assertAlmostEqual(round2(1.5e21, -21), 2e21)
+ self.assertAlmostEqual(round2(2.5e21, -21), 3e21)
+ self.assertAlmostEqual(round2(5.5e21, -21), 6e21)
+ self.assertAlmostEqual(round2(8.5e21, -21), 9e21)
+
+ self.assertAlmostEqual(round2(-1.5e22, -22), -2e22)
+ self.assertAlmostEqual(round2(-0.5e22, -22), -1e22)
+ self.assertAlmostEqual(round2(0.5e22, -22), 1e22)
+ self.assertAlmostEqual(round2(1.5e22, -22), 2e22)
class Round3Test(unittest.TestCase):
- """ Same as above but results adapted for Python 3 round() """
-
- def test_second_argument_type(self):
- # floats should be illegal
- self.assertRaises(TypeError, round3, 3.14159, 2.0)
-
- # None should be allowed
- self.assertEqual(round3(1.0, None), 1)
- # the following would raise an error with the built-in Python3.5 round:
- # TypeError: 'NoneType' object cannot be interpreted as an integer
- self.assertEqual(round3(1, None), 1)
-
- def test_halfway_cases(self):
- self.assertAlmostEqual(round3(0.125, 2), 0.12)
- self.assertAlmostEqual(round3(0.375, 2), 0.38)
- self.assertAlmostEqual(round3(0.625, 2), 0.62)
- self.assertAlmostEqual(round3(0.875, 2), 0.88)
- self.assertAlmostEqual(round3(-0.125, 2), -0.12)
- self.assertAlmostEqual(round3(-0.375, 2), -0.38)
- self.assertAlmostEqual(round3(-0.625, 2), -0.62)
- self.assertAlmostEqual(round3(-0.875, 2), -0.88)
-
- self.assertAlmostEqual(round3(0.25, 1), 0.2)
- self.assertAlmostEqual(round3(0.75, 1), 0.8)
- self.assertAlmostEqual(round3(-0.25, 1), -0.2)
- self.assertAlmostEqual(round3(-0.75, 1), -0.8)
-
- self.assertEqual(round3(-6.5, 0), -6.0)
- self.assertEqual(round3(-5.5, 0), -6.0)
- self.assertEqual(round3(-1.5, 0), -2.0)
- self.assertEqual(round3(-0.5, 0), 0.0)
- self.assertEqual(round3(0.5, 0), 0.0)
- self.assertEqual(round3(1.5, 0), 2.0)
- self.assertEqual(round3(2.5, 0), 2.0)
- self.assertEqual(round3(3.5, 0), 4.0)
- self.assertEqual(round3(4.5, 0), 4.0)
- self.assertEqual(round3(5.5, 0), 6.0)
- self.assertEqual(round3(6.5, 0), 6.0)
-
- # same but without an explicit second argument; in 2.x these
- # will give floats
- self.assertEqual(round3(-6.5), -6)
- self.assertEqual(round3(-5.5), -6)
- self.assertEqual(round3(-1.5), -2.0)
- self.assertEqual(round3(-0.5), 0)
- self.assertEqual(round3(0.5), 0)
- self.assertEqual(round3(1.5), 2)
- self.assertEqual(round3(2.5), 2)
- self.assertEqual(round3(3.5), 4)
- self.assertEqual(round3(4.5), 4)
- self.assertEqual(round3(5.5), 6)
- self.assertEqual(round3(6.5), 6)
-
- # no ndigits and input is already an integer: output == input
- rv = round3(1)
- self.assertEqual(rv, 1)
- self.assertTrue(isinstance(rv, int))
- rv = round3(1.0)
- self.assertEqual(rv, 1)
- self.assertTrue(isinstance(rv, int))
-
- self.assertEqual(round3(-25.0, -1), -20.0)
- self.assertEqual(round3(-15.0, -1), -20.0)
- self.assertEqual(round3(-5.0, -1), 0.0)
- self.assertEqual(round3(5.0, -1), 0.0)
- self.assertEqual(round3(15.0, -1), 20.0)
- self.assertEqual(round3(25.0, -1), 20.0)
- self.assertEqual(round3(35.0, -1), 40.0)
- self.assertEqual(round3(45.0, -1), 40.0)
- self.assertEqual(round3(55.0, -1), 60.0)
- self.assertEqual(round3(65.0, -1), 60.0)
- self.assertEqual(round3(75.0, -1), 80.0)
- self.assertEqual(round3(85.0, -1), 80.0)
- self.assertEqual(round3(95.0, -1), 100.0)
- self.assertEqual(round3(12325.0, -1), 12320.0)
- self.assertEqual(round3(0, -1), 0.0)
-
- self.assertEqual(round3(350.0, -2), 400.0)
- self.assertEqual(round3(450.0, -2), 400.0)
-
- self.assertAlmostEqual(round3(0.5e21, -21), 0.0)
- self.assertAlmostEqual(round3(1.5e21, -21), 2e21)
- self.assertAlmostEqual(round3(2.5e21, -21), 2e21)
- self.assertAlmostEqual(round3(5.5e21, -21), 6e21)
- self.assertAlmostEqual(round3(8.5e21, -21), 8e21)
-
- self.assertAlmostEqual(round3(-1.5e22, -22), -2e22)
- self.assertAlmostEqual(round3(-0.5e22, -22), 0.0)
- self.assertAlmostEqual(round3(0.5e22, -22), 0.0)
- self.assertAlmostEqual(round3(1.5e22, -22), 2e22)
-
-
-NAN = float('nan')
-INF = float('inf')
-NINF = float('-inf')
+ """Same as above but results adapted for Python 3 round()"""
+
+ def test_second_argument_type(self):
+ # floats should be illegal
+ self.assertRaises(TypeError, round3, 3.14159, 2.0)
+
+ # None should be allowed
+ self.assertEqual(round3(1.0, None), 1)
+ # the following would raise an error with the built-in Python3.5 round:
+ # TypeError: 'NoneType' object cannot be interpreted as an integer
+ self.assertEqual(round3(1, None), 1)
+
+ def test_halfway_cases(self):
+ self.assertAlmostEqual(round3(0.125, 2), 0.12)
+ self.assertAlmostEqual(round3(0.375, 2), 0.38)
+ self.assertAlmostEqual(round3(0.625, 2), 0.62)
+ self.assertAlmostEqual(round3(0.875, 2), 0.88)
+ self.assertAlmostEqual(round3(-0.125, 2), -0.12)
+ self.assertAlmostEqual(round3(-0.375, 2), -0.38)
+ self.assertAlmostEqual(round3(-0.625, 2), -0.62)
+ self.assertAlmostEqual(round3(-0.875, 2), -0.88)
+
+ self.assertAlmostEqual(round3(0.25, 1), 0.2)
+ self.assertAlmostEqual(round3(0.75, 1), 0.8)
+ self.assertAlmostEqual(round3(-0.25, 1), -0.2)
+ self.assertAlmostEqual(round3(-0.75, 1), -0.8)
+
+ self.assertEqual(round3(-6.5, 0), -6.0)
+ self.assertEqual(round3(-5.5, 0), -6.0)
+ self.assertEqual(round3(-1.5, 0), -2.0)
+ self.assertEqual(round3(-0.5, 0), 0.0)
+ self.assertEqual(round3(0.5, 0), 0.0)
+ self.assertEqual(round3(1.5, 0), 2.0)
+ self.assertEqual(round3(2.5, 0), 2.0)
+ self.assertEqual(round3(3.5, 0), 4.0)
+ self.assertEqual(round3(4.5, 0), 4.0)
+ self.assertEqual(round3(5.5, 0), 6.0)
+ self.assertEqual(round3(6.5, 0), 6.0)
+
+ # same but without an explicit second argument; in 2.x these
+ # will give floats
+ self.assertEqual(round3(-6.5), -6)
+ self.assertEqual(round3(-5.5), -6)
+ self.assertEqual(round3(-1.5), -2.0)
+ self.assertEqual(round3(-0.5), 0)
+ self.assertEqual(round3(0.5), 0)
+ self.assertEqual(round3(1.5), 2)
+ self.assertEqual(round3(2.5), 2)
+ self.assertEqual(round3(3.5), 4)
+ self.assertEqual(round3(4.5), 4)
+ self.assertEqual(round3(5.5), 6)
+ self.assertEqual(round3(6.5), 6)
+
+ # no ndigits and input is already an integer: output == input
+ rv = round3(1)
+ self.assertEqual(rv, 1)
+ self.assertTrue(isinstance(rv, int))
+ rv = round3(1.0)
+ self.assertEqual(rv, 1)
+ self.assertTrue(isinstance(rv, int))
+
+ self.assertEqual(round3(-25.0, -1), -20.0)
+ self.assertEqual(round3(-15.0, -1), -20.0)
+ self.assertEqual(round3(-5.0, -1), 0.0)
+ self.assertEqual(round3(5.0, -1), 0.0)
+ self.assertEqual(round3(15.0, -1), 20.0)
+ self.assertEqual(round3(25.0, -1), 20.0)
+ self.assertEqual(round3(35.0, -1), 40.0)
+ self.assertEqual(round3(45.0, -1), 40.0)
+ self.assertEqual(round3(55.0, -1), 60.0)
+ self.assertEqual(round3(65.0, -1), 60.0)
+ self.assertEqual(round3(75.0, -1), 80.0)
+ self.assertEqual(round3(85.0, -1), 80.0)
+ self.assertEqual(round3(95.0, -1), 100.0)
+ self.assertEqual(round3(12325.0, -1), 12320.0)
+ self.assertEqual(round3(0, -1), 0.0)
+
+ self.assertEqual(round3(350.0, -2), 400.0)
+ self.assertEqual(round3(450.0, -2), 400.0)
+
+ self.assertAlmostEqual(round3(0.5e21, -21), 0.0)
+ self.assertAlmostEqual(round3(1.5e21, -21), 2e21)
+ self.assertAlmostEqual(round3(2.5e21, -21), 2e21)
+ self.assertAlmostEqual(round3(5.5e21, -21), 6e21)
+ self.assertAlmostEqual(round3(8.5e21, -21), 8e21)
+
+ self.assertAlmostEqual(round3(-1.5e22, -22), -2e22)
+ self.assertAlmostEqual(round3(-0.5e22, -22), 0.0)
+ self.assertAlmostEqual(round3(0.5e22, -22), 0.0)
+ self.assertAlmostEqual(round3(1.5e22, -22), 2e22)
+
+
+NAN = float("nan")
+INF = float("inf")
+NINF = float("-inf")
class IsCloseTests(unittest.TestCase):
- """
- Tests taken from Python 3.5 test_math.py:
- https://hg.python.org/cpython/file/v3.5.2/Lib/test/test_math.py
- """
- isclose = staticmethod(isclose)
-
- def assertIsClose(self, a, b, *args, **kwargs):
- self.assertTrue(
- self.isclose(a, b, *args, **kwargs),
- msg="%s and %s should be close!" % (a, b))
-
- def assertIsNotClose(self, a, b, *args, **kwargs):
- self.assertFalse(
- self.isclose(a, b, *args, **kwargs),
- msg="%s and %s should not be close!" % (a, b))
-
- def assertAllClose(self, examples, *args, **kwargs):
- for a, b in examples:
- self.assertIsClose(a, b, *args, **kwargs)
-
- def assertAllNotClose(self, examples, *args, **kwargs):
- for a, b in examples:
- self.assertIsNotClose(a, b, *args, **kwargs)
-
- def test_negative_tolerances(self):
- # ValueError should be raised if either tolerance is less than zero
- with self.assertRaises(ValueError):
- self.assertIsClose(1, 1, rel_tol=-1e-100)
- with self.assertRaises(ValueError):
- self.assertIsClose(1, 1, rel_tol=1e-100, abs_tol=-1e10)
-
- def test_identical(self):
- # identical values must test as close
- identical_examples = [
- (2.0, 2.0),
- (0.1e200, 0.1e200),
- (1.123e-300, 1.123e-300),
- (12345, 12345.0),
- (0.0, -0.0),
- (345678, 345678)]
- self.assertAllClose(identical_examples, rel_tol=0.0, abs_tol=0.0)
-
- def test_eight_decimal_places(self):
- # examples that are close to 1e-8, but not 1e-9
- eight_decimal_places_examples = [
- (1e8, 1e8 + 1),
- (-1e-8, -1.000000009e-8),
- (1.12345678, 1.12345679)]
- self.assertAllClose(eight_decimal_places_examples, rel_tol=1e-8)
- self.assertAllNotClose(eight_decimal_places_examples, rel_tol=1e-9)
-
- def test_near_zero(self):
- # values close to zero
- near_zero_examples = [
- (1e-9, 0.0),
- (-1e-9, 0.0),
- (-1e-150, 0.0)]
- # these should not be close to any rel_tol
- self.assertAllNotClose(near_zero_examples, rel_tol=0.9)
- # these should be close to abs_tol=1e-8
- self.assertAllClose(near_zero_examples, abs_tol=1e-8)
-
- def test_identical_infinite(self):
- # these are close regardless of tolerance -- i.e. they are equal
- self.assertIsClose(INF, INF)
- self.assertIsClose(INF, INF, abs_tol=0.0)
- self.assertIsClose(NINF, NINF)
- self.assertIsClose(NINF, NINF, abs_tol=0.0)
-
- def test_inf_ninf_nan(self):
- # these should never be close (following IEEE 754 rules for equality)
- not_close_examples = [
- (NAN, NAN),
- (NAN, 1e-100),
- (1e-100, NAN),
- (INF, NAN),
- (NAN, INF),
- (INF, NINF),
- (INF, 1.0),
- (1.0, INF),
- (INF, 1e308),
- (1e308, INF)]
- # use largest reasonable tolerance
- self.assertAllNotClose(not_close_examples, abs_tol=0.999999999999999)
-
- def test_zero_tolerance(self):
- # test with zero tolerance
- zero_tolerance_close_examples = [
- (1.0, 1.0),
- (-3.4, -3.4),
- (-1e-300, -1e-300)]
- self.assertAllClose(zero_tolerance_close_examples, rel_tol=0.0)
-
- zero_tolerance_not_close_examples = [
- (1.0, 1.000000000000001),
- (0.99999999999999, 1.0),
- (1.0e200, .999999999999999e200)]
- self.assertAllNotClose(zero_tolerance_not_close_examples, rel_tol=0.0)
-
- def test_assymetry(self):
- # test the assymetry example from PEP 485
- self.assertAllClose([(9, 10), (10, 9)], rel_tol=0.1)
-
- def test_integers(self):
- # test with integer values
- integer_examples = [
- (100000001, 100000000),
- (123456789, 123456788)]
-
- self.assertAllClose(integer_examples, rel_tol=1e-8)
- self.assertAllNotClose(integer_examples, rel_tol=1e-9)
-
- def test_decimals(self):
- # test with Decimal values
- from decimal import Decimal
-
- decimal_examples = [
- (Decimal('1.00000001'), Decimal('1.0')),
- (Decimal('1.00000001e-20'), Decimal('1.0e-20')),
- (Decimal('1.00000001e-100'), Decimal('1.0e-100'))]
- self.assertAllClose(decimal_examples, rel_tol=1e-8)
- self.assertAllNotClose(decimal_examples, rel_tol=1e-9)
-
- def test_fractions(self):
- # test with Fraction values
- from fractions import Fraction
-
- # could use some more examples here!
- fraction_examples = [(Fraction(1, 100000000) + 1, Fraction(1))]
- self.assertAllClose(fraction_examples, rel_tol=1e-8)
- self.assertAllNotClose(fraction_examples, rel_tol=1e-9)
+ """
+ Tests taken from Python 3.5 test_math.py:
+ https://hg.python.org/cpython/file/v3.5.2/Lib/test/test_math.py
+ """
+
+ isclose = staticmethod(isclose)
+
+ def assertIsClose(self, a, b, *args, **kwargs):
+ self.assertTrue(
+ self.isclose(a, b, *args, **kwargs),
+ msg="%s and %s should be close!" % (a, b),
+ )
+
+ def assertIsNotClose(self, a, b, *args, **kwargs):
+ self.assertFalse(
+ self.isclose(a, b, *args, **kwargs),
+ msg="%s and %s should not be close!" % (a, b),
+ )
+
+ def assertAllClose(self, examples, *args, **kwargs):
+ for a, b in examples:
+ self.assertIsClose(a, b, *args, **kwargs)
+
+ def assertAllNotClose(self, examples, *args, **kwargs):
+ for a, b in examples:
+ self.assertIsNotClose(a, b, *args, **kwargs)
+
+ def test_negative_tolerances(self):
+ # ValueError should be raised if either tolerance is less than zero
+ with self.assertRaises(ValueError):
+ self.assertIsClose(1, 1, rel_tol=-1e-100)
+ with self.assertRaises(ValueError):
+ self.assertIsClose(1, 1, rel_tol=1e-100, abs_tol=-1e10)
+
+ def test_identical(self):
+ # identical values must test as close
+ identical_examples = [
+ (2.0, 2.0),
+ (0.1e200, 0.1e200),
+ (1.123e-300, 1.123e-300),
+ (12345, 12345.0),
+ (0.0, -0.0),
+ (345678, 345678),
+ ]
+ self.assertAllClose(identical_examples, rel_tol=0.0, abs_tol=0.0)
+
+ def test_eight_decimal_places(self):
+ # examples that are close to 1e-8, but not 1e-9
+ eight_decimal_places_examples = [
+ (1e8, 1e8 + 1),
+ (-1e-8, -1.000000009e-8),
+ (1.12345678, 1.12345679),
+ ]
+ self.assertAllClose(eight_decimal_places_examples, rel_tol=1e-8)
+ self.assertAllNotClose(eight_decimal_places_examples, rel_tol=1e-9)
+
+ def test_near_zero(self):
+ # values close to zero
+ near_zero_examples = [(1e-9, 0.0), (-1e-9, 0.0), (-1e-150, 0.0)]
+ # these should not be close to any rel_tol
+ self.assertAllNotClose(near_zero_examples, rel_tol=0.9)
+ # these should be close to abs_tol=1e-8
+ self.assertAllClose(near_zero_examples, abs_tol=1e-8)
+
+ def test_identical_infinite(self):
+ # these are close regardless of tolerance -- i.e. they are equal
+ self.assertIsClose(INF, INF)
+ self.assertIsClose(INF, INF, abs_tol=0.0)
+ self.assertIsClose(NINF, NINF)
+ self.assertIsClose(NINF, NINF, abs_tol=0.0)
+
+ def test_inf_ninf_nan(self):
+ # these should never be close (following IEEE 754 rules for equality)
+ not_close_examples = [
+ (NAN, NAN),
+ (NAN, 1e-100),
+ (1e-100, NAN),
+ (INF, NAN),
+ (NAN, INF),
+ (INF, NINF),
+ (INF, 1.0),
+ (1.0, INF),
+ (INF, 1e308),
+ (1e308, INF),
+ ]
+ # use largest reasonable tolerance
+ self.assertAllNotClose(not_close_examples, abs_tol=0.999999999999999)
+
+ def test_zero_tolerance(self):
+ # test with zero tolerance
+ zero_tolerance_close_examples = [(1.0, 1.0), (-3.4, -3.4), (-1e-300, -1e-300)]
+ self.assertAllClose(zero_tolerance_close_examples, rel_tol=0.0)
+
+ zero_tolerance_not_close_examples = [
+ (1.0, 1.000000000000001),
+ (0.99999999999999, 1.0),
+ (1.0e200, 0.999999999999999e200),
+ ]
+ self.assertAllNotClose(zero_tolerance_not_close_examples, rel_tol=0.0)
+
+ def test_assymetry(self):
+ # test the assymetry example from PEP 485
+ self.assertAllClose([(9, 10), (10, 9)], rel_tol=0.1)
+
+ def test_integers(self):
+ # test with integer values
+ integer_examples = [(100000001, 100000000), (123456789, 123456788)]
+
+ self.assertAllClose(integer_examples, rel_tol=1e-8)
+ self.assertAllNotClose(integer_examples, rel_tol=1e-9)
+
+ def test_decimals(self):
+ # test with Decimal values
+ from decimal import Decimal
+
+ decimal_examples = [
+ (Decimal("1.00000001"), Decimal("1.0")),
+ (Decimal("1.00000001e-20"), Decimal("1.0e-20")),
+ (Decimal("1.00000001e-100"), Decimal("1.0e-100")),
+ ]
+ self.assertAllClose(decimal_examples, rel_tol=1e-8)
+ self.assertAllNotClose(decimal_examples, rel_tol=1e-9)
+
+ def test_fractions(self):
+ # test with Fraction values
+ from fractions import Fraction
+
+ # could use some more examples here!
+ fraction_examples = [(Fraction(1, 100000000) + 1, Fraction(1))]
+ self.assertAllClose(fraction_examples, rel_tol=1e-8)
+ self.assertAllNotClose(fraction_examples, rel_tol=1e-9)
class TestRedirectStream:
-
redirect_stream = None
orig_stream = None
@@ -441,16 +443,14 @@ class TestRedirectStream:
class TestRedirectStdout(TestRedirectStream, unittest.TestCase):
-
redirect_stream = redirect_stdout
orig_stream = "stdout"
class TestRedirectStderr(TestRedirectStream, unittest.TestCase):
-
redirect_stream = redirect_stderr
orig_stream = "stderr"
if __name__ == "__main__":
- sys.exit(unittest.main())
+ sys.exit(unittest.main())
diff --git a/Tests/misc/testTools_test.py b/Tests/misc/testTools_test.py
index 80d4d2ba..22d79eb7 100644
--- a/Tests/misc/testTools_test.py
+++ b/Tests/misc/testTools_test.py
@@ -3,77 +3,88 @@ import unittest
class TestToolsTest(unittest.TestCase):
-
def test_parseXML_str(self):
- self.assertEqual(testTools.parseXML(
- '<Foo n="1"/>'
- '<Foo n="2">'
- ' some ünıcòðe text'
- ' <Bar color="red"/>'
- ' some more text'
- '</Foo>'
- '<Foo n="3"/>'), [
+ self.assertEqual(
+ testTools.parseXML(
+ '<Foo n="1"/>'
+ '<Foo n="2">'
+ " some ünıcòðe text"
+ ' <Bar color="red"/>'
+ " some more text"
+ "</Foo>"
+ '<Foo n="3"/>'
+ ),
+ [
("Foo", {"n": "1"}, []),
- ("Foo", {"n": "2"}, [
- " some ünıcòðe text ",
- ("Bar", {"color": "red"}, []),
- " some more text",
- ]),
- ("Foo", {"n": "3"}, [])
- ])
+ (
+ "Foo",
+ {"n": "2"},
+ [
+ " some ünıcòðe text ",
+ ("Bar", {"color": "red"}, []),
+ " some more text",
+ ],
+ ),
+ ("Foo", {"n": "3"}, []),
+ ],
+ )
def test_parseXML_bytes(self):
- self.assertEqual(testTools.parseXML(
- b'<Foo n="1"/>'
- b'<Foo n="2">'
- b' some \xc3\xbcn\xc4\xb1c\xc3\xb2\xc3\xb0e text'
- b' <Bar color="red"/>'
- b' some more text'
- b'</Foo>'
- b'<Foo n="3"/>'), [
+ self.assertEqual(
+ testTools.parseXML(
+ b'<Foo n="1"/>'
+ b'<Foo n="2">'
+ b" some \xc3\xbcn\xc4\xb1c\xc3\xb2\xc3\xb0e text"
+ b' <Bar color="red"/>'
+ b" some more text"
+ b"</Foo>"
+ b'<Foo n="3"/>'
+ ),
+ [
("Foo", {"n": "1"}, []),
- ("Foo", {"n": "2"}, [
- " some ünıcòðe text ",
- ("Bar", {"color": "red"}, []),
- " some more text",
- ]),
- ("Foo", {"n": "3"}, [])
- ])
+ (
+ "Foo",
+ {"n": "2"},
+ [
+ " some ünıcòðe text ",
+ ("Bar", {"color": "red"}, []),
+ " some more text",
+ ],
+ ),
+ ("Foo", {"n": "3"}, []),
+ ],
+ )
def test_parseXML_str_list(self):
- self.assertEqual(testTools.parseXML(
- ['<Foo n="1"/>'
- '<Foo n="2"/>']), [
- ("Foo", {"n": "1"}, []),
- ("Foo", {"n": "2"}, [])
- ])
+ self.assertEqual(
+ testTools.parseXML(['<Foo n="1"/>' '<Foo n="2"/>']),
+ [("Foo", {"n": "1"}, []), ("Foo", {"n": "2"}, [])],
+ )
def test_parseXML_bytes_list(self):
- self.assertEqual(testTools.parseXML(
- [b'<Foo n="1"/>'
- b'<Foo n="2"/>']), [
- ("Foo", {"n": "1"}, []),
- ("Foo", {"n": "2"}, [])
- ])
+ self.assertEqual(
+ testTools.parseXML([b'<Foo n="1"/>' b'<Foo n="2"/>']),
+ [("Foo", {"n": "1"}, []), ("Foo", {"n": "2"}, [])],
+ )
def test_getXML(self):
def toXML(writer, ttFont):
writer.simpletag("simple")
writer.newline()
- writer.begintag("tag", attr='value')
+ writer.begintag("tag", attr="value")
writer.newline()
writer.write("hello world")
writer.newline()
writer.endtag("tag")
writer.newline() # toXML always ends with a newline
- self.assertEqual(testTools.getXML(toXML),
- ['<simple/>',
- '<tag attr="value">',
- ' hello world',
- '</tag>'])
+ self.assertEqual(
+ testTools.getXML(toXML),
+ ["<simple/>", '<tag attr="value">', " hello world", "</tag>"],
+ )
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/misc/textTools_test.py b/Tests/misc/textTools_test.py
index f83abf91..f28ca211 100644
--- a/Tests/misc/textTools_test.py
+++ b/Tests/misc/textTools_test.py
@@ -2,8 +2,8 @@ from fontTools.misc.textTools import pad
def test_pad():
- assert len(pad(b'abcd', 4)) == 4
- assert len(pad(b'abcde', 2)) == 6
- assert len(pad(b'abcde', 4)) == 8
- assert pad(b'abcdef', 4) == b'abcdef\x00\x00'
- assert pad(b'abcdef', 1) == b'abcdef'
+ assert len(pad(b"abcd", 4)) == 4
+ assert len(pad(b"abcde", 2)) == 6
+ assert len(pad(b"abcde", 4)) == 8
+ assert pad(b"abcdef", 4) == b"abcdef\x00\x00"
+ assert pad(b"abcdef", 1) == b"abcdef"
diff --git a/Tests/misc/timeTools_test.py b/Tests/misc/timeTools_test.py
index 4d75ce4e..d37e3c6d 100644
--- a/Tests/misc/timeTools_test.py
+++ b/Tests/misc/timeTools_test.py
@@ -1,4 +1,10 @@
-from fontTools.misc.timeTools import asctime, timestampNow, timestampToString, timestampFromString, epoch_diff
+from fontTools.misc.timeTools import (
+ asctime,
+ timestampNow,
+ timestampToString,
+ timestampFromString,
+ epoch_diff,
+)
import os
import time
import locale
@@ -7,7 +13,7 @@ import pytest
def test_asctime():
assert isinstance(asctime(), str)
- assert asctime(time.gmtime(0)) == 'Thu Jan 1 00:00:00 1970'
+ assert asctime(time.gmtime(0)) == "Thu Jan 1 00:00:00 1970"
def test_source_date_epoch():
@@ -27,7 +33,7 @@ def test_source_date_epoch():
def test_date_parsing_with_locale():
l = locale.getlocale(locale.LC_TIME)
try:
- locale.setlocale(locale.LC_TIME, 'de_DE.utf8')
+ locale.setlocale(locale.LC_TIME, "de_DE.utf8")
except locale.Error:
pytest.skip("Locale de_DE not available")
diff --git a/Tests/misc/transform_test.py b/Tests/misc/transform_test.py
index 53d4a202..eaa16678 100644
--- a/Tests/misc/transform_test.py
+++ b/Tests/misc/transform_test.py
@@ -1,10 +1,15 @@
-from fontTools.misc.transform import Transform, Identity, Offset, Scale
+from fontTools.misc.transform import (
+ Transform,
+ Identity,
+ Offset,
+ Scale,
+ DecomposedTransform,
+)
import math
import pytest
class TransformTest(object):
-
def test_examples(self):
t = Transform()
assert repr(t) == "<Transform [1 0 0 1 0 0]>"
@@ -19,9 +24,12 @@ class TransformTest(object):
def test_transformPoints(self):
t = Transform(2, 0, 0, 3, 0, 0)
- assert t.transformPoints(
- [(0, 0), (0, 100), (100, 100), (100, 0)]
- ) == [(0, 0), (0, 300), (200, 300), (200, 0)]
+ assert t.transformPoints([(0, 0), (0, 100), (100, 100), (100, 0)]) == [
+ (0, 0),
+ (0, 300),
+ (200, 300),
+ (200, 0),
+ ]
def test_transformVector(self):
t = Transform(2, 0, 0, 3, -10, 30)
@@ -47,7 +55,8 @@ class TransformTest(object):
assert t.rotate(-math.pi / 2) == Transform(0, -1, 1, 0, 0, 0)
t = Transform()
assert tuple(t.rotate(math.radians(30))) == pytest.approx(
- tuple(Transform(0.866025, 0.5, -0.5, 0.866025, 0, 0)))
+ tuple(Transform(0.866025, 0.5, -0.5, 0.866025, 0, 0))
+ )
def test_skew(self):
t = Transform().skew(math.pi / 4)
@@ -74,7 +83,7 @@ class TransformTest(object):
def test_toPS(self):
t = Transform().scale(2, 3).translate(4, 5)
- assert t.toPS() == '[2 0 0 3 8 15]'
+ assert t.toPS() == "[2 0 0 3 8 15]"
def test__ne__(self):
assert Transform() != Transform(2, 0, 0, 2, 0, 0)
@@ -90,7 +99,7 @@ class TransformTest(object):
assert Transform(1, 0, 0, 1, 1, 0)
def test__repr__(self):
- assert repr(Transform(1, 2, 3, 4, 5, 6)) == '<Transform [1 2 3 4 5 6]>'
+ assert repr(Transform(1, 2, 3, 4, 5, 6)) == "<Transform [1 2 3 4 5 6]>"
def test_Identity(self):
assert isinstance(Identity, Transform)
@@ -105,3 +114,85 @@ class TransformTest(object):
assert Scale(1) == Transform(1, 0, 0, 1, 0, 0)
assert Scale(2) == Transform(2, 0, 0, 2, 0, 0)
assert Scale(1, 2) == Transform(1, 0, 0, 2, 0, 0)
+
+ def test_decompose(self):
+ t = Transform(2, 0, 0, 3, 5, 7)
+ d = t.toDecomposed()
+ assert d.scaleX == 2
+ assert d.scaleY == 3
+ assert d.translateX == 5
+ assert d.translateY == 7
+
+ def test_decompose(self):
+ t = Transform(-1, 0, 0, 1, 0, 0)
+ d = t.toDecomposed()
+ assert d.scaleX == -1
+ assert d.scaleY == 1
+ assert d.rotation == 0
+
+ t = Transform(1, 0, 0, -1, 0, 0)
+ d = t.toDecomposed()
+ assert d.scaleX == 1
+ assert d.scaleY == -1
+ assert d.rotation == 0
+
+
+class DecomposedTransformTest(object):
+ def test_identity(self):
+ t = DecomposedTransform()
+ assert (
+ repr(t)
+ == "DecomposedTransform(translateX=0, translateY=0, rotation=0, scaleX=1, scaleY=1, skewX=0, skewY=0, tCenterX=0, tCenterY=0)"
+ )
+ assert t == DecomposedTransform(scaleX=1.0)
+
+ def test_scale(self):
+ t = DecomposedTransform(scaleX=2, scaleY=3)
+ assert t.scaleX == 2
+ assert t.scaleY == 3
+
+ def test_toTransform(self):
+ t = DecomposedTransform(scaleX=2, scaleY=3)
+ assert t.toTransform() == (2, 0, 0, 3, 0, 0)
+
+ @pytest.mark.parametrize(
+ "decomposed",
+ [
+ DecomposedTransform(scaleX=1, scaleY=0),
+ DecomposedTransform(scaleX=0, scaleY=1),
+ DecomposedTransform(scaleX=1, scaleY=0, rotation=30),
+ DecomposedTransform(scaleX=0, scaleY=1, rotation=30),
+ DecomposedTransform(scaleX=1, scaleY=1),
+ DecomposedTransform(scaleX=-1, scaleY=1),
+ DecomposedTransform(scaleX=1, scaleY=-1),
+ DecomposedTransform(scaleX=-1, scaleY=-1),
+ DecomposedTransform(rotation=90),
+ DecomposedTransform(rotation=-90),
+ DecomposedTransform(skewX=45),
+ DecomposedTransform(skewY=45),
+ DecomposedTransform(scaleX=-1, skewX=45),
+ DecomposedTransform(scaleX=-1, skewY=45),
+ DecomposedTransform(scaleY=-1, skewX=45),
+ DecomposedTransform(scaleY=-1, skewY=45),
+ DecomposedTransform(scaleX=-1, skewX=45, rotation=30),
+ DecomposedTransform(scaleX=-1, skewY=45, rotation=30),
+ DecomposedTransform(scaleY=-1, skewX=45, rotation=30),
+ DecomposedTransform(scaleY=-1, skewY=45, rotation=30),
+ DecomposedTransform(scaleX=-1, skewX=45, rotation=-30),
+ DecomposedTransform(scaleX=-1, skewY=45, rotation=-30),
+ DecomposedTransform(scaleY=-1, skewX=45, rotation=-30),
+ DecomposedTransform(scaleY=-1, skewY=45, rotation=-30),
+ DecomposedTransform(scaleX=-2, skewX=45, rotation=30),
+ DecomposedTransform(scaleX=-2, skewY=45, rotation=30),
+ DecomposedTransform(scaleY=-2, skewX=45, rotation=30),
+ DecomposedTransform(scaleY=-2, skewY=45, rotation=30),
+ DecomposedTransform(scaleX=-2, skewX=45, rotation=-30),
+ DecomposedTransform(scaleX=-2, skewY=45, rotation=-30),
+ DecomposedTransform(scaleY=-2, skewX=45, rotation=-30),
+ DecomposedTransform(scaleY=-2, skewY=45, rotation=-30),
+ ],
+ )
+ def test_roundtrip(lst, decomposed):
+ assert decomposed.toTransform().toDecomposed().toTransform() == pytest.approx(
+ tuple(decomposed.toTransform())
+ ), decomposed
diff --git a/Tests/misc/treeTools_test.py b/Tests/misc/treeTools_test.py
index 467a5c57..be8ffa99 100644
--- a/Tests/misc/treeTools_test.py
+++ b/Tests/misc/treeTools_test.py
@@ -70,7 +70,7 @@ import pytest
(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)),
+ list(range(256**2)),
256,
[list(range(k * 256, k * 256 + 256)) for k in range(256)],
),
diff --git a/Tests/misc/visitor_test.py b/Tests/misc/visitor_test.py
index fe71e08f..268cc716 100644
--- a/Tests/misc/visitor_test.py
+++ b/Tests/misc/visitor_test.py
@@ -8,6 +8,7 @@ class E(enum.Enum):
E2 = 2
E3 = 3
+
class A:
def __init__(self):
self.a = 1
diff --git a/Tests/misc/xmlReader_test.py b/Tests/misc/xmlReader_test.py
index ec4aff57..1f06e1ea 100644
--- a/Tests/misc/xmlReader_test.py
+++ b/Tests/misc/xmlReader_test.py
@@ -8,24 +8,21 @@ import tempfile
class TestXMLReader(unittest.TestCase):
-
- def test_decode_utf8(self):
-
- class DebugXMLReader(XMLReader):
-
- def __init__(self, fileOrPath, ttFont, progress=None):
- super(DebugXMLReader, self).__init__(
- fileOrPath, ttFont, progress)
- self.contents = []
-
- def _endElementHandler(self, name):
- if self.stackSize == 3:
- name, attrs, content = self.root
- self.contents.append(content)
- super(DebugXMLReader, self)._endElementHandler(name)
-
- expected = 'fôôbär'
- data = '''\
+ def test_decode_utf8(self):
+ class DebugXMLReader(XMLReader):
+ def __init__(self, fileOrPath, ttFont, progress=None):
+ super(DebugXMLReader, self).__init__(fileOrPath, ttFont, progress)
+ self.contents = []
+
+ def _endElementHandler(self, name):
+ if self.stackSize == 3:
+ name, attrs, content = self.root
+ self.contents.append(content)
+ super(DebugXMLReader, self)._endElementHandler(name)
+
+ expected = "fôôbär"
+ data = (
+ """\
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<name>
@@ -34,155 +31,157 @@ class TestXMLReader(unittest.TestCase):
</namerecord>
</name>
</ttFont>
-''' % expected
-
- with BytesIO(data.encode('utf-8')) as tmp:
- reader = DebugXMLReader(tmp, TTFont())
- reader.read()
- content = strjoin(reader.contents[0]).strip()
- self.assertEqual(expected, content)
-
- def test_normalise_newlines(self):
-
- class DebugXMLReader(XMLReader):
-
- def __init__(self, fileOrPath, ttFont, progress=None):
- super(DebugXMLReader, self).__init__(
- fileOrPath, ttFont, progress)
- self.newlines = []
-
- def _characterDataHandler(self, data):
- self.newlines.extend([c for c in data if c in ('\r', '\n')])
-
- # notice how when CR is escaped, it is not normalised by the XML parser
- data = (
- '<ttFont>\r' # \r -> \n
- ' <test>\r\n' # \r\n -> \n
- ' a line of text\n' # \n
- ' escaped CR and unix newline &#13;\n' # &#13;\n -> \r\n
- ' escaped CR and macintosh newline &#13;\r' # &#13;\r -> \r\n
- ' escaped CR and windows newline &#13;\r\n' # &#13;\r\n -> \r\n
- ' </test>\n' # \n
- '</ttFont>')
-
- with BytesIO(data.encode('utf-8')) as tmp:
- reader = DebugXMLReader(tmp, TTFont())
- reader.read()
- expected = ['\n'] * 3 + ['\r', '\n'] * 3 + ['\n']
- self.assertEqual(expected, reader.newlines)
-
- def test_progress(self):
-
- class DummyProgressPrinter(ProgressPrinter):
-
- def __init__(self, title, maxval=100):
- self.label = title
- self.maxval = maxval
- self.pos = 0
-
- def set(self, val, maxval=None):
- if maxval is not None:
- self.maxval = maxval
- self.pos = val
-
- def increment(self, val=1):
- self.pos += val
-
- def setLabel(self, text):
- self.label = text
-
- data = (
- '<ttFont>\n'
- ' <test>\n'
- ' %s\n'
- ' </test>\n'
- '</ttFont>\n'
- % ("z" * 2 * BUFSIZE)
- ).encode('utf-8')
-
- dataSize = len(data)
- progressBar = DummyProgressPrinter('test')
- with BytesIO(data) as tmp:
- reader = XMLReader(tmp, TTFont(), progress=progressBar)
- self.assertEqual(progressBar.pos, 0)
- reader.read()
- self.assertEqual(progressBar.pos, dataSize // 100)
- self.assertEqual(progressBar.maxval, dataSize // 100)
- self.assertTrue('test' in progressBar.label)
- with BytesIO(b"<ttFont></ttFont>") as tmp:
- reader = XMLReader(tmp, TTFont(), progress=progressBar)
- reader.read()
- # when data size is less than 100 bytes, 'maxval' is 1
- self.assertEqual(progressBar.maxval, 1)
-
- def test_close_file_path(self):
- with tempfile.NamedTemporaryFile(delete=False) as tmp:
- tmp.write(b'<ttFont></ttFont>')
- reader = XMLReader(tmp.name, TTFont())
- reader.read()
- # when reading from path, the file is closed automatically at the end
- self.assertTrue(reader.file.closed)
- # this does nothing
- reader.close()
- self.assertTrue(reader.file.closed)
- os.remove(tmp.name)
-
- def test_close_file_obj(self):
- with tempfile.NamedTemporaryFile(delete=False) as tmp:
- tmp.write(b'<ttFont>"hello"</ttFont>')
- with open(tmp.name, "rb") as f:
- reader = XMLReader(f, TTFont())
- reader.read()
- # when reading from a file or file-like object, the latter is kept open
- self.assertFalse(reader.file.closed)
- # ... until the user explicitly closes it
- reader.close()
- self.assertTrue(reader.file.closed)
- os.remove(tmp.name)
-
- def test_read_sub_file(self):
- # Verifies that sub-file content is able to be read to a table.
- expectedContent = 'testContent'
- expectedNameID = '1'
- expectedPlatform = '3'
- expectedLangId = '0x409'
-
- with tempfile.NamedTemporaryFile(delete=False) as tmp:
- subFileData = (
- '<ttFont ttLibVersion="3.15">'
- '<name>'
- '<namerecord nameID="%s" platformID="%s" platEncID="1" langID="%s">'
- '%s'
- '</namerecord>'
- '</name>'
- '</ttFont>'
- ) % (expectedNameID, expectedPlatform, expectedLangId, expectedContent)
- tmp.write(subFileData.encode("utf-8"))
-
- with tempfile.NamedTemporaryFile(delete=False) as tmp2:
- fileData = (
- '<ttFont ttLibVersion="3.15">'
- '<name>'
- '<namerecord src="%s"/>'
- '</name>'
- '</ttFont>'
- ) % tmp.name
- tmp2.write(fileData.encode('utf-8'))
-
- ttf = TTFont()
- with open(tmp2.name, "rb") as f:
- reader = XMLReader(f, ttf)
- reader.read()
- reader.close()
- nameTable = ttf['name']
- self.assertTrue(int(expectedNameID) == nameTable.names[0].nameID)
- self.assertTrue(int(expectedLangId, 16) == nameTable.names[0].langID)
- self.assertTrue(int(expectedPlatform) == nameTable.names[0].platformID)
- self.assertEqual(expectedContent, nameTable.names[0].string.decode(nameTable.names[0].getEncoding()))
-
- os.remove(tmp.name)
- os.remove(tmp2.name)
-
-if __name__ == '__main__':
- import sys
- sys.exit(unittest.main())
+"""
+ % expected
+ )
+
+ with BytesIO(data.encode("utf-8")) as tmp:
+ reader = DebugXMLReader(tmp, TTFont())
+ reader.read()
+ content = strjoin(reader.contents[0]).strip()
+ self.assertEqual(expected, content)
+
+ def test_normalise_newlines(self):
+ class DebugXMLReader(XMLReader):
+ def __init__(self, fileOrPath, ttFont, progress=None):
+ super(DebugXMLReader, self).__init__(fileOrPath, ttFont, progress)
+ self.newlines = []
+
+ def _characterDataHandler(self, data):
+ self.newlines.extend([c for c in data if c in ("\r", "\n")])
+
+ # notice how when CR is escaped, it is not normalised by the XML parser
+ data = (
+ "<ttFont>\r" # \r -> \n
+ " <test>\r\n" # \r\n -> \n
+ " a line of text\n" # \n
+ " escaped CR and unix newline &#13;\n" # &#13;\n -> \r\n
+ " escaped CR and macintosh newline &#13;\r" # &#13;\r -> \r\n
+ " escaped CR and windows newline &#13;\r\n" # &#13;\r\n -> \r\n
+ " </test>\n" # \n
+ "</ttFont>"
+ )
+
+ with BytesIO(data.encode("utf-8")) as tmp:
+ reader = DebugXMLReader(tmp, TTFont())
+ reader.read()
+ expected = ["\n"] * 3 + ["\r", "\n"] * 3 + ["\n"]
+ self.assertEqual(expected, reader.newlines)
+
+ def test_progress(self):
+ class DummyProgressPrinter(ProgressPrinter):
+ def __init__(self, title, maxval=100):
+ self.label = title
+ self.maxval = maxval
+ self.pos = 0
+
+ def set(self, val, maxval=None):
+ if maxval is not None:
+ self.maxval = maxval
+ self.pos = val
+
+ def increment(self, val=1):
+ self.pos += val
+
+ def setLabel(self, text):
+ self.label = text
+
+ data = (
+ "<ttFont>\n"
+ " <test>\n"
+ " %s\n"
+ " </test>\n"
+ "</ttFont>\n" % ("z" * 2 * BUFSIZE)
+ ).encode("utf-8")
+
+ dataSize = len(data)
+ progressBar = DummyProgressPrinter("test")
+ with BytesIO(data) as tmp:
+ reader = XMLReader(tmp, TTFont(), progress=progressBar)
+ self.assertEqual(progressBar.pos, 0)
+ reader.read()
+ self.assertEqual(progressBar.pos, dataSize // 100)
+ self.assertEqual(progressBar.maxval, dataSize // 100)
+ self.assertTrue("test" in progressBar.label)
+ with BytesIO(b"<ttFont></ttFont>") as tmp:
+ reader = XMLReader(tmp, TTFont(), progress=progressBar)
+ reader.read()
+ # when data size is less than 100 bytes, 'maxval' is 1
+ self.assertEqual(progressBar.maxval, 1)
+
+ def test_close_file_path(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
+ tmp.write(b"<ttFont></ttFont>")
+ reader = XMLReader(tmp.name, TTFont())
+ reader.read()
+ # when reading from path, the file is closed automatically at the end
+ self.assertTrue(reader.file.closed)
+ # this does nothing
+ reader.close()
+ self.assertTrue(reader.file.closed)
+ os.remove(tmp.name)
+
+ def test_close_file_obj(self):
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
+ tmp.write(b'<ttFont>"hello"</ttFont>')
+ with open(tmp.name, "rb") as f:
+ reader = XMLReader(f, TTFont())
+ reader.read()
+ # when reading from a file or file-like object, the latter is kept open
+ self.assertFalse(reader.file.closed)
+ # ... until the user explicitly closes it
+ reader.close()
+ self.assertTrue(reader.file.closed)
+ os.remove(tmp.name)
+
+ def test_read_sub_file(self):
+ # Verifies that sub-file content is able to be read to a table.
+ expectedContent = "testContent"
+ expectedNameID = "1"
+ expectedPlatform = "3"
+ expectedLangId = "0x409"
+
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
+ subFileData = (
+ '<ttFont ttLibVersion="3.15">'
+ "<name>"
+ '<namerecord nameID="%s" platformID="%s" platEncID="1" langID="%s">'
+ "%s"
+ "</namerecord>"
+ "</name>"
+ "</ttFont>"
+ ) % (expectedNameID, expectedPlatform, expectedLangId, expectedContent)
+ tmp.write(subFileData.encode("utf-8"))
+
+ with tempfile.NamedTemporaryFile(delete=False) as tmp2:
+ fileData = (
+ '<ttFont ttLibVersion="3.15">'
+ "<name>"
+ '<namerecord src="%s"/>'
+ "</name>"
+ "</ttFont>"
+ ) % tmp.name
+ tmp2.write(fileData.encode("utf-8"))
+
+ ttf = TTFont()
+ with open(tmp2.name, "rb") as f:
+ reader = XMLReader(f, ttf)
+ reader.read()
+ reader.close()
+ nameTable = ttf["name"]
+ self.assertTrue(int(expectedNameID) == nameTable.names[0].nameID)
+ self.assertTrue(int(expectedLangId, 16) == nameTable.names[0].langID)
+ self.assertTrue(int(expectedPlatform) == nameTable.names[0].platformID)
+ self.assertEqual(
+ expectedContent,
+ nameTable.names[0].string.decode(nameTable.names[0].getEncoding()),
+ )
+
+ os.remove(tmp.name)
+ os.remove(tmp2.name)
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/misc/xmlWriter_test.py b/Tests/misc/xmlWriter_test.py
index 69471543..c1e65171 100644
--- a/Tests/misc/xmlWriter_test.py
+++ b/Tests/misc/xmlWriter_test.py
@@ -6,122 +6,146 @@ from fontTools.misc.xmlWriter import XMLWriter
HEADER = b'<?xml version="1.0" encoding="UTF-8"?>\n'
-class TestXMLWriter(unittest.TestCase):
- def test_comment_escaped(self):
- writer = XMLWriter(BytesIO())
- writer.comment("This&that are <comments>")
- self.assertEqual(HEADER + b"<!-- This&amp;that are &lt;comments&gt; -->", writer.file.getvalue())
-
- def test_comment_multiline(self):
- writer = XMLWriter(BytesIO())
- writer.comment("Hello world\nHow are you?")
- self.assertEqual(HEADER + b"<!-- Hello world\n How are you? -->",
- writer.file.getvalue())
-
- def test_encoding_default(self):
- writer = XMLWriter(BytesIO())
- self.assertEqual(b'<?xml version="1.0" encoding="UTF-8"?>\n',
- writer.file.getvalue())
-
- def test_encoding_utf8(self):
- # https://github.com/fonttools/fonttools/issues/246
- writer = XMLWriter(BytesIO(), encoding="utf8")
- self.assertEqual(b'<?xml version="1.0" encoding="UTF-8"?>\n',
- writer.file.getvalue())
-
- def test_encoding_UTF_8(self):
- # https://github.com/fonttools/fonttools/issues/246
- writer = XMLWriter(BytesIO(), encoding="UTF-8")
- self.assertEqual(b'<?xml version="1.0" encoding="UTF-8"?>\n',
- writer.file.getvalue())
-
- def test_encoding_UTF8(self):
- # https://github.com/fonttools/fonttools/issues/246
- writer = XMLWriter(BytesIO(), encoding="UTF8")
- self.assertEqual(b'<?xml version="1.0" encoding="UTF-8"?>\n',
- writer.file.getvalue())
-
- def test_encoding_other(self):
- self.assertRaises(Exception, XMLWriter, BytesIO(),
- encoding="iso-8859-1")
-
- def test_write(self):
- writer = XMLWriter(BytesIO())
- writer.write("foo&bar")
- self.assertEqual(HEADER + b"foo&amp;bar", writer.file.getvalue())
-
- def test_indent_dedent(self):
- writer = XMLWriter(BytesIO())
- writer.write("foo")
- writer.newline()
- writer.indent()
- writer.write("bar")
- writer.newline()
- writer.dedent()
- writer.write("baz")
- self.assertEqual(HEADER + bytesjoin(["foo", " bar", "baz"], "\n"),
- writer.file.getvalue())
-
- def test_writecdata(self):
- writer = XMLWriter(BytesIO())
- writer.writecdata("foo&bar")
- self.assertEqual(HEADER + b"<![CDATA[foo&bar]]>", writer.file.getvalue())
-
- def test_simpletag(self):
- writer = XMLWriter(BytesIO())
- writer.simpletag("tag", a="1", b="2")
- self.assertEqual(HEADER + b'<tag a="1" b="2"/>', writer.file.getvalue())
-
- def test_begintag_endtag(self):
- writer = XMLWriter(BytesIO())
- writer.begintag("tag", attr="value")
- writer.write("content")
- writer.endtag("tag")
- self.assertEqual(HEADER + b'<tag attr="value">content</tag>', writer.file.getvalue())
-
- def test_dumphex(self):
- writer = XMLWriter(BytesIO())
- writer.dumphex("Type is a beautiful group of letters, not a group of beautiful letters.")
- self.assertEqual(HEADER + bytesjoin([
- "54797065 20697320 61206265 61757469",
- "66756c20 67726f75 70206f66 206c6574",
- "74657273 2c206e6f 74206120 67726f75",
- "70206f66 20626561 75746966 756c206c",
- "65747465 72732e ", ""], joiner="\n"), writer.file.getvalue())
-
- def test_stringifyattrs(self):
- writer = XMLWriter(BytesIO())
- expected = ' attr="0"'
- self.assertEqual(expected, writer.stringifyattrs(attr=0))
- self.assertEqual(expected, writer.stringifyattrs(attr=b'0'))
- self.assertEqual(expected, writer.stringifyattrs(attr='0'))
- self.assertEqual(expected, writer.stringifyattrs(attr=u'0'))
-
- def test_carriage_return_escaped(self):
- writer = XMLWriter(BytesIO())
- writer.write("two lines\r\nseparated by Windows line endings")
- self.assertEqual(
- HEADER + b'two lines&#13;\nseparated by Windows line endings',
- writer.file.getvalue())
-
- def test_newlinestr(self):
- header = b'<?xml version="1.0" encoding="UTF-8"?>'
-
- for nls in (None, '\n', '\r\n', '\r', ''):
- writer = XMLWriter(BytesIO(), newlinestr=nls)
- writer.write("hello")
- writer.newline()
- writer.write("world")
- writer.newline()
-
- linesep = tobytes(os.linesep) if nls is None else tobytes(nls)
-
- self.assertEqual(
- header + linesep + b"hello" + linesep + b"world" + linesep,
- writer.file.getvalue())
-
-
-if __name__ == '__main__':
- import sys
- sys.exit(unittest.main())
+class TestXMLWriter(unittest.TestCase):
+ def test_comment_escaped(self):
+ writer = XMLWriter(BytesIO())
+ writer.comment("This&that are <comments>")
+ self.assertEqual(
+ HEADER + b"<!-- This&amp;that are &lt;comments&gt; -->",
+ writer.file.getvalue(),
+ )
+
+ def test_comment_multiline(self):
+ writer = XMLWriter(BytesIO())
+ writer.comment("Hello world\nHow are you?")
+ self.assertEqual(
+ HEADER + b"<!-- Hello world\n How are you? -->", writer.file.getvalue()
+ )
+
+ def test_encoding_default(self):
+ writer = XMLWriter(BytesIO())
+ self.assertEqual(
+ b'<?xml version="1.0" encoding="UTF-8"?>\n', writer.file.getvalue()
+ )
+
+ def test_encoding_utf8(self):
+ # https://github.com/fonttools/fonttools/issues/246
+ writer = XMLWriter(BytesIO(), encoding="utf8")
+ self.assertEqual(
+ b'<?xml version="1.0" encoding="UTF-8"?>\n', writer.file.getvalue()
+ )
+
+ def test_encoding_UTF_8(self):
+ # https://github.com/fonttools/fonttools/issues/246
+ writer = XMLWriter(BytesIO(), encoding="UTF-8")
+ self.assertEqual(
+ b'<?xml version="1.0" encoding="UTF-8"?>\n', writer.file.getvalue()
+ )
+
+ def test_encoding_UTF8(self):
+ # https://github.com/fonttools/fonttools/issues/246
+ writer = XMLWriter(BytesIO(), encoding="UTF8")
+ self.assertEqual(
+ b'<?xml version="1.0" encoding="UTF-8"?>\n', writer.file.getvalue()
+ )
+
+ def test_encoding_other(self):
+ self.assertRaises(Exception, XMLWriter, BytesIO(), encoding="iso-8859-1")
+
+ def test_write(self):
+ writer = XMLWriter(BytesIO())
+ writer.write("foo&bar")
+ self.assertEqual(HEADER + b"foo&amp;bar", writer.file.getvalue())
+
+ def test_indent_dedent(self):
+ writer = XMLWriter(BytesIO())
+ writer.write("foo")
+ writer.newline()
+ writer.indent()
+ writer.write("bar")
+ writer.newline()
+ writer.dedent()
+ writer.write("baz")
+ self.assertEqual(
+ HEADER + bytesjoin(["foo", " bar", "baz"], "\n"), writer.file.getvalue()
+ )
+
+ def test_writecdata(self):
+ writer = XMLWriter(BytesIO())
+ writer.writecdata("foo&bar")
+ self.assertEqual(HEADER + b"<![CDATA[foo&bar]]>", writer.file.getvalue())
+
+ def test_simpletag(self):
+ writer = XMLWriter(BytesIO())
+ writer.simpletag("tag", a="1", b="2")
+ self.assertEqual(HEADER + b'<tag a="1" b="2"/>', writer.file.getvalue())
+
+ def test_begintag_endtag(self):
+ writer = XMLWriter(BytesIO())
+ writer.begintag("tag", attr="value")
+ writer.write("content")
+ writer.endtag("tag")
+ self.assertEqual(
+ HEADER + b'<tag attr="value">content</tag>', writer.file.getvalue()
+ )
+
+ def test_dumphex(self):
+ writer = XMLWriter(BytesIO())
+ writer.dumphex(
+ "Type is a beautiful group of letters, not a group of beautiful letters."
+ )
+ self.assertEqual(
+ HEADER
+ + bytesjoin(
+ [
+ "54797065 20697320 61206265 61757469",
+ "66756c20 67726f75 70206f66 206c6574",
+ "74657273 2c206e6f 74206120 67726f75",
+ "70206f66 20626561 75746966 756c206c",
+ "65747465 72732e ",
+ "",
+ ],
+ joiner="\n",
+ ),
+ writer.file.getvalue(),
+ )
+
+ def test_stringifyattrs(self):
+ writer = XMLWriter(BytesIO())
+ expected = ' attr="0"'
+ self.assertEqual(expected, writer.stringifyattrs(attr=0))
+ self.assertEqual(expected, writer.stringifyattrs(attr=b"0"))
+ self.assertEqual(expected, writer.stringifyattrs(attr="0"))
+ self.assertEqual(expected, writer.stringifyattrs(attr="0"))
+
+ def test_carriage_return_escaped(self):
+ writer = XMLWriter(BytesIO())
+ writer.write("two lines\r\nseparated by Windows line endings")
+ self.assertEqual(
+ HEADER + b"two lines&#13;\nseparated by Windows line endings",
+ writer.file.getvalue(),
+ )
+
+ def test_newlinestr(self):
+ header = b'<?xml version="1.0" encoding="UTF-8"?>'
+
+ for nls in (None, "\n", "\r\n", "\r", ""):
+ writer = XMLWriter(BytesIO(), newlinestr=nls)
+ writer.write("hello")
+ writer.newline()
+ writer.write("world")
+ writer.newline()
+
+ linesep = tobytes(os.linesep) if nls is None else tobytes(nls)
+
+ self.assertEqual(
+ header + linesep + b"hello" + linesep + b"world" + linesep,
+ writer.file.getvalue(),
+ )
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/mtiLib/data/featurename-backward.ttx.GSUB b/Tests/mtiLib/data/featurename-backward.ttx.GSUB
index cc893cd9..0fbe51fd 100644
--- a/Tests/mtiLib/data/featurename-backward.ttx.GSUB
+++ b/Tests/mtiLib/data/featurename-backward.ttx.GSUB
@@ -47,6 +47,7 @@
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- l1: -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/featurename-forward.ttx.GSUB b/Tests/mtiLib/data/featurename-forward.ttx.GSUB
index cc893cd9..0fbe51fd 100644
--- a/Tests/mtiLib/data/featurename-forward.ttx.GSUB
+++ b/Tests/mtiLib/data/featurename-forward.ttx.GSUB
@@ -47,6 +47,7 @@
</FeatureList>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- l1: -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB b/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB
index cb358d7c..811f79a0 100644
--- a/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB
+++ b/Tests/mtiLib/data/lookupnames-backward.ttx.GSUB
@@ -35,6 +35,7 @@
</FeatureList>
<LookupList>
<!-- LookupCount=2 -->
+ <!-- l1: -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
@@ -44,6 +45,7 @@
<Substitution in="uvowelsignkannada" out="uvowelsignaltkannada"/>
</SingleSubst>
</Lookup>
+ <!-- l0: -->
<Lookup index="1">
<LookupType value="6"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB b/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB
index 249d605b..86b3148f 100644
--- a/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB
+++ b/Tests/mtiLib/data/lookupnames-forward.ttx.GSUB
@@ -35,6 +35,7 @@
</FeatureList>
<LookupList>
<!-- LookupCount=2 -->
+ <!-- l0: -->
<Lookup index="0">
<LookupType value="6"/>
<LookupFlag value="0"/>
@@ -74,6 +75,7 @@
</ChainSubClassSet>
</ChainContextSubst>
</Lookup>
+ <!-- l1: -->
<Lookup index="1">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB b/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB
index 249d605b..74192a51 100644
--- a/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB
+++ b/Tests/mtiLib/data/mixed-toplevels.ttx.GSUB
@@ -35,6 +35,7 @@
</FeatureList>
<LookupList>
<!-- LookupCount=2 -->
+ <!-- 0: -->
<Lookup index="0">
<LookupType value="6"/>
<LookupFlag value="0"/>
@@ -74,6 +75,7 @@
</ChainSubClassSet>
</ChainContextSubst>
</Lookup>
+ <!-- 1: -->
<Lookup index="1">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS b/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS
index b550c700..32fffc4b 100644
--- a/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/chained-glyph.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=2 -->
+ <!-- raucontext-sinh: -->
<Lookup index="0">
<LookupType value="8"/>
<LookupFlag value="512"/><!-- markAttachmentType[2] -->
@@ -43,6 +44,7 @@
</ChainPosRuleSet>
</ChainContextPos>
</Lookup>
+ <!-- u2aelow-sinh: -->
<Lookup index="1" empty="1"/>
</LookupList>
</GPOS>
diff --git a/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB b/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB
index 7dfdb848..30a15304 100644
--- a/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/chained-glyph.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=2 -->
+ <!-- raucontext-sinh: -->
<Lookup index="0">
<LookupType value="6"/>
<LookupFlag value="512"/><!-- markAttachmentType[2] -->
@@ -43,6 +44,7 @@
</ChainSubRuleSet>
</ChainContextSubst>
</Lookup>
+ <!-- u2aelow-sinh: -->
<Lookup index="1" empty="1"/>
</LookupList>
</GSUB>
diff --git a/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB b/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB
index fcd7569f..39691c44 100644
--- a/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/chainedclass.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=2 -->
+ <!-- swashes-knda: -->
<Lookup index="0">
<LookupType value="6"/>
<LookupFlag value="0"/>
@@ -42,6 +43,7 @@
</ChainSubClassSet>
</ChainContextSubst>
</Lookup>
+ <!-- u-swash-knda: -->
<Lookup index="1">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB b/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB
index 4f312c6e..bea53f50 100644
--- a/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/chainedcoverage.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=2 -->
+ <!-- slashcontext: -->
<Lookup index="0">
<LookupType value="6"/>
<LookupFlag value="0"/>
@@ -45,6 +46,7 @@
</SubstLookupRecord>
</ChainContextSubst>
</Lookup>
+ <!-- slashTofraction: -->
<Lookup index="1">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS b/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS
index 6c08c50c..6d92d083 100644
--- a/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gposcursive.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- kernpairs: -->
<Lookup index="0">
<LookupType value="3"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS b/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS
index a8371233..e7a5ff78 100644
--- a/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gposkernset.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- 0: -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS b/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS
index e6e21028..b78d4ff0 100644
--- a/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gposmarktobase.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- topmarktobase-guru: -->
<Lookup index="0">
<LookupType value="4"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS b/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS
index 32b35aee..9058eb0f 100644
--- a/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gpospairclass.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- 0: -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS b/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS
index f03a90e3..58567a97 100644
--- a/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gpospairglyph.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- 0: -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS b/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS
index c3bdbf68..3a955f65 100644
--- a/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/gpossingle.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- supsToInferiors: -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB b/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB
index 86b0b731..7762c621 100644
--- a/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubalternate.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- 27: -->
<Lookup index="0">
<LookupType value="3"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB b/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB
index 26c88c81..5ad20184 100644
--- a/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubligature.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- latinLigatures: -->
<Lookup index="0">
<LookupType value="4"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB b/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB
index 5bedfba6..72eefb87 100644
--- a/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubmultiple.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- replace-akhand-telugu: -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB b/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB
index d705af53..87412eae 100644
--- a/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubreversechanined.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- arabicReverse: -->
<Lookup index="0">
<LookupType value="8"/>
<LookupFlag value="9"/><!-- rightToLeft ignoreMarks -->
diff --git a/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB b/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB
index dc6a2950..adc3ba52 100644
--- a/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB
+++ b/Tests/mtiLib/data/mti/gsubsingle.ttx.GSUB
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- alt-fractions: -->
<Lookup index="0">
<LookupType value="1"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS b/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS
index b5f275eb..f723670c 100644
--- a/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS
+++ b/Tests/mtiLib/data/mti/mark-to-ligature.ttx.GPOS
@@ -3,6 +3,7 @@
<Version value="0x00010000"/>
<LookupList>
<!-- LookupCount=1 -->
+ <!-- LigMk0: -->
<Lookup index="0">
<LookupType value="5"/>
<LookupFlag value="0"/>
diff --git a/Tests/mtiLib/mti_test.py b/Tests/mtiLib/mti_test.py
index 8a80113c..a4cc098c 100644
--- a/Tests/mtiLib/mti_test.py
+++ b/Tests/mtiLib/mti_test.py
@@ -1,120 +1,396 @@
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib import TTFont
+from fontTools.feaLib.lookupDebugInfo import LOOKUP_DEBUG_ENV_VAR
from fontTools import mtiLib
import difflib
from io import StringIO
import os
import sys
-import unittest
-
-
-class MtiTest(unittest.TestCase):
-
- GLYPH_ORDER = ['.notdef',
- 'a', 'b', 'pakannada', 'phakannada', 'vakannada', 'pevowelkannada',
- 'phevowelkannada', 'vevowelkannada', 'uvowelsignkannada', 'uuvowelsignkannada',
- 'uvowelsignaltkannada', 'uuvowelsignaltkannada', 'uuvowelsignsinh',
- 'uvowelsignsinh', 'rakarsinh', 'zero', 'one', 'two', 'three', 'four', 'five',
- 'six', 'seven', 'eight', 'nine', 'slash', 'fraction', 'A', 'B', 'C', 'fi',
- 'fl', 'breve', 'acute', 'uniFB01', 'ffi', 'grave', 'commaacent', 'dotbelow',
- 'dotabove', 'cedilla', 'commaaccent', 'Acircumflex', 'V', 'T', 'acircumflex',
- 'Aacute', 'Agrave', 'O', 'Oacute', 'Ograve', 'Ocircumflex', 'aacute', 'agrave',
- 'aimatrabindigurmukhi', 'aimatragurmukhi', 'aimatratippigurmukhi',
- 'aumatrabindigurmukhi', 'aumatragurmukhi', 'bindigurmukhi',
- 'eematrabindigurmukhi', 'eematragurmukhi', 'eematratippigurmukhi',
- 'oomatrabindigurmukhi', 'oomatragurmukhi', 'oomatratippigurmukhi',
- 'lagurmukhi', 'lanuktagurmukhi', 'nagurmukhi', 'nanuktagurmukhi',
- 'ngagurmukhi', 'nganuktagurmukhi', 'nnagurmukhi', 'nnanuktagurmukhi',
- 'tthagurmukhi', 'tthanuktagurmukhi', 'bsuperior', 'isuperior', 'vsuperior',
- 'wsuperior', 'periodsuperior', 'osuperior', 'tsuperior', 'dollarsuperior',
- 'fsuperior', 'gsuperior', 'zsuperior', 'dsuperior', 'psuperior', 'hsuperior',
- 'oesuperior', 'aesuperior', 'centsuperior', 'esuperior', 'lsuperior',
- 'qsuperior', 'csuperior', 'asuperior', 'commasuperior', 'xsuperior',
- 'egravesuperior', 'usuperior', 'rsuperior', 'nsuperior', 'ssuperior',
- 'msuperior', 'jsuperior', 'ysuperior', 'ksuperior', 'guilsinglright',
- 'guilsinglleft', 'uniF737', 'uniE11C', 'uniE11D', 'uniE11A', 'uni2077',
- 'uni2087', 'uniE11B', 'uniE119', 'uniE0DD', 'uniE0DE', 'uniF736', 'uniE121',
- 'uniE122', 'uniE11F', 'uni2076', 'uni2086', 'uniE120', 'uniE11E', 'uniE0DB',
- 'uniE0DC', 'uniF733', 'uniE12B', 'uniE12C', 'uniE129', 'uni00B3', 'uni2083',
- 'uniE12A', 'uniE128', 'uniF732', 'uniE133', 'uniE134', 'uniE131', 'uni00B2',
- 'uni2082', 'uniE132', 'uniE130', 'uniE0F9', 'uniF734', 'uniE0D4', 'uniE0D5',
- 'uniE0D2', 'uni2074', 'uni2084', 'uniE0D3', 'uniE0D1', 'uniF730', 'uniE13D',
- 'uniE13E', 'uniE13A', 'uni2070', 'uni2080', 'uniE13B', 'uniE139', 'uniE13C',
- 'uniF739', 'uniE0EC', 'uniE0ED', 'uniE0EA', 'uni2079', 'uni2089', 'uniE0EB',
- 'uniE0E9', 'uniF735', 'uniE0CD', 'uniE0CE', 'uniE0CB', 'uni2075', 'uni2085',
- 'uniE0CC', 'uniE0CA', 'uniF731', 'uniE0F3', 'uniE0F4', 'uniE0F1', 'uni00B9',
- 'uni2081', 'uniE0F2', 'uniE0F0', 'uniE0F8', 'uniF738', 'uniE0C0', 'uniE0C1',
- 'uniE0BE', 'uni2078', 'uni2088', 'uniE0BF', 'uniE0BD', 'I', 'Ismall', 't', 'i',
- 'f', 'IJ', 'J', 'IJsmall', 'Jsmall', 'tt', 'ij', 'j', 'ffb', 'ffh', 'h', 'ffk',
- 'k', 'ffl', 'l', 'fft', 'fb', 'ff', 'fh', 'fj', 'fk', 'ft', 'janyevoweltelugu',
- 'kassevoweltelugu', 'jaivoweltelugu', 'nyasubscripttelugu', 'kaivoweltelugu',
- 'ssasubscripttelugu', 'bayi1', 'jeemi1', 'kafi1', 'ghafi1', 'laami1', 'kafm1',
- 'ghafm1', 'laamm1', 'rayf2', 'reyf2', 'yayf2', 'zayf2', 'fayi1', 'ayehf2',
- 'hamzayeharabf2', 'hamzayehf2', 'yehf2', 'ray', 'rey', 'zay', 'yay', 'dal',
- 'del', 'zal', 'rayf1', 'reyf1', 'yayf1', 'zayf1', 'ayehf1', 'hamzayeharabf1',
- 'hamzayehf1', 'yehf1', 'dal1', 'del1', 'zal1', 'onehalf', 'onehalf.alt',
- 'onequarter', 'onequarter.alt', 'threequarters', 'threequarters.alt',
- 'AlefSuperiorNS', 'DammaNS', 'DammaRflxNS', 'DammatanNS', 'Fatha2dotsNS',
- 'FathaNS', 'FathatanNS', 'FourDotsAboveNS', 'HamzaAboveNS', 'MaddaNS',
- 'OneDotAbove2NS', 'OneDotAboveNS', 'ShaddaAlefNS', 'ShaddaDammaNS',
- 'ShaddaDammatanNS', 'ShaddaFathatanNS', 'ShaddaKasraNS', 'ShaddaKasratanNS',
- 'ShaddaNS', 'SharetKafNS', 'SukunNS', 'ThreeDotsDownAboveNS',
- 'ThreeDotsUpAboveNS', 'TwoDotsAboveNS', 'TwoDotsVerticalAboveNS', 'UltapeshNS',
- 'WaslaNS', 'AinIni.12m_MeemFin.02', 'AinIni_YehBarreeFin',
- 'AinMed_YehBarreeFin', 'BehxIni_MeemFin', 'BehxIni_NoonGhunnaFin',
- 'BehxIni_RehFin', 'BehxIni_RehFin.b', 'BehxMed_MeemFin.py',
- 'BehxMed_NoonGhunnaFin', 'BehxMed_NoonGhunnaFin.cup', 'BehxMed_RehFin',
- 'BehxMed_RehFin.cup', 'BehxMed_YehxFin', 'FehxMed_YehBarreeFin',
- 'HahIni_YehBarreeFin', 'KafIni_YehBarreeFin', 'KafMed.12_YehxFin.01',
- 'KafMed_MeemFin', 'KafMed_YehBarreeFin', 'LamAlefFin', 'LamAlefFin.cup',
- 'LamAlefFin.cut', 'LamAlefFin.short', 'LamAlefSep', 'LamIni_MeemFin',
- 'LamIni_YehBarreeFin', 'LamMed_MeemFin', 'LamMed_MeemFin.b', 'LamMed_YehxFin',
- 'LamMed_YehxFin.cup', 'TahIni_YehBarreeFin', 'null', 'CR', 'space',
- 'exclam', 'quotedbl', 'numbersign',
+import pytest
+
+
+@pytest.fixture(autouse=True)
+def set_lookup_debug_env_var(monkeypatch):
+ monkeypatch.setenv(LOOKUP_DEBUG_ENV_VAR, "1")
+
+
+class MtiTest:
+ GLYPH_ORDER = [
+ ".notdef",
+ "a",
+ "b",
+ "pakannada",
+ "phakannada",
+ "vakannada",
+ "pevowelkannada",
+ "phevowelkannada",
+ "vevowelkannada",
+ "uvowelsignkannada",
+ "uuvowelsignkannada",
+ "uvowelsignaltkannada",
+ "uuvowelsignaltkannada",
+ "uuvowelsignsinh",
+ "uvowelsignsinh",
+ "rakarsinh",
+ "zero",
+ "one",
+ "two",
+ "three",
+ "four",
+ "five",
+ "six",
+ "seven",
+ "eight",
+ "nine",
+ "slash",
+ "fraction",
+ "A",
+ "B",
+ "C",
+ "fi",
+ "fl",
+ "breve",
+ "acute",
+ "uniFB01",
+ "ffi",
+ "grave",
+ "commaacent",
+ "dotbelow",
+ "dotabove",
+ "cedilla",
+ "commaaccent",
+ "Acircumflex",
+ "V",
+ "T",
+ "acircumflex",
+ "Aacute",
+ "Agrave",
+ "O",
+ "Oacute",
+ "Ograve",
+ "Ocircumflex",
+ "aacute",
+ "agrave",
+ "aimatrabindigurmukhi",
+ "aimatragurmukhi",
+ "aimatratippigurmukhi",
+ "aumatrabindigurmukhi",
+ "aumatragurmukhi",
+ "bindigurmukhi",
+ "eematrabindigurmukhi",
+ "eematragurmukhi",
+ "eematratippigurmukhi",
+ "oomatrabindigurmukhi",
+ "oomatragurmukhi",
+ "oomatratippigurmukhi",
+ "lagurmukhi",
+ "lanuktagurmukhi",
+ "nagurmukhi",
+ "nanuktagurmukhi",
+ "ngagurmukhi",
+ "nganuktagurmukhi",
+ "nnagurmukhi",
+ "nnanuktagurmukhi",
+ "tthagurmukhi",
+ "tthanuktagurmukhi",
+ "bsuperior",
+ "isuperior",
+ "vsuperior",
+ "wsuperior",
+ "periodsuperior",
+ "osuperior",
+ "tsuperior",
+ "dollarsuperior",
+ "fsuperior",
+ "gsuperior",
+ "zsuperior",
+ "dsuperior",
+ "psuperior",
+ "hsuperior",
+ "oesuperior",
+ "aesuperior",
+ "centsuperior",
+ "esuperior",
+ "lsuperior",
+ "qsuperior",
+ "csuperior",
+ "asuperior",
+ "commasuperior",
+ "xsuperior",
+ "egravesuperior",
+ "usuperior",
+ "rsuperior",
+ "nsuperior",
+ "ssuperior",
+ "msuperior",
+ "jsuperior",
+ "ysuperior",
+ "ksuperior",
+ "guilsinglright",
+ "guilsinglleft",
+ "uniF737",
+ "uniE11C",
+ "uniE11D",
+ "uniE11A",
+ "uni2077",
+ "uni2087",
+ "uniE11B",
+ "uniE119",
+ "uniE0DD",
+ "uniE0DE",
+ "uniF736",
+ "uniE121",
+ "uniE122",
+ "uniE11F",
+ "uni2076",
+ "uni2086",
+ "uniE120",
+ "uniE11E",
+ "uniE0DB",
+ "uniE0DC",
+ "uniF733",
+ "uniE12B",
+ "uniE12C",
+ "uniE129",
+ "uni00B3",
+ "uni2083",
+ "uniE12A",
+ "uniE128",
+ "uniF732",
+ "uniE133",
+ "uniE134",
+ "uniE131",
+ "uni00B2",
+ "uni2082",
+ "uniE132",
+ "uniE130",
+ "uniE0F9",
+ "uniF734",
+ "uniE0D4",
+ "uniE0D5",
+ "uniE0D2",
+ "uni2074",
+ "uni2084",
+ "uniE0D3",
+ "uniE0D1",
+ "uniF730",
+ "uniE13D",
+ "uniE13E",
+ "uniE13A",
+ "uni2070",
+ "uni2080",
+ "uniE13B",
+ "uniE139",
+ "uniE13C",
+ "uniF739",
+ "uniE0EC",
+ "uniE0ED",
+ "uniE0EA",
+ "uni2079",
+ "uni2089",
+ "uniE0EB",
+ "uniE0E9",
+ "uniF735",
+ "uniE0CD",
+ "uniE0CE",
+ "uniE0CB",
+ "uni2075",
+ "uni2085",
+ "uniE0CC",
+ "uniE0CA",
+ "uniF731",
+ "uniE0F3",
+ "uniE0F4",
+ "uniE0F1",
+ "uni00B9",
+ "uni2081",
+ "uniE0F2",
+ "uniE0F0",
+ "uniE0F8",
+ "uniF738",
+ "uniE0C0",
+ "uniE0C1",
+ "uniE0BE",
+ "uni2078",
+ "uni2088",
+ "uniE0BF",
+ "uniE0BD",
+ "I",
+ "Ismall",
+ "t",
+ "i",
+ "f",
+ "IJ",
+ "J",
+ "IJsmall",
+ "Jsmall",
+ "tt",
+ "ij",
+ "j",
+ "ffb",
+ "ffh",
+ "h",
+ "ffk",
+ "k",
+ "ffl",
+ "l",
+ "fft",
+ "fb",
+ "ff",
+ "fh",
+ "fj",
+ "fk",
+ "ft",
+ "janyevoweltelugu",
+ "kassevoweltelugu",
+ "jaivoweltelugu",
+ "nyasubscripttelugu",
+ "kaivoweltelugu",
+ "ssasubscripttelugu",
+ "bayi1",
+ "jeemi1",
+ "kafi1",
+ "ghafi1",
+ "laami1",
+ "kafm1",
+ "ghafm1",
+ "laamm1",
+ "rayf2",
+ "reyf2",
+ "yayf2",
+ "zayf2",
+ "fayi1",
+ "ayehf2",
+ "hamzayeharabf2",
+ "hamzayehf2",
+ "yehf2",
+ "ray",
+ "rey",
+ "zay",
+ "yay",
+ "dal",
+ "del",
+ "zal",
+ "rayf1",
+ "reyf1",
+ "yayf1",
+ "zayf1",
+ "ayehf1",
+ "hamzayeharabf1",
+ "hamzayehf1",
+ "yehf1",
+ "dal1",
+ "del1",
+ "zal1",
+ "onehalf",
+ "onehalf.alt",
+ "onequarter",
+ "onequarter.alt",
+ "threequarters",
+ "threequarters.alt",
+ "AlefSuperiorNS",
+ "DammaNS",
+ "DammaRflxNS",
+ "DammatanNS",
+ "Fatha2dotsNS",
+ "FathaNS",
+ "FathatanNS",
+ "FourDotsAboveNS",
+ "HamzaAboveNS",
+ "MaddaNS",
+ "OneDotAbove2NS",
+ "OneDotAboveNS",
+ "ShaddaAlefNS",
+ "ShaddaDammaNS",
+ "ShaddaDammatanNS",
+ "ShaddaFathatanNS",
+ "ShaddaKasraNS",
+ "ShaddaKasratanNS",
+ "ShaddaNS",
+ "SharetKafNS",
+ "SukunNS",
+ "ThreeDotsDownAboveNS",
+ "ThreeDotsUpAboveNS",
+ "TwoDotsAboveNS",
+ "TwoDotsVerticalAboveNS",
+ "UltapeshNS",
+ "WaslaNS",
+ "AinIni.12m_MeemFin.02",
+ "AinIni_YehBarreeFin",
+ "AinMed_YehBarreeFin",
+ "BehxIni_MeemFin",
+ "BehxIni_NoonGhunnaFin",
+ "BehxIni_RehFin",
+ "BehxIni_RehFin.b",
+ "BehxMed_MeemFin.py",
+ "BehxMed_NoonGhunnaFin",
+ "BehxMed_NoonGhunnaFin.cup",
+ "BehxMed_RehFin",
+ "BehxMed_RehFin.cup",
+ "BehxMed_YehxFin",
+ "FehxMed_YehBarreeFin",
+ "HahIni_YehBarreeFin",
+ "KafIni_YehBarreeFin",
+ "KafMed.12_YehxFin.01",
+ "KafMed_MeemFin",
+ "KafMed_YehBarreeFin",
+ "LamAlefFin",
+ "LamAlefFin.cup",
+ "LamAlefFin.cut",
+ "LamAlefFin.short",
+ "LamAlefSep",
+ "LamIni_MeemFin",
+ "LamIni_YehBarreeFin",
+ "LamMed_MeemFin",
+ "LamMed_MeemFin.b",
+ "LamMed_YehxFin",
+ "LamMed_YehxFin.cup",
+ "TahIni_YehBarreeFin",
+ "null",
+ "CR",
+ "space",
+ "exclam",
+ "quotedbl",
+ "numbersign",
]
# Feature files in data/*.txt; output gets compared to data/*.ttx.
TESTS = {
- None: (
- 'mti/cmap',
+ None: ("mti/cmap",),
+ "cmap": ("mti/cmap",),
+ "GSUB": (
+ "featurename-backward",
+ "featurename-forward",
+ "lookupnames-backward",
+ "lookupnames-forward",
+ "mixed-toplevels",
+ "mti/scripttable",
+ "mti/chainedclass",
+ "mti/chainedcoverage",
+ "mti/chained-glyph",
+ "mti/gsubalternate",
+ "mti/gsubligature",
+ "mti/gsubmultiple",
+ "mti/gsubreversechanined",
+ "mti/gsubsingle",
),
- 'cmap': (
- 'mti/cmap',
+ "GPOS": (
+ "mti/scripttable",
+ "mti/chained-glyph",
+ "mti/gposcursive",
+ "mti/gposkernset",
+ "mti/gposmarktobase",
+ "mti/gpospairclass",
+ "mti/gpospairglyph",
+ "mti/gpossingle",
+ "mti/mark-to-ligature",
),
- 'GSUB': (
- 'featurename-backward',
- 'featurename-forward',
- 'lookupnames-backward',
- 'lookupnames-forward',
- 'mixed-toplevels',
-
- 'mti/scripttable',
- 'mti/chainedclass',
- 'mti/chainedcoverage',
- 'mti/chained-glyph',
- 'mti/gsubalternate',
- 'mti/gsubligature',
- 'mti/gsubmultiple',
- 'mti/gsubreversechanined',
- 'mti/gsubsingle',
- ),
- 'GPOS': (
- 'mti/scripttable',
- 'mti/chained-glyph',
- 'mti/gposcursive',
- 'mti/gposkernset',
- 'mti/gposmarktobase',
- 'mti/gpospairclass',
- 'mti/gpospairglyph',
- 'mti/gpossingle',
- 'mti/mark-to-ligature',
- ),
- 'GDEF': (
- 'mti/gdefattach',
- 'mti/gdefclasses',
- 'mti/gdefligcaret',
- 'mti/gdefmarkattach',
- 'mti/gdefmarkfilter',
+ "GDEF": (
+ "mti/gdefattach",
+ "mti/gdefclasses",
+ "mti/gdefligcaret",
+ "mti/gdefmarkattach",
+ "mti/gdefmarkfilter",
),
}
# TODO:
@@ -125,33 +401,21 @@ class MtiTest(unittest.TestCase):
# 'mti/contextcoverage'
# 'mti/context-glyph'
- 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 setUp(self):
- pass
-
- def tearDown(self):
- pass
-
@staticmethod
def getpath(testfile):
path, _ = os.path.split(__file__)
return os.path.join(path, "data", testfile)
def expect_ttx(self, expected_ttx, actual_ttx, fromfile=None, tofile=None):
- expected = [l+'\n' for l in expected_ttx.split('\n')]
- actual = [l+'\n' for l in actual_ttx.split('\n')]
+ expected = [l + "\n" for l in expected_ttx.split("\n")]
+ actual = [l + "\n" for l in actual_ttx.split("\n")]
if actual != expected:
- sys.stderr.write('\n')
+ sys.stderr.write("\n")
for line in difflib.unified_diff(
- expected, actual, fromfile=fromfile, tofile=tofile):
+ expected, actual, fromfile=fromfile, tofile=tofile
+ ):
sys.stderr.write(line)
- self.fail("TTX output is different from expected")
+ pytest.fail("TTX output is different from expected")
@classmethod
def create_font(celf):
@@ -160,18 +424,19 @@ class MtiTest(unittest.TestCase):
return font
def check_mti_file(self, name, tableTag=None):
-
- xml_expected_path = self.getpath("%s.ttx" % name + ('.'+tableTag if tableTag is not None else ''))
- with open(xml_expected_path, 'rt', encoding="utf-8") as xml_expected_file:
+ xml_expected_path = self.getpath(
+ "%s.ttx" % name + ("." + tableTag if tableTag is not None else "")
+ )
+ with open(xml_expected_path, "rt", encoding="utf-8") as xml_expected_file:
xml_expected = xml_expected_file.read()
font = self.create_font()
- with open(self.getpath("%s.txt" % name), 'rt', encoding="utf-8") as f:
+ with open(self.getpath("%s.txt" % name), "rt", encoding="utf-8") as f:
table = mtiLib.build(f, font, tableTag=tableTag)
if tableTag is not None:
- self.assertEqual(tableTag, table.tableTag)
+ assert tableTag == table.tableTag
tableTag = table.tableTag
# Make sure it compiles.
@@ -183,22 +448,29 @@ class MtiTest(unittest.TestCase):
# XML from built object.
writer = XMLWriter(StringIO())
- writer.begintag(tableTag); writer.newline()
+ writer.begintag(tableTag)
+ writer.newline()
table.toXML(writer, font)
- writer.endtag(tableTag); writer.newline()
+ writer.endtag(tableTag)
+ writer.newline()
xml_built = writer.file.getvalue()
# XML from decompiled object.
writer = XMLWriter(StringIO())
- writer.begintag(tableTag); writer.newline()
+ writer.begintag(tableTag)
+ writer.newline()
decompiled.toXML(writer, font)
- writer.endtag(tableTag); writer.newline()
+ writer.endtag(tableTag)
+ writer.newline()
xml_binary = writer.file.getvalue()
- self.expect_ttx(xml_binary, xml_built, fromfile='decompiled', tofile='built')
- self.expect_ttx(xml_expected, xml_built, fromfile=xml_expected_path, tofile='built')
+ self.expect_ttx(xml_binary, xml_built, fromfile="decompiled", tofile="built")
+ self.expect_ttx(
+ xml_expected, xml_built, fromfile=xml_expected_path, tofile="built"
+ )
from fontTools.misc import xmlReader
+
f = StringIO()
f.write(xml_expected)
f.seek(0)
@@ -209,26 +481,37 @@ class MtiTest(unittest.TestCase):
# XML from object read from XML.
writer = XMLWriter(StringIO())
- writer.begintag(tableTag); writer.newline()
+ writer.begintag(tableTag)
+ writer.newline()
font2[tableTag].toXML(writer, font)
- writer.endtag(tableTag); writer.newline()
+ writer.endtag(tableTag)
+ writer.newline()
xml_fromxml = writer.file.getvalue()
- self.expect_ttx(xml_expected, xml_fromxml, fromfile=xml_expected_path, tofile='fromxml')
+ self.expect_ttx(
+ xml_expected, xml_fromxml, fromfile=xml_expected_path, tofile="fromxml"
+ )
+
def generate_mti_file_test(name, tableTag=None):
- return lambda self: self.check_mti_file(os.path.join(*name.split('/')), tableTag=tableTag)
+ return lambda self: self.check_mti_file(
+ os.path.join(*name.split("/")), tableTag=tableTag
+ )
-for tableTag,tests in MtiTest.TESTS.items():
+for tableTag, tests in MtiTest.TESTS.items():
for name in tests:
- setattr(MtiTest, "test_MtiFile_%s%s" % (name, '_'+tableTag if tableTag else ''),
- generate_mti_file_test(name, tableTag=tableTag))
+ setattr(
+ MtiTest,
+ "test_MtiFile_%s%s" % (name, "_" + tableTag if tableTag else ""),
+ generate_mti_file_test(name, tableTag=tableTag),
+ )
if __name__ == "__main__":
if len(sys.argv) > 1:
from fontTools.mtiLib import main
+
font = MtiTest.create_font()
sys.exit(main(sys.argv[1:], font))
- sys.exit(unittest.main())
+ sys.exit(pytest.main(sys.argv))
diff --git a/Tests/otlLib/builder_test.py b/Tests/otlLib/builder_test.py
index 548a31e9..b7a6caa2 100644
--- a/Tests/otlLib/builder_test.py
+++ b/Tests/otlLib/builder_test.py
@@ -1080,7 +1080,7 @@ class ClassDefBuilderTest(object):
b.add({"e", "f", "g", "h"})
cdef = b.build()
assert isinstance(cdef, otTables.ClassDef)
- assert cdef.classDefs == {"a": 2, "b": 2, "c": 3, "aa": 1, "bb": 1}
+ assert cdef.classDefs == {"a": 1, "b": 1, "c": 3, "aa": 2, "bb": 2}
def test_build_notUsingClass0(self):
b = builder.ClassDefBuilder(useClass0=False)
diff --git a/Tests/otlLib/maxContextCalc_test.py b/Tests/otlLib/maxContextCalc_test.py
index dc169c60..f672052e 100644
--- a/Tests/otlLib/maxContextCalc_test.py
+++ b/Tests/otlLib/maxContextCalc_test.py
@@ -1,4 +1,3 @@
-
import os
import pytest
from fontTools.ttLib import TTFont
@@ -9,13 +8,13 @@ from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
def test_max_ctx_calc_no_features():
font = TTFont()
assert maxCtxFont(font) == 0
- font.setGlyphOrder(['.notdef'])
- addOpenTypeFeaturesFromString(font, '')
+ font.setGlyphOrder([".notdef"])
+ addOpenTypeFeaturesFromString(font, "")
assert maxCtxFont(font) == 0
def test_max_ctx_calc_features():
- glyphs = '.notdef space A B C a b c'.split()
+ glyphs = ".notdef space A B C a b c".split()
features = """
lookup GSUB_EXT useExtension {
sub a by b;
@@ -59,15 +58,19 @@ def test_max_ctx_calc_features():
assert maxCtxFont(font) == 3
-@pytest.mark.parametrize('file_name, max_context', [
- ('gsub_51', 2),
- ('gsub_52', 2),
- ('gsub_71', 1),
- ('gpos_91', 1),
-])
+@pytest.mark.parametrize(
+ "file_name, max_context",
+ [
+ ("gsub_51", 2),
+ ("gsub_52", 2),
+ ("gsub_71", 1),
+ ("gpos_91", 1),
+ ],
+)
def test_max_ctx_calc_features_ttx(file_name, max_context):
- ttx_path = os.path.join(os.path.dirname(__file__),
- 'data', '{}.ttx'.format(file_name))
+ ttx_path = os.path.join(
+ os.path.dirname(__file__), "data", "{}.ttx".format(file_name)
+ )
font = TTFont()
font.importXML(ttx_path)
diff --git a/Tests/otlLib/mock_builder_test.py b/Tests/otlLib/mock_builder_test.py
index b3fecd83..46f5f80b 100644
--- a/Tests/otlLib/mock_builder_test.py
+++ b/Tests/otlLib/mock_builder_test.py
@@ -13,7 +13,7 @@ from fontTools.otlLib.builder import (
ClassPairPosSubtableBuilder,
PairPosBuilder,
SinglePosBuilder,
- ChainContextualRule
+ ChainContextualRule,
)
from fontTools.otlLib.error import OpenTypeLibError
from fontTools.ttLib import TTFont
@@ -76,11 +76,15 @@ def test_unsupported_subtable_break_1(ttfont):
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"):
+ 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
index 187b9816..00e7b05c 100644
--- a/Tests/pens/__init__.py
+++ b/Tests/pens/__init__.py
@@ -1,13 +1,6 @@
-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'):
+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 c3f3f80c..487c108e 100644
--- a/Tests/pens/areaPen_test.py
+++ b/Tests/pens/areaPen_test.py
@@ -3,120 +3,128 @@ import unittest
precision = 6
+
def draw1_(pen):
- pen.moveTo( (254, 360) )
- pen.lineTo( (771, 367) )
- pen.curveTo( (800, 393), (808, 399), (819, 412) )
- pen.curveTo( (818, 388), (774, 138), (489, 145) )
- pen.curveTo( (188, 145), (200, 398), (200, 421) )
- pen.curveTo( (209, 409), (220, 394), (254, 360) )
+ pen.moveTo((254, 360))
+ pen.lineTo((771, 367))
+ pen.curveTo((800, 393), (808, 399), (819, 412))
+ pen.curveTo((818, 388), (774, 138), (489, 145))
+ pen.curveTo((188, 145), (200, 398), (200, 421))
+ pen.curveTo((209, 409), (220, 394), (254, 360))
pen.closePath()
+
def draw2_(pen):
- pen.moveTo( (254, 360) )
- pen.curveTo( (220, 394), (209, 409), (200, 421) )
- pen.curveTo( (200, 398), (188, 145), (489, 145) )
- pen.curveTo( (774, 138), (818, 388), (819, 412) )
- pen.curveTo( (808, 399), (800, 393), (771, 367) )
+ pen.moveTo((254, 360))
+ pen.curveTo((220, 394), (209, 409), (200, 421))
+ pen.curveTo((200, 398), (188, 145), (489, 145))
+ pen.curveTo((774, 138), (818, 388), (819, 412))
+ pen.curveTo((808, 399), (800, 393), (771, 367))
pen.closePath()
+
def draw3_(pen):
- pen.moveTo( (771, 367) )
- pen.curveTo( (800, 393), (808, 399), (819, 412) )
- pen.curveTo( (818, 388), (774, 138), (489, 145) )
- pen.curveTo( (188, 145), (200, 398), (200, 421) )
- pen.curveTo( (209, 409), (220, 394), (254, 360) )
+ pen.moveTo((771, 367))
+ pen.curveTo((800, 393), (808, 399), (819, 412))
+ pen.curveTo((818, 388), (774, 138), (489, 145))
+ pen.curveTo((188, 145), (200, 398), (200, 421))
+ pen.curveTo((209, 409), (220, 394), (254, 360))
pen.closePath()
+
def draw4_(pen):
- pen.moveTo( (771, 367) )
- pen.lineTo( (254, 360) )
- pen.curveTo( (220, 394), (209, 409), (200, 421) )
- pen.curveTo( (200, 398), (188, 145), (489, 145) )
- pen.curveTo( (774, 138), (818, 388), (819, 412) )
- pen.curveTo( (808, 399), (800, 393), (771, 367) )
+ pen.moveTo((771, 367))
+ pen.lineTo((254, 360))
+ pen.curveTo((220, 394), (209, 409), (200, 421))
+ pen.curveTo((200, 398), (188, 145), (489, 145))
+ pen.curveTo((774, 138), (818, 388), (819, 412))
+ pen.curveTo((808, 399), (800, 393), (771, 367))
pen.closePath()
+
def draw5_(pen):
- pen.moveTo( (254, 360) )
- pen.lineTo( (771, 367) )
- pen.qCurveTo( (793, 386), (802, 394) )
- pen.qCurveTo( (811, 402), (819, 412) )
- pen.qCurveTo( (819, 406), (814, 383.5) )
- pen.qCurveTo( (809, 361), (796, 330.5) )
- pen.qCurveTo( (783, 300), (760.5, 266.5) )
- pen.qCurveTo( (738, 233), (701, 205.5) )
- pen.qCurveTo( (664, 178), (612, 160.5) )
- pen.qCurveTo( (560, 143), (489, 145) )
- pen.qCurveTo( (414, 145), (363, 164) )
- pen.qCurveTo( (312, 183), (280, 211.5) )
- pen.qCurveTo( (248, 240), (231.5, 274.5) )
- pen.qCurveTo( (215, 309), (208, 339.5) )
- pen.qCurveTo( (201, 370), (200.5, 392.5) )
- pen.qCurveTo( (200, 415), (200, 421) )
- pen.qCurveTo( (207, 412), (217.5, 399) )
- pen.qCurveTo( (228, 386), (254, 360) )
+ pen.moveTo((254, 360))
+ pen.lineTo((771, 367))
+ pen.qCurveTo((793, 386), (802, 394))
+ pen.qCurveTo((811, 402), (819, 412))
+ pen.qCurveTo((819, 406), (814, 383.5))
+ pen.qCurveTo((809, 361), (796, 330.5))
+ pen.qCurveTo((783, 300), (760.5, 266.5))
+ pen.qCurveTo((738, 233), (701, 205.5))
+ pen.qCurveTo((664, 178), (612, 160.5))
+ pen.qCurveTo((560, 143), (489, 145))
+ pen.qCurveTo((414, 145), (363, 164))
+ pen.qCurveTo((312, 183), (280, 211.5))
+ pen.qCurveTo((248, 240), (231.5, 274.5))
+ pen.qCurveTo((215, 309), (208, 339.5))
+ pen.qCurveTo((201, 370), (200.5, 392.5))
+ pen.qCurveTo((200, 415), (200, 421))
+ pen.qCurveTo((207, 412), (217.5, 399))
+ pen.qCurveTo((228, 386), (254, 360))
pen.closePath()
+
def draw6_(pen):
- pen.moveTo( (254, 360) )
- pen.qCurveTo( (228, 386), (217.5, 399) )
- pen.qCurveTo( (207, 412), (200, 421) )
- pen.qCurveTo( (200, 415), (200.5, 392.5) )
- pen.qCurveTo( (201, 370), (208, 339.5) )
- pen.qCurveTo( (215, 309), (231.5, 274.5) )
- pen.qCurveTo( (248, 240), (280, 211.5) )
- pen.qCurveTo( (312, 183), (363, 164) )
- pen.qCurveTo( (414, 145), (489, 145) )
- pen.qCurveTo( (560, 143), (612, 160.5) )
- pen.qCurveTo( (664, 178), (701, 205.5) )
- pen.qCurveTo( (738, 233), (760.5, 266.5) )
- pen.qCurveTo( (783, 300), (796, 330.5) )
- pen.qCurveTo( (809, 361), (814, 383.5) )
- pen.qCurveTo( (819, 406), (819, 412) )
- pen.qCurveTo( (811, 402), (802, 394) )
- pen.qCurveTo( (793, 386), (771, 367) )
+ pen.moveTo((254, 360))
+ pen.qCurveTo((228, 386), (217.5, 399))
+ pen.qCurveTo((207, 412), (200, 421))
+ pen.qCurveTo((200, 415), (200.5, 392.5))
+ pen.qCurveTo((201, 370), (208, 339.5))
+ pen.qCurveTo((215, 309), (231.5, 274.5))
+ pen.qCurveTo((248, 240), (280, 211.5))
+ pen.qCurveTo((312, 183), (363, 164))
+ pen.qCurveTo((414, 145), (489, 145))
+ pen.qCurveTo((560, 143), (612, 160.5))
+ pen.qCurveTo((664, 178), (701, 205.5))
+ pen.qCurveTo((738, 233), (760.5, 266.5))
+ pen.qCurveTo((783, 300), (796, 330.5))
+ pen.qCurveTo((809, 361), (814, 383.5))
+ pen.qCurveTo((819, 406), (819, 412))
+ pen.qCurveTo((811, 402), (802, 394))
+ pen.qCurveTo((793, 386), (771, 367))
pen.closePath()
+
def draw7_(pen):
- pen.moveTo( (771, 367) )
- pen.qCurveTo( (793, 386), (802, 394) )
- pen.qCurveTo( (811, 402), (819, 412) )
- pen.qCurveTo( (819, 406), (814, 383.5) )
- pen.qCurveTo( (809, 361), (796, 330.5) )
- pen.qCurveTo( (783, 300), (760.5, 266.5) )
- pen.qCurveTo( (738, 233), (701, 205.5) )
- pen.qCurveTo( (664, 178), (612, 160.5) )
- pen.qCurveTo( (560, 143), (489, 145) )
- pen.qCurveTo( (414, 145), (363, 164) )
- pen.qCurveTo( (312, 183), (280, 211.5) )
- pen.qCurveTo( (248, 240), (231.5, 274.5) )
- pen.qCurveTo( (215, 309), (208, 339.5) )
- pen.qCurveTo( (201, 370), (200.5, 392.5) )
- pen.qCurveTo( (200, 415), (200, 421) )
- pen.qCurveTo( (207, 412), (217.5, 399) )
- pen.qCurveTo( (228, 386), (254, 360) )
+ pen.moveTo((771, 367))
+ pen.qCurveTo((793, 386), (802, 394))
+ pen.qCurveTo((811, 402), (819, 412))
+ pen.qCurveTo((819, 406), (814, 383.5))
+ pen.qCurveTo((809, 361), (796, 330.5))
+ pen.qCurveTo((783, 300), (760.5, 266.5))
+ pen.qCurveTo((738, 233), (701, 205.5))
+ pen.qCurveTo((664, 178), (612, 160.5))
+ pen.qCurveTo((560, 143), (489, 145))
+ pen.qCurveTo((414, 145), (363, 164))
+ pen.qCurveTo((312, 183), (280, 211.5))
+ pen.qCurveTo((248, 240), (231.5, 274.5))
+ pen.qCurveTo((215, 309), (208, 339.5))
+ pen.qCurveTo((201, 370), (200.5, 392.5))
+ pen.qCurveTo((200, 415), (200, 421))
+ pen.qCurveTo((207, 412), (217.5, 399))
+ pen.qCurveTo((228, 386), (254, 360))
pen.closePath()
+
def draw8_(pen):
- pen.moveTo( (771, 367) )
- pen.lineTo( (254, 360) )
- pen.qCurveTo( (228, 386), (217.5, 399) )
- pen.qCurveTo( (207, 412), (200, 421) )
- pen.qCurveTo( (200, 415), (200.5, 392.5) )
- pen.qCurveTo( (201, 370), (208, 339.5) )
- pen.qCurveTo( (215, 309), (231.5, 274.5) )
- pen.qCurveTo( (248, 240), (280, 211.5) )
- pen.qCurveTo( (312, 183), (363, 164) )
- pen.qCurveTo( (414, 145), (489, 145) )
- pen.qCurveTo( (560, 143), (612, 160.5) )
- pen.qCurveTo( (664, 178), (701, 205.5) )
- pen.qCurveTo( (738, 233), (760.5, 266.5) )
- pen.qCurveTo( (783, 300), (796, 330.5) )
- pen.qCurveTo( (809, 361), (814, 383.5) )
- pen.qCurveTo( (819, 406), (819, 412) )
- pen.qCurveTo( (811, 402), (802, 394) )
- pen.qCurveTo( (793, 386), (771, 367) )
+ pen.moveTo((771, 367))
+ pen.lineTo((254, 360))
+ pen.qCurveTo((228, 386), (217.5, 399))
+ pen.qCurveTo((207, 412), (200, 421))
+ pen.qCurveTo((200, 415), (200.5, 392.5))
+ pen.qCurveTo((201, 370), (208, 339.5))
+ pen.qCurveTo((215, 309), (231.5, 274.5))
+ pen.qCurveTo((248, 240), (280, 211.5))
+ pen.qCurveTo((312, 183), (363, 164))
+ pen.qCurveTo((414, 145), (489, 145))
+ pen.qCurveTo((560, 143), (612, 160.5))
+ pen.qCurveTo((664, 178), (701, 205.5))
+ pen.qCurveTo((738, 233), (760.5, 266.5))
+ pen.qCurveTo((783, 300), (796, 330.5))
+ pen.qCurveTo((809, 361), (814, 383.5))
+ pen.qCurveTo((819, 406), (819, 412))
+ pen.qCurveTo((811, 402), (802, 394))
+ pen.qCurveTo((793, 386), (771, 367))
pen.closePath()
@@ -173,6 +181,7 @@ class AreaPenTest(unittest.TestCase):
pen.endPath()
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/pens/basePen_test.py b/Tests/pens/basePen_test.py
index db57e80e..d8508fd1 100644
--- a/Tests/pens/basePen_test.py
+++ b/Tests/pens/basePen_test.py
@@ -1,5 +1,9 @@
-from fontTools.pens.basePen import \
- AbstractPen, BasePen, decomposeSuperBezierSegment, decomposeQuadraticSegment
+from fontTools.pens.basePen import (
+ AbstractPen,
+ BasePen,
+ decomposeSuperBezierSegment,
+ decomposeQuadraticSegment,
+)
from fontTools.pens.pointPen import AbstractPointPen
from fontTools.misc.loggingTools import CapturingLogHandler
import unittest
@@ -23,10 +27,10 @@ class _TestPen(BasePen):
self._commands.append("%s %s lineto" % (pt[0], pt[1]))
def _curveToOne(self, bcp1, bcp2, pt):
- self._commands.append("%s %s %s %s %s %s curveto" %
- (bcp1[0], bcp1[1],
- bcp2[0], bcp2[1],
- pt[0], pt[1]))
+ self._commands.append(
+ "%s %s %s %s %s %s curveto"
+ % (bcp1[0], bcp1[1], bcp2[0], bcp2[1], pt[0], pt[1])
+ )
def _closePath(self):
self._commands.append("closepath")
@@ -73,17 +77,19 @@ class BasePenTest(unittest.TestCase):
pen = _TestPen()
pen.moveTo((0.0, 0.0))
pen.curveTo((6.0, 3.0), (3.0, 6.0))
- self.assertEqual("0.0 0.0 moveto 4.0 2.0 5.0 4.0 3.0 6.0 curveto",
- repr(pen))
+ self.assertEqual("0.0 0.0 moveto 4.0 2.0 5.0 4.0 3.0 6.0 curveto", repr(pen))
self.assertEqual((3.0, 6.0), pen.getCurrentPoint())
def test_curveTo_manyPoints(self):
pen = _TestPen()
pen.moveTo((0.0, 0.0))
pen.curveTo((1.0, 1.1), (2.0, 2.1), (3.0, 3.1), (4.0, 4.1))
- self.assertEqual("0.0 0.0 moveto "
- "1.0 1.1 1.5 1.6 2.0 2.1 curveto "
- "2.5 2.6 3.0 3.1 4.0 4.1 curveto", repr(pen))
+ self.assertEqual(
+ "0.0 0.0 moveto "
+ "1.0 1.1 1.5 1.6 2.0 2.1 curveto "
+ "2.5 2.6 3.0 3.1 4.0 4.1 curveto",
+ repr(pen),
+ )
self.assertEqual((4.0, 4.1), pen.getCurrentPoint())
def test_qCurveTo_zeroPoints(self):
@@ -102,19 +108,21 @@ class BasePenTest(unittest.TestCase):
pen = _TestPen()
pen.moveTo((0.0, 0.0))
pen.qCurveTo((6.0, 3.0), (3.0, 6.0))
- self.assertEqual("0.0 0.0 moveto 4.0 2.0 5.0 4.0 3.0 6.0 curveto",
- repr(pen))
+ self.assertEqual("0.0 0.0 moveto 4.0 2.0 5.0 4.0 3.0 6.0 curveto", repr(pen))
self.assertEqual((3.0, 6.0), pen.getCurrentPoint())
def test_qCurveTo_onlyOffCurvePoints(self):
pen = _TestPen()
pen.moveTo((0.0, 0.0))
pen.qCurveTo((6.0, -6.0), (12.0, 12.0), (18.0, -18.0), None)
- self.assertEqual("0.0 0.0 moveto "
- "12.0 -12.0 moveto "
- "8.0 -8.0 7.0 -3.0 9.0 3.0 curveto "
- "11.0 9.0 13.0 7.0 15.0 -3.0 curveto "
- "17.0 -13.0 16.0 -16.0 12.0 -12.0 curveto", repr(pen))
+ self.assertEqual(
+ "0.0 0.0 moveto "
+ "12.0 -12.0 moveto "
+ "8.0 -8.0 7.0 -3.0 9.0 3.0 curveto "
+ "11.0 9.0 13.0 7.0 15.0 -3.0 curveto "
+ "17.0 -13.0 16.0 -16.0 12.0 -12.0 curveto",
+ repr(pen),
+ )
self.assertEqual((12.0, -12.0), pen.getCurrentPoint())
def test_closePath(self):
@@ -135,11 +143,14 @@ class BasePenTest(unittest.TestCase):
pen = _TestPen()
pen.glyphSet["oslash"] = _TestGlyph()
pen.addComponent("oslash", (2, 3, 0.5, 2, -10, 0))
- self.assertEqual("-10.0 0.0 moveto "
- "40.0 200.0 lineto "
- "127.5 300.0 131.25 290.0 125.0 265.0 curveto "
- "118.75 240.0 102.5 200.0 -10.0 0.0 curveto "
- "closepath", repr(pen))
+ self.assertEqual(
+ "-10.0 0.0 moveto "
+ "40.0 200.0 lineto "
+ "127.5 300.0 131.25 290.0 125.0 265.0 curveto "
+ "118.75 240.0 102.5 200.0 -10.0 0.0 curveto "
+ "closepath",
+ repr(pen),
+ )
self.assertEqual(None, pen.getCurrentPoint())
def test_addComponent_skip_missing(self):
@@ -155,24 +166,29 @@ class DecomposeSegmentTest(unittest.TestCase):
self.assertRaises(AssertionError, decompose, [])
self.assertRaises(AssertionError, decompose, [(0, 0)])
self.assertRaises(AssertionError, decompose, [(0, 0), (1, 1)])
- self.assertEqual([((0, 0), (1, 1), (2, 2))],
- decompose([(0, 0), (1, 1), (2, 2)]))
+ self.assertEqual(
+ [((0, 0), (1, 1), (2, 2))], decompose([(0, 0), (1, 1), (2, 2)])
+ )
self.assertEqual(
[((0, 0), (2, -2), (4, 0)), ((6, 2), (8, 8), (12, -12))],
- decompose([(0, 0), (4, -4), (8, 8), (12, -12)]))
+ decompose([(0, 0), (4, -4), (8, 8), (12, -12)]),
+ )
def test_decomposeQuadraticSegment(self):
decompose = decomposeQuadraticSegment
self.assertRaises(AssertionError, decompose, [])
self.assertRaises(AssertionError, decompose, [(0, 0)])
- self.assertEqual([((0,0), (4, 8))], decompose([(0, 0), (4, 8)]))
- self.assertEqual([((0,0), (2, 4)), ((4, 8), (9, -9))],
- decompose([(0, 0), (4, 8), (9, -9)]))
+ self.assertEqual([((0, 0), (4, 8))], decompose([(0, 0), (4, 8)]))
+ self.assertEqual(
+ [((0, 0), (2, 4)), ((4, 8), (9, -9))], decompose([(0, 0), (4, 8), (9, -9)])
+ )
self.assertEqual(
[((0, 0), (2.0, 4.0)), ((4, 8), (6.5, -0.5)), ((9, -9), (10, 10))],
- decompose([(0, 0), (4, 8), (9, -9), (10, 10)]))
+ decompose([(0, 0), (4, 8), (9, -9), (10, 10)]),
+ )
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/pens/boundsPen_test.py b/Tests/pens/boundsPen_test.py
index c0c56108..190161f3 100644
--- a/Tests/pens/boundsPen_test.py
+++ b/Tests/pens/boundsPen_test.py
@@ -70,6 +70,7 @@ class ControlBoundsPenTest(unittest.TestCase):
self.assertEqual(None, pen.bounds)
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/pens/cocoaPen_test.py b/Tests/pens/cocoaPen_test.py
index 11077c0b..6222cc7e 100644
--- a/Tests/pens/cocoaPen_test.py
+++ b/Tests/pens/cocoaPen_test.py
@@ -7,10 +7,10 @@ try:
PATH_ELEMENTS = {
# NSBezierPathElement key desc
- NSBezierPathElementMoveTo: 'moveto',
- NSBezierPathElementLineTo: 'lineto',
- NSBezierPathElementCurveTo: 'curveto',
- NSBezierPathElementClosePath: 'close',
+ NSBezierPathElementMoveTo: "moveto",
+ NSBezierPathElementLineTo: "lineto",
+ NSBezierPathElementCurveTo: "curveto",
+ NSBezierPathElementClosePath: "close",
}
PYOBJC_AVAILABLE = True
@@ -45,7 +45,7 @@ class CocoaPenTest(unittest.TestCase):
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)
+ cocoaPathToString(pen.path),
)
def test_empty(self):
@@ -53,6 +53,7 @@ class CocoaPenTest(unittest.TestCase):
self.assertEqual("", cocoaPathToString(pen.path))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/pens/cu2quPen_test.py b/Tests/pens/cu2quPen_test.py
index 4ce5b512..779254c3 100644
--- a/Tests/pens/cu2quPen_test.py
+++ b/Tests/pens/cu2quPen_test.py
@@ -15,13 +15,19 @@
import sys
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.pens.cu2quPen import Cu2QuPen, Cu2QuPointPen, Cu2QuMultiPen
+from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
from fontTools.misc.loggingTools import CapturingLogHandler
from textwrap import dedent
import logging
+import pytest
+
+try:
+ from .utils import CUBIC_GLYPHS, QUAD_GLYPHS
+ from .utils import DummyGlyph, DummyPointGlyph
+ from .utils import DummyPen, DummyPointPen
+except ImportError as e:
+ pytest.skip(str(e), allow_module_level=True)
MAX_ERR = 1.0
@@ -36,10 +42,12 @@ class _TestPenMixin(object):
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')
+ expected, actual, fromfile="expected", tofile="actual"
+ )
return "".join(diff)
def convert_glyph(self, glyph, **kwargs):
@@ -58,28 +66,27 @@ class _TestPenMixin(object):
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'])
+ 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']
+ 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'])
+ self.expect_glyph(CUBIC_GLYPHS["Eacute"], QUAD_GLYPHS["Eacute"])
def test_reverse_direction(self):
- for name in ('a', 'A', 'Eacute'):
+ 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.assertTrue(len(normal_glyph.outline), len(reversed_glyph.outline))
self.assertNotEqual(normal_glyph, reversed_glyph)
def test_stats(self):
@@ -89,8 +96,8 @@ class _TestPenMixin(object):
self.convert_glyph(source, stats=stats)
self.assertTrue(stats)
- self.assertTrue('1' in stats)
- self.assertEqual(type(stats['1']), int)
+ self.assertTrue("1" in stats)
+ self.assertEqual(type(stats["1"]), int)
def test_addComponent(self):
pen = self.Pen()
@@ -98,65 +105,22 @@ class _TestPenMixin(object):
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))",
- ])
+ 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()
+ self.pen_getter_name = "getPen"
+ self.draw_method_name = "draw"
def test_qCurveTo_1_point(self):
pen = DummyPen()
@@ -164,10 +128,13 @@ class TestCu2QuPen(unittest.TestCase, _TestPenMixin):
quadpen.moveTo((0, 0))
quadpen.qCurveTo((1, 1))
- self.assertEqual(str(pen).splitlines(), [
- "pen.moveTo((0, 0))",
- "pen.lineTo((1, 1))",
- ])
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1))",
+ ],
+ )
def test_qCurveTo_more_than_1_point(self):
pen = DummyPen()
@@ -175,18 +142,13 @@ class TestCu2QuPen(unittest.TestCase, _TestPenMixin):
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()
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1), (2, 2))",
+ ],
+ )
def test_curveTo_1_point(self):
pen = DummyPen()
@@ -194,10 +156,13 @@ class TestCu2QuPen(unittest.TestCase, _TestPenMixin):
quadpen.moveTo((0, 0))
quadpen.curveTo((1, 1))
- self.assertEqual(str(pen).splitlines(), [
- "pen.moveTo((0, 0))",
- "pen.lineTo((1, 1))",
- ])
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1))",
+ ],
+ )
def test_curveTo_2_points(self):
pen = DummyPen()
@@ -205,10 +170,13 @@ class TestCu2QuPen(unittest.TestCase, _TestPenMixin):
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))",
- ])
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1), (2, 2))",
+ ],
+ )
def test_curveTo_3_points(self):
pen = DummyPen()
@@ -216,10 +184,13 @@ class TestCu2QuPen(unittest.TestCase, _TestPenMixin):
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))",
- ])
+ 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
@@ -228,71 +199,24 @@ class TestCu2QuPen(unittest.TestCase, _TestPenMixin):
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)
- if sys.version_info < (3, 11):
- self.assertIn("ignore_single_points is deprecated",
- log.records[0].args[0])
- else:
- self.assertIn("ignore_single_points is deprecated",
- log.records[0].msg)
-
- # 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()"
- ])
+ 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))",
+ ],
+ )
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'
+ self.pen_getter_name = "getPointPen"
+ self.draw_method_name = "drawPoints"
def test_super_bezier_curve(self):
pen = DummyPointPen()
@@ -303,10 +227,13 @@ class TestCu2QuPointPen(unittest.TestCase, _TestPenMixin):
quadpen.addPoint((2, 2))
quadpen.addPoint((3, 3))
quadpen.addPoint(
- (4, 4), segmentType="curve", smooth=False, name="up", selected=1)
+ (4, 4), segmentType="curve", smooth=False, name="up", selected=1
+ )
quadpen.endPath()
- self.assertEqual(str(pen).splitlines(), """\
+ 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)
@@ -315,7 +242,8 @@ 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())
+pen.endPath()""".splitlines(),
+ )
def test__flushContour_restore_starting_point(self):
pen = DummyPointPen()
@@ -323,24 +251,34 @@ pen.endPath()""".splitlines())
# 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, {}),
- ]),
- ])
+ 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
@@ -349,16 +287,24 @@ pen.endPath()""".splitlines())
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, {}),
- ]),
- ])
+ 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))
@@ -387,9 +333,97 @@ pen.endPath()""".splitlines())
pen.addPoint((2, 2), name=None, segmentType=None, smooth=False)
pen.addPoint((3, 3), name=None, segmentType=None, smooth=False)
pen.endPath()"""
- )
+ ),
)
+class TestCu2QuMultiPen(unittest.TestCase):
+ def test_multi_pen(self):
+ pens = [RecordingPen(), RecordingPen()]
+ pen = Cu2QuMultiPen(pens, 0.1)
+ pen.moveTo([((0, 0),), ((0, 0),)])
+ pen.lineTo([((0, 1),), ((0, 1),)])
+ pen.qCurveTo([((0, 2),), ((0, 2),)])
+ pen.qCurveTo([((0, 3), (1, 3)), ((0, 3), (1, 4))])
+ pen.curveTo([((2, 3), (0, 3), (0, 0)), ((1.1, 4), (0, 4), (0, 0))])
+ pen.closePath()
+
+ assert len(pens[0].value) == 6
+ assert len(pens[1].value) == 6
+
+ for op0, op1 in zip(pens[0].value, pens[1].value):
+ assert op0[0] == op0[0]
+ assert op0[0] != "curveTo"
+
+
+class TestAllQuadraticFalse(unittest.TestCase):
+ def test_segment_pen_cubic(self):
+ rpen = RecordingPen()
+ pen = Cu2QuPen(rpen, 0.1, all_quadratic=False)
+
+ pen.moveTo((0, 0))
+ pen.curveTo((0, 1), (2, 1), (2, 0))
+ pen.closePath()
+
+ assert rpen.value == [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 1), (2, 1), (2, 0))),
+ ("closePath", ()),
+ ]
+
+ def test_segment_pen_quadratic(self):
+ rpen = RecordingPen()
+ pen = Cu2QuPen(rpen, 0.1, all_quadratic=False)
+
+ pen.moveTo((0, 0))
+ pen.curveTo((2, 2), (4, 2), (6, 0))
+ pen.closePath()
+
+ assert rpen.value == [
+ ("moveTo", ((0, 0),)),
+ ("qCurveTo", ((3, 3), (6, 0))),
+ ("closePath", ()),
+ ]
+
+ def test_point_pen_cubic(self):
+ rpen = RecordingPointPen()
+ pen = Cu2QuPointPen(rpen, 0.1, all_quadratic=False)
+
+ pen.beginPath()
+ pen.addPoint((0, 0), "move")
+ pen.addPoint((0, 1))
+ pen.addPoint((2, 1))
+ pen.addPoint((2, 0), "curve")
+ pen.endPath()
+
+ assert rpen.value == [
+ ("beginPath", (), {}),
+ ("addPoint", ((0, 0), "move", False, None), {}),
+ ("addPoint", ((0, 1), None, False, None), {}),
+ ("addPoint", ((2, 1), None, False, None), {}),
+ ("addPoint", ((2, 0), "curve", False, None), {}),
+ ("endPath", (), {}),
+ ]
+
+ def test_point_pen_quadratic(self):
+ rpen = RecordingPointPen()
+ pen = Cu2QuPointPen(rpen, 0.1, all_quadratic=False)
+
+ pen.beginPath()
+ pen.addPoint((0, 0), "move")
+ pen.addPoint((2, 2))
+ pen.addPoint((4, 2))
+ pen.addPoint((6, 0), "curve")
+ pen.endPath()
+
+ assert rpen.value == [
+ ("beginPath", (), {}),
+ ("addPoint", ((0, 0), "move", False, None), {}),
+ ("addPoint", ((3, 3), None, False, None), {}),
+ ("addPoint", ((6, 0), "qcurve", False, None), {}),
+ ("endPath", (), {}),
+ ]
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Tests/pens/perimeterPen_test.py b/Tests/pens/perimeterPen_test.py
index 1b645345..dff1a088 100644
--- a/Tests/pens/perimeterPen_test.py
+++ b/Tests/pens/perimeterPen_test.py
@@ -1,120 +1,128 @@
from fontTools.pens.perimeterPen import PerimeterPen
import unittest
+
def draw1_(pen):
- pen.moveTo( (254, 360) )
- pen.lineTo( (771, 367) )
- pen.curveTo( (800, 393), (808, 399), (819, 412) )
- pen.curveTo( (818, 388), (774, 138), (489, 145) )
- pen.curveTo( (188, 145), (200, 398), (200, 421) )
- pen.curveTo( (209, 409), (220, 394), (254, 360) )
+ pen.moveTo((254, 360))
+ pen.lineTo((771, 367))
+ pen.curveTo((800, 393), (808, 399), (819, 412))
+ pen.curveTo((818, 388), (774, 138), (489, 145))
+ pen.curveTo((188, 145), (200, 398), (200, 421))
+ pen.curveTo((209, 409), (220, 394), (254, 360))
pen.closePath()
+
def draw2_(pen):
- pen.moveTo( (254, 360) )
- pen.curveTo( (220, 394), (209, 409), (200, 421) )
- pen.curveTo( (200, 398), (188, 145), (489, 145) )
- pen.curveTo( (774, 138), (818, 388), (819, 412) )
- pen.curveTo( (808, 399), (800, 393), (771, 367) )
+ pen.moveTo((254, 360))
+ pen.curveTo((220, 394), (209, 409), (200, 421))
+ pen.curveTo((200, 398), (188, 145), (489, 145))
+ pen.curveTo((774, 138), (818, 388), (819, 412))
+ pen.curveTo((808, 399), (800, 393), (771, 367))
pen.closePath()
+
def draw3_(pen):
- pen.moveTo( (771, 367) )
- pen.curveTo( (800, 393), (808, 399), (819, 412) )
- pen.curveTo( (818, 388), (774, 138), (489, 145) )
- pen.curveTo( (188, 145), (200, 398), (200, 421) )
- pen.curveTo( (209, 409), (220, 394), (254, 360) )
+ pen.moveTo((771, 367))
+ pen.curveTo((800, 393), (808, 399), (819, 412))
+ pen.curveTo((818, 388), (774, 138), (489, 145))
+ pen.curveTo((188, 145), (200, 398), (200, 421))
+ pen.curveTo((209, 409), (220, 394), (254, 360))
pen.closePath()
+
def draw4_(pen):
- pen.moveTo( (771, 367) )
- pen.lineTo( (254, 360) )
- pen.curveTo( (220, 394), (209, 409), (200, 421) )
- pen.curveTo( (200, 398), (188, 145), (489, 145) )
- pen.curveTo( (774, 138), (818, 388), (819, 412) )
- pen.curveTo( (808, 399), (800, 393), (771, 367) )
+ pen.moveTo((771, 367))
+ pen.lineTo((254, 360))
+ pen.curveTo((220, 394), (209, 409), (200, 421))
+ pen.curveTo((200, 398), (188, 145), (489, 145))
+ pen.curveTo((774, 138), (818, 388), (819, 412))
+ pen.curveTo((808, 399), (800, 393), (771, 367))
pen.closePath()
+
def draw5_(pen):
- pen.moveTo( (254, 360) )
- pen.lineTo( (771, 367) )
- pen.qCurveTo( (793, 386), (802, 394) )
- pen.qCurveTo( (811, 402), (819, 412) )
- pen.qCurveTo( (819, 406), (814, 383.5) )
- pen.qCurveTo( (809, 361), (796, 330.5) )
- pen.qCurveTo( (783, 300), (760.5, 266.5) )
- pen.qCurveTo( (738, 233), (701, 205.5) )
- pen.qCurveTo( (664, 178), (612, 160.5) )
- pen.qCurveTo( (560, 143), (489, 145) )
- pen.qCurveTo( (414, 145), (363, 164) )
- pen.qCurveTo( (312, 183), (280, 211.5) )
- pen.qCurveTo( (248, 240), (231.5, 274.5) )
- pen.qCurveTo( (215, 309), (208, 339.5) )
- pen.qCurveTo( (201, 370), (200.5, 392.5) )
- pen.qCurveTo( (200, 415), (200, 421) )
- pen.qCurveTo( (207, 412), (217.5, 399) )
- pen.qCurveTo( (228, 386), (254, 360) )
+ pen.moveTo((254, 360))
+ pen.lineTo((771, 367))
+ pen.qCurveTo((793, 386), (802, 394))
+ pen.qCurveTo((811, 402), (819, 412))
+ pen.qCurveTo((819, 406), (814, 383.5))
+ pen.qCurveTo((809, 361), (796, 330.5))
+ pen.qCurveTo((783, 300), (760.5, 266.5))
+ pen.qCurveTo((738, 233), (701, 205.5))
+ pen.qCurveTo((664, 178), (612, 160.5))
+ pen.qCurveTo((560, 143), (489, 145))
+ pen.qCurveTo((414, 145), (363, 164))
+ pen.qCurveTo((312, 183), (280, 211.5))
+ pen.qCurveTo((248, 240), (231.5, 274.5))
+ pen.qCurveTo((215, 309), (208, 339.5))
+ pen.qCurveTo((201, 370), (200.5, 392.5))
+ pen.qCurveTo((200, 415), (200, 421))
+ pen.qCurveTo((207, 412), (217.5, 399))
+ pen.qCurveTo((228, 386), (254, 360))
pen.closePath()
+
def draw6_(pen):
- pen.moveTo( (254, 360) )
- pen.qCurveTo( (228, 386), (217.5, 399) )
- pen.qCurveTo( (207, 412), (200, 421) )
- pen.qCurveTo( (200, 415), (200.5, 392.5) )
- pen.qCurveTo( (201, 370), (208, 339.5) )
- pen.qCurveTo( (215, 309), (231.5, 274.5) )
- pen.qCurveTo( (248, 240), (280, 211.5) )
- pen.qCurveTo( (312, 183), (363, 164) )
- pen.qCurveTo( (414, 145), (489, 145) )
- pen.qCurveTo( (560, 143), (612, 160.5) )
- pen.qCurveTo( (664, 178), (701, 205.5) )
- pen.qCurveTo( (738, 233), (760.5, 266.5) )
- pen.qCurveTo( (783, 300), (796, 330.5) )
- pen.qCurveTo( (809, 361), (814, 383.5) )
- pen.qCurveTo( (819, 406), (819, 412) )
- pen.qCurveTo( (811, 402), (802, 394) )
- pen.qCurveTo( (793, 386), (771, 367) )
+ pen.moveTo((254, 360))
+ pen.qCurveTo((228, 386), (217.5, 399))
+ pen.qCurveTo((207, 412), (200, 421))
+ pen.qCurveTo((200, 415), (200.5, 392.5))
+ pen.qCurveTo((201, 370), (208, 339.5))
+ pen.qCurveTo((215, 309), (231.5, 274.5))
+ pen.qCurveTo((248, 240), (280, 211.5))
+ pen.qCurveTo((312, 183), (363, 164))
+ pen.qCurveTo((414, 145), (489, 145))
+ pen.qCurveTo((560, 143), (612, 160.5))
+ pen.qCurveTo((664, 178), (701, 205.5))
+ pen.qCurveTo((738, 233), (760.5, 266.5))
+ pen.qCurveTo((783, 300), (796, 330.5))
+ pen.qCurveTo((809, 361), (814, 383.5))
+ pen.qCurveTo((819, 406), (819, 412))
+ pen.qCurveTo((811, 402), (802, 394))
+ pen.qCurveTo((793, 386), (771, 367))
pen.closePath()
+
def draw7_(pen):
- pen.moveTo( (771, 367) )
- pen.qCurveTo( (793, 386), (802, 394) )
- pen.qCurveTo( (811, 402), (819, 412) )
- pen.qCurveTo( (819, 406), (814, 383.5) )
- pen.qCurveTo( (809, 361), (796, 330.5) )
- pen.qCurveTo( (783, 300), (760.5, 266.5) )
- pen.qCurveTo( (738, 233), (701, 205.5) )
- pen.qCurveTo( (664, 178), (612, 160.5) )
- pen.qCurveTo( (560, 143), (489, 145) )
- pen.qCurveTo( (414, 145), (363, 164) )
- pen.qCurveTo( (312, 183), (280, 211.5) )
- pen.qCurveTo( (248, 240), (231.5, 274.5) )
- pen.qCurveTo( (215, 309), (208, 339.5) )
- pen.qCurveTo( (201, 370), (200.5, 392.5) )
- pen.qCurveTo( (200, 415), (200, 421) )
- pen.qCurveTo( (207, 412), (217.5, 399) )
- pen.qCurveTo( (228, 386), (254, 360) )
+ pen.moveTo((771, 367))
+ pen.qCurveTo((793, 386), (802, 394))
+ pen.qCurveTo((811, 402), (819, 412))
+ pen.qCurveTo((819, 406), (814, 383.5))
+ pen.qCurveTo((809, 361), (796, 330.5))
+ pen.qCurveTo((783, 300), (760.5, 266.5))
+ pen.qCurveTo((738, 233), (701, 205.5))
+ pen.qCurveTo((664, 178), (612, 160.5))
+ pen.qCurveTo((560, 143), (489, 145))
+ pen.qCurveTo((414, 145), (363, 164))
+ pen.qCurveTo((312, 183), (280, 211.5))
+ pen.qCurveTo((248, 240), (231.5, 274.5))
+ pen.qCurveTo((215, 309), (208, 339.5))
+ pen.qCurveTo((201, 370), (200.5, 392.5))
+ pen.qCurveTo((200, 415), (200, 421))
+ pen.qCurveTo((207, 412), (217.5, 399))
+ pen.qCurveTo((228, 386), (254, 360))
pen.closePath()
+
def draw8_(pen):
- pen.moveTo( (771, 367) )
- pen.lineTo( (254, 360) )
- pen.qCurveTo( (228, 386), (217.5, 399) )
- pen.qCurveTo( (207, 412), (200, 421) )
- pen.qCurveTo( (200, 415), (200.5, 392.5) )
- pen.qCurveTo( (201, 370), (208, 339.5) )
- pen.qCurveTo( (215, 309), (231.5, 274.5) )
- pen.qCurveTo( (248, 240), (280, 211.5) )
- pen.qCurveTo( (312, 183), (363, 164) )
- pen.qCurveTo( (414, 145), (489, 145) )
- pen.qCurveTo( (560, 143), (612, 160.5) )
- pen.qCurveTo( (664, 178), (701, 205.5) )
- pen.qCurveTo( (738, 233), (760.5, 266.5) )
- pen.qCurveTo( (783, 300), (796, 330.5) )
- pen.qCurveTo( (809, 361), (814, 383.5) )
- pen.qCurveTo( (819, 406), (819, 412) )
- pen.qCurveTo( (811, 402), (802, 394) )
- pen.qCurveTo( (793, 386), (771, 367) )
+ pen.moveTo((771, 367))
+ pen.lineTo((254, 360))
+ pen.qCurveTo((228, 386), (217.5, 399))
+ pen.qCurveTo((207, 412), (200, 421))
+ pen.qCurveTo((200, 415), (200.5, 392.5))
+ pen.qCurveTo((201, 370), (208, 339.5))
+ pen.qCurveTo((215, 309), (231.5, 274.5))
+ pen.qCurveTo((248, 240), (280, 211.5))
+ pen.qCurveTo((312, 183), (363, 164))
+ pen.qCurveTo((414, 145), (489, 145))
+ pen.qCurveTo((560, 143), (612, 160.5))
+ pen.qCurveTo((664, 178), (701, 205.5))
+ pen.qCurveTo((738, 233), (760.5, 266.5))
+ pen.qCurveTo((783, 300), (796, 330.5))
+ pen.qCurveTo((809, 361), (814, 383.5))
+ pen.qCurveTo((819, 406), (819, 412))
+ pen.qCurveTo((811, 402), (802, 394))
+ pen.qCurveTo((793, 386), (771, 367))
pen.closePath()
@@ -160,6 +168,7 @@ class PerimeterPenTest(unittest.TestCase):
self.assertEqual(1589, round(pen.value))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/pens/pointInsidePen_test.py b/Tests/pens/pointInsidePen_test.py
index b561c43f..85936ff5 100644
--- a/Tests/pens/pointInsidePen_test.py
+++ b/Tests/pens/pointInsidePen_test.py
@@ -6,69 +6,59 @@ import unittest
class PointInsidePenTest(unittest.TestCase):
def test_line(self):
def draw_triangles(pen):
- pen.moveTo((0,0)); pen.lineTo((10,5)); pen.lineTo((10,0))
- pen.moveTo((9,1)); pen.lineTo((4,1)); pen.lineTo((9,4))
+ pen.moveTo((0, 0))
+ pen.lineTo((10, 5))
+ pen.lineTo((10, 0))
+ pen.moveTo((9, 1))
+ pen.lineTo((4, 1))
+ pen.lineTo((9, 4))
pen.closePath()
self.assertEqual(
- " *********"
- " ** *"
- " ** *"
- " * *"
- " *",
- self.render(draw_triangles, even_odd=True))
+ " *********" " ** *" " ** *" " * *" " *",
+ self.render(draw_triangles, even_odd=True),
+ )
self.assertEqual(
- " *********"
- " *******"
- " *****"
- " ***"
- " *",
- self.render(draw_triangles, even_odd=False))
+ " *********" " *******" " *****" " ***" " *",
+ self.render(draw_triangles, even_odd=False),
+ )
def test_curve(self):
def draw_curves(pen):
- pen.moveTo((0,0)); pen.curveTo((9,1), (9,4), (0,5))
- pen.moveTo((10,5)); pen.curveTo((1,4), (1,1), (10,0))
+ pen.moveTo((0, 0))
+ pen.curveTo((9, 1), (9, 4), (0, 5))
+ pen.moveTo((10, 5))
+ pen.curveTo((1, 4), (1, 1), (10, 0))
pen.closePath()
self.assertEqual(
- "*** ***"
- "**** ****"
- "*** ***"
- "**** ****"
- "*** ***",
- self.render(draw_curves, even_odd=True))
+ "*** ***" "**** ****" "*** ***" "**** ****" "*** ***",
+ self.render(draw_curves, even_odd=True),
+ )
self.assertEqual(
- "*** ***"
- "**********"
- "**********"
- "**********"
- "*** ***",
- self.render(draw_curves, even_odd=False))
+ "*** ***" "**********" "**********" "**********" "*** ***",
+ self.render(draw_curves, even_odd=False),
+ )
def test_qCurve(self):
def draw_qCurves(pen):
- pen.moveTo((0,0)); pen.qCurveTo((15,2), (0,5))
- pen.moveTo((10,5)); pen.qCurveTo((-5,3), (10,0))
+ pen.moveTo((0, 0))
+ pen.qCurveTo((15, 2), (0, 5))
+ pen.moveTo((10, 5))
+ pen.qCurveTo((-5, 3), (10, 0))
pen.closePath()
self.assertEqual(
- "*** **"
- "**** ***"
- "*** ***"
- "*** ****"
- "** ***",
- self.render(draw_qCurves, even_odd=True))
+ "*** **" "**** ***" "*** ***" "*** ****" "** ***",
+ self.render(draw_qCurves, even_odd=True),
+ )
self.assertEqual(
- "*** **"
- "**********"
- "**********"
- "**********"
- "** ***",
- self.render(draw_qCurves, even_odd=False))
+ "*** **" "**********" "**********" "**********" "** ***",
+ self.render(draw_qCurves, even_odd=False),
+ )
@staticmethod
def render(draw_function, even_odd):
@@ -83,142 +73,148 @@ class PointInsidePenTest(unittest.TestCase):
result.write(" ")
return result.getvalue()
-
def test_contour_no_solutions(self):
def draw_contour(pen):
- pen.moveTo( (969, 230) )
- pen.curveTo( (825, 348) , (715, 184) , (614, 202) )
- pen.lineTo( (614, 160) )
- pen.lineTo( (969, 160) )
+ pen.moveTo((969, 230))
+ pen.curveTo((825, 348), (715, 184), (614, 202))
+ pen.lineTo((614, 160))
+ pen.lineTo((969, 160))
pen.closePath()
- piPen = PointInsidePen(None, (750, 295)) # this point is outside
+ piPen = PointInsidePen(None, (750, 295)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
self.assertEqual(piPen.getResult(), False)
- piPen = PointInsidePen(None, (835, 190)) # this point is inside
+ piPen = PointInsidePen(None, (835, 190)) # this point is inside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 1)
self.assertEqual(piPen.getResult(), True)
def test_contour_square_closed(self):
def draw_contour(pen):
- pen.moveTo( (100, 100) )
- pen.lineTo( (-100, 100) )
- pen.lineTo( (-100, -100) )
- pen.lineTo( (100, -100) )
+ pen.moveTo((100, 100))
+ pen.lineTo((-100, 100))
+ pen.lineTo((-100, -100))
+ pen.lineTo((100, -100))
pen.closePath()
- piPen = PointInsidePen(None, (0, 0)) # this point is inside
+ piPen = PointInsidePen(None, (0, 0)) # this point is inside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 1)
self.assertEqual(piPen.getResult(), True)
def test_contour_square_opened(self):
def draw_contour(pen):
- pen.moveTo( (100, 100) )
- pen.lineTo( (-100, 100) )
- pen.lineTo( (-100, -100) )
- pen.lineTo( (100, -100) )
+ pen.moveTo((100, 100))
+ pen.lineTo((-100, 100))
+ pen.lineTo((-100, -100))
+ pen.lineTo((100, -100))
# contour not explicitly closed
- piPen = PointInsidePen(None, (0, 0)) # this point is inside
+ piPen = PointInsidePen(None, (0, 0)) # this point is inside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 1)
self.assertEqual(piPen.getResult(), True)
def test_contour_circle(self):
def draw_contour(pen):
- pen.moveTo( (0, 100) )
- pen.curveTo( (-55, 100) , (-100, 55) , (-100, 0) )
- pen.curveTo( (-100, -55) , (-55, -100) , (0, -100) )
- pen.curveTo( (55, -100) , (100, -55) , (100, 0) )
- pen.curveTo( (100, 55) , (55, 100) , (0, 100) )
+ pen.moveTo((0, 100))
+ pen.curveTo((-55, 100), (-100, 55), (-100, 0))
+ pen.curveTo((-100, -55), (-55, -100), (0, -100))
+ pen.curveTo((55, -100), (100, -55), (100, 0))
+ pen.curveTo((100, 55), (55, 100), (0, 100))
- piPen = PointInsidePen(None, (50, 50)) # this point is inside
+ piPen = PointInsidePen(None, (50, 50)) # this point is inside
draw_contour(piPen)
self.assertEqual(piPen.getResult(), True)
- piPen = PointInsidePen(None, (50, -50)) # this point is inside
+ piPen = PointInsidePen(None, (50, -50)) # this point is inside
draw_contour(piPen)
self.assertEqual(piPen.getResult(), True)
def test_contour_diamond(self):
def draw_contour(pen):
- pen.moveTo( (0, 100) )
- pen.lineTo( (100, 0) )
- pen.lineTo( (0, -100) )
- pen.lineTo( (-100, 0) )
+ pen.moveTo((0, 100))
+ pen.lineTo((100, 0))
+ pen.lineTo((0, -100))
+ pen.lineTo((-100, 0))
pen.closePath()
- piPen = PointInsidePen(None, (-200, 0)) # this point is outside
+ piPen = PointInsidePen(None, (-200, 0)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
- piPen = PointInsidePen(None, (-200, 100)) # this point is outside
+ piPen = PointInsidePen(None, (-200, 100)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
- piPen = PointInsidePen(None, (-200, -100)) # this point is outside
+ piPen = PointInsidePen(None, (-200, -100)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
- piPen = PointInsidePen(None, (-200, 50)) # this point is outside
+ piPen = PointInsidePen(None, (-200, 50)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
def test_contour_integers(self):
def draw_contour(pen):
- pen.moveTo( (728, 697) )
- pen.lineTo( (504, 699) )
- pen.curveTo( (487, 719) , (508, 783) , (556, 783) )
- pen.lineTo( (718, 783) )
- pen.curveTo( (739, 783) , (749, 712) , (728, 697) )
+ pen.moveTo((728, 697))
+ pen.lineTo((504, 699))
+ pen.curveTo((487, 719), (508, 783), (556, 783))
+ pen.lineTo((718, 783))
+ pen.curveTo((739, 783), (749, 712), (728, 697))
pen.closePath()
- piPen = PointInsidePen(None, (416, 783)) # this point is outside
+ piPen = PointInsidePen(None, (416, 783)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
def test_contour_decimals(self):
def draw_contour(pen):
- pen.moveTo( (727.546875, 697.0) )
- pen.lineTo( (504.375, 698.515625) )
- pen.curveTo( (487.328125, 719.359375), (507.84375, 783.140625), (555.796875, 783.140625) )
- pen.lineTo( (717.96875, 783.140625) )
- pen.curveTo( (738.890625, 783.140625), (748.796875, 711.5), (727.546875, 697.0) )
+ pen.moveTo((727.546875, 697.0))
+ pen.lineTo((504.375, 698.515625))
+ pen.curveTo(
+ (487.328125, 719.359375),
+ (507.84375, 783.140625),
+ (555.796875, 783.140625),
+ )
+ pen.lineTo((717.96875, 783.140625))
+ pen.curveTo(
+ (738.890625, 783.140625), (748.796875, 711.5), (727.546875, 697.0)
+ )
pen.closePath()
- piPen = PointInsidePen(None, (416.625, 783.140625)) # this point is outside
+ piPen = PointInsidePen(None, (416.625, 783.140625)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
def test_contour2_integers(self):
def draw_contour(pen):
- pen.moveTo( (51, 22) )
- pen.lineTo( (51, 74) )
- pen.lineTo( (83, 50) )
- pen.curveTo( (83, 49) , (82, 48) , (82, 47) )
+ pen.moveTo((51, 22))
+ pen.lineTo((51, 74))
+ pen.lineTo((83, 50))
+ pen.curveTo((83, 49), (82, 48), (82, 47))
pen.closePath()
- piPen = PointInsidePen(None, (21, 50)) # this point is outside
+ piPen = PointInsidePen(None, (21, 50)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
def test_contour2_decimals(self):
def draw_contour(pen):
- pen.moveTo( (51.25, 21.859375) )
- pen.lineTo( (51.25, 73.828125) )
- pen.lineTo( (82.5, 50.0) )
- pen.curveTo( (82.5, 49.09375) , (82.265625, 48.265625) , (82.234375, 47.375) )
+ pen.moveTo((51.25, 21.859375))
+ pen.lineTo((51.25, 73.828125))
+ pen.lineTo((82.5, 50.0))
+ pen.curveTo((82.5, 49.09375), (82.265625, 48.265625), (82.234375, 47.375))
pen.closePath()
- piPen = PointInsidePen(None, (21.25, 50.0)) # this point is outside
+ piPen = PointInsidePen(None, (21.25, 50.0)) # this point is outside
draw_contour(piPen)
self.assertEqual(piPen.getWinding(), 0)
+
if __name__ == "__main__":
import sys
- sys.exit(unittest.main())
+ sys.exit(unittest.main())
diff --git a/Tests/pens/pointPen_test.py b/Tests/pens/pointPen_test.py
index a9201780..e811826f 100644
--- a/Tests/pens/pointPen_test.py
+++ b/Tests/pens/pointPen_test.py
@@ -1,12 +1,16 @@
import unittest
from fontTools.pens.basePen import AbstractPen
-from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen, \
- SegmentToPointPen, GuessSmoothPointPen, ReverseContourPointPen
+from fontTools.pens.pointPen import (
+ AbstractPointPen,
+ PointToSegmentPen,
+ SegmentToPointPen,
+ GuessSmoothPointPen,
+ ReverseContourPointPen,
+)
class _TestSegmentPen(AbstractPen):
-
def __init__(self):
self._commands = []
@@ -49,7 +53,6 @@ def _reprKwargs(kwargs):
class _TestPointPen(AbstractPointPen):
-
def __init__(self):
self._commands = []
@@ -63,8 +66,9 @@ class _TestPointPen(AbstractPointPen):
items.extend(_reprKwargs(kwargs))
self._commands.append("beginPath(%s)" % ", ".join(items))
- def addPoint(self, pt, segmentType=None, smooth=False, name=None,
- identifier=None, **kwargs):
+ def addPoint(
+ self, pt, segmentType=None, smooth=False, name=None, identifier=None, **kwargs
+ ):
items = ["%s" % (pt,)]
if segmentType is not None:
items.append("segmentType='%s'" % segmentType)
@@ -89,7 +93,6 @@ class _TestPointPen(AbstractPointPen):
class PointToSegmentPenTest(unittest.TestCase):
-
def test_open(self):
pen = _TestSegmentPen()
ppen = PointToSegmentPen(pen)
@@ -123,7 +126,7 @@ class PointToSegmentPenTest(unittest.TestCase):
def test_quad(self):
pen = _TestSegmentPen()
ppen = PointToSegmentPen(pen)
- ppen.beginPath(identifier='foo')
+ ppen.beginPath(identifier="foo")
ppen.addPoint((10, 10), "line")
ppen.addPoint((10, 40))
ppen.addPoint((40, 40))
@@ -150,9 +153,11 @@ class PointToSegmentPenTest(unittest.TestCase):
ppen.addPoint((20, 20))
ppen.addPoint((20, 40), "curve")
ppen.endPath()
- self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') addPoint((10, 20)) "
- "addPoint((20, 20)) addPoint((20, 40), segmentType='curve') endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((10, 10), segmentType='line') addPoint((10, 20)) "
+ "addPoint((20, 20)) addPoint((20, 40), segmentType='curve') endPath()",
+ repr(tpen),
+ )
def test_closed_outputImpliedClosingLine(self):
tpen = _TestSegmentPen()
@@ -168,7 +173,7 @@ class PointToSegmentPenTest(unittest.TestCase):
"20 20 lineto "
"10 10 lineto " # explicit closing line
"closepath",
- repr(tpen)
+ repr(tpen),
)
def test_closed_line_overlapping_start_end_points(self):
@@ -193,7 +198,7 @@ class PointToSegmentPenTest(unittest.TestCase):
"0 651 lineto "
"0 651 lineto "
"closepath",
- repr(tpen)
+ repr(tpen),
)
def test_roundTrip2(self):
@@ -212,19 +217,19 @@ class PointToSegmentPenTest(unittest.TestCase):
"addPoint((0, 101), segmentType='line') "
"addPoint((0, 651), segmentType='line') "
"endPath()",
- repr(tpen)
+ repr(tpen),
)
class TestSegmentToPointPen(unittest.TestCase):
-
def test_move(self):
tpen = _TestPointPen()
pen = SegmentToPointPen(tpen)
pen.moveTo((10, 10))
pen.endPath()
- self.assertEqual("beginPath() addPoint((10, 10), segmentType='move') endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((10, 10), segmentType='move') endPath()", repr(tpen)
+ )
def test_poly(self):
tpen = _TestPointPen()
@@ -233,10 +238,12 @@ class TestSegmentToPointPen(unittest.TestCase):
pen.lineTo((10, 20))
pen.lineTo((20, 20))
pen.closePath()
- self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') "
- "addPoint((10, 20), segmentType='line') "
- "addPoint((20, 20), segmentType='line') endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((10, 10), segmentType='line') "
+ "addPoint((10, 20), segmentType='line') "
+ "addPoint((20, 20), segmentType='line') endPath()",
+ repr(tpen),
+ )
def test_cubic(self):
tpen = _TestPointPen()
@@ -244,9 +251,12 @@ class TestSegmentToPointPen(unittest.TestCase):
pen.moveTo((10, 10))
pen.curveTo((10, 20), (20, 20), (20, 10))
pen.closePath()
- self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') "
- "addPoint((10, 20)) addPoint((20, 20)) addPoint((20, 10), "
- "segmentType='curve') endPath()", repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((10, 10), segmentType='line') "
+ "addPoint((10, 20)) addPoint((20, 20)) addPoint((20, 10), "
+ "segmentType='curve') endPath()",
+ repr(tpen),
+ )
def test_quad(self):
tpen = _TestPointPen()
@@ -254,19 +264,23 @@ class TestSegmentToPointPen(unittest.TestCase):
pen.moveTo((10, 10))
pen.qCurveTo((10, 20), (20, 20), (20, 10))
pen.closePath()
- self.assertEqual("beginPath() addPoint((10, 10), segmentType='line') "
- "addPoint((10, 20)) addPoint((20, 20)) "
- "addPoint((20, 10), segmentType='qcurve') endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((10, 10), segmentType='line') "
+ "addPoint((10, 20)) addPoint((20, 20)) "
+ "addPoint((20, 10), segmentType='qcurve') endPath()",
+ repr(tpen),
+ )
def test_quad2(self):
tpen = _TestPointPen()
pen = SegmentToPointPen(tpen)
pen.qCurveTo((10, 20), (20, 20), (20, 10), (10, 10), None)
pen.closePath()
- self.assertEqual("beginPath() addPoint((10, 20)) addPoint((20, 20)) "
- "addPoint((20, 10)) addPoint((10, 10)) endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((10, 20)) addPoint((20, 20)) "
+ "addPoint((20, 10)) addPoint((10, 10)) endPath()",
+ repr(tpen),
+ )
def test_roundTrip1(self):
spen = _TestSegmentPen()
@@ -282,31 +296,34 @@ class TestSegmentToPointPen(unittest.TestCase):
pen = SegmentToPointPen(PointToSegmentPen(spen))
pen.qCurveTo((10, 20), (20, 20), (20, 10), (10, 10), None)
pen.closePath()
- pen.addComponent('base', [1, 0, 0, 1, 0, 0])
- self.assertEqual("10 20 20 20 20 10 10 10 None qcurveto closepath "
- "'base' [1, 0, 0, 1, 0, 0] addcomponent",
- repr(spen))
+ pen.addComponent("base", [1, 0, 0, 1, 0, 0])
+ self.assertEqual(
+ "10 20 20 20 20 10 10 10 None qcurveto closepath "
+ "'base' [1, 0, 0, 1, 0, 0] addcomponent",
+ repr(spen),
+ )
class TestGuessSmoothPointPen(unittest.TestCase):
-
def test_guessSmooth_exact(self):
tpen = _TestPointPen()
pen = GuessSmoothPointPen(tpen)
pen.beginPath(identifier="foo")
pen.addPoint((0, 100), segmentType="curve")
pen.addPoint((0, 200))
- pen.addPoint((400, 200), identifier='bar')
+ pen.addPoint((400, 200), identifier="bar")
pen.addPoint((400, 100), segmentType="curve")
pen.addPoint((400, 0))
pen.addPoint((0, 0))
pen.endPath()
- self.assertEqual("beginPath(identifier='foo') "
- "addPoint((0, 100), segmentType='curve', smooth=True) "
- "addPoint((0, 200)) addPoint((400, 200), identifier='bar') "
- "addPoint((400, 100), segmentType='curve', smooth=True) "
- "addPoint((400, 0)) addPoint((0, 0)) endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath(identifier='foo') "
+ "addPoint((0, 100), segmentType='curve', smooth=True) "
+ "addPoint((0, 200)) addPoint((400, 200), identifier='bar') "
+ "addPoint((400, 100), segmentType='curve', smooth=True) "
+ "addPoint((400, 0)) addPoint((0, 0)) endPath()",
+ repr(tpen),
+ )
def test_guessSmooth_almost(self):
tpen = _TestPointPen()
@@ -319,11 +336,13 @@ class TestGuessSmoothPointPen(unittest.TestCase):
pen.addPoint((400, 0))
pen.addPoint((0, 0))
pen.endPath()
- self.assertEqual("beginPath() addPoint((0, 100), segmentType='curve', smooth=True) "
- "addPoint((1, 200)) addPoint((395, 200)) "
- "addPoint((400, 100), segmentType='curve', smooth=True) "
- "addPoint((400, 0)) addPoint((0, 0)) endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((0, 100), segmentType='curve', smooth=True) "
+ "addPoint((1, 200)) addPoint((395, 200)) "
+ "addPoint((400, 100), segmentType='curve', smooth=True) "
+ "addPoint((400, 0)) addPoint((0, 0)) endPath()",
+ repr(tpen),
+ )
def test_guessSmooth_tangent(self):
tpen = _TestPointPen()
@@ -335,24 +354,26 @@ class TestGuessSmoothPointPen(unittest.TestCase):
pen.addPoint((300, 200))
pen.addPoint((400, 200), segmentType="curve")
pen.endPath()
- self.assertEqual("beginPath() addPoint((0, 0), segmentType='move') "
- "addPoint((0, 100), segmentType='line', smooth=True) "
- "addPoint((3, 200)) addPoint((300, 200)) "
- "addPoint((400, 200), segmentType='curve') endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() addPoint((0, 0), segmentType='move') "
+ "addPoint((0, 100), segmentType='line', smooth=True) "
+ "addPoint((3, 200)) addPoint((300, 200)) "
+ "addPoint((400, 200), segmentType='curve') endPath()",
+ repr(tpen),
+ )
-class TestReverseContourPointPen(unittest.TestCase):
+class TestReverseContourPointPen(unittest.TestCase):
def test_singlePoint(self):
tpen = _TestPointPen()
pen = ReverseContourPointPen(tpen)
pen.beginPath()
pen.addPoint((0, 0), segmentType="move")
pen.endPath()
- self.assertEqual("beginPath() "
- "addPoint((0, 0), segmentType='move') "
- "endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() " "addPoint((0, 0), segmentType='move') " "endPath()",
+ repr(tpen),
+ )
def test_line(self):
tpen = _TestPointPen()
@@ -361,11 +382,13 @@ class TestReverseContourPointPen(unittest.TestCase):
pen.addPoint((0, 0), segmentType="move")
pen.addPoint((0, 100), segmentType="line")
pen.endPath()
- self.assertEqual("beginPath() "
- "addPoint((0, 100), segmentType='move') "
- "addPoint((0, 0), segmentType='line') "
- "endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((0, 100), segmentType='move') "
+ "addPoint((0, 0), segmentType='line') "
+ "endPath()",
+ repr(tpen),
+ )
def test_triangle(self):
tpen = _TestPointPen()
@@ -375,12 +398,14 @@ class TestReverseContourPointPen(unittest.TestCase):
pen.addPoint((0, 100), segmentType="line")
pen.addPoint((100, 100), segmentType="line")
pen.endPath()
- self.assertEqual("beginPath() "
- "addPoint((0, 0), segmentType='line') "
- "addPoint((100, 100), segmentType='line') "
- "addPoint((0, 100), segmentType='line') "
- "endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((0, 0), segmentType='line') "
+ "addPoint((100, 100), segmentType='line') "
+ "addPoint((0, 100), segmentType='line') "
+ "endPath()",
+ repr(tpen),
+ )
def test_cubicOpen(self):
tpen = _TestPointPen()
@@ -391,13 +416,15 @@ class TestReverseContourPointPen(unittest.TestCase):
pen.addPoint((100, 200))
pen.addPoint((200, 200), segmentType="curve")
pen.endPath()
- self.assertEqual("beginPath() "
- "addPoint((200, 200), segmentType='move') "
- "addPoint((100, 200)) "
- "addPoint((0, 100)) "
- "addPoint((0, 0), segmentType='curve') "
- "endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((200, 200), segmentType='move') "
+ "addPoint((100, 200)) "
+ "addPoint((0, 100)) "
+ "addPoint((0, 0), segmentType='curve') "
+ "endPath()",
+ repr(tpen),
+ )
def test_quadOpen(self):
tpen = _TestPointPen()
@@ -408,13 +435,15 @@ class TestReverseContourPointPen(unittest.TestCase):
pen.addPoint((100, 200))
pen.addPoint((200, 200), segmentType="qcurve")
pen.endPath()
- self.assertEqual("beginPath() "
- "addPoint((200, 200), segmentType='move') "
- "addPoint((100, 200)) "
- "addPoint((0, 100)) "
- "addPoint((0, 0), segmentType='qcurve') "
- "endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((200, 200), segmentType='move') "
+ "addPoint((100, 200)) "
+ "addPoint((0, 100)) "
+ "addPoint((0, 0), segmentType='qcurve') "
+ "endPath()",
+ repr(tpen),
+ )
def test_cubicClosed(self):
tpen = _TestPointPen()
@@ -425,13 +454,15 @@ class TestReverseContourPointPen(unittest.TestCase):
pen.addPoint((100, 200))
pen.addPoint((200, 200), segmentType="curve")
pen.endPath()
- self.assertEqual("beginPath() "
- "addPoint((0, 0), segmentType='curve') "
- "addPoint((200, 200), segmentType='line') "
- "addPoint((100, 200)) "
- "addPoint((0, 100)) "
- "endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((0, 0), segmentType='curve') "
+ "addPoint((200, 200), segmentType='line') "
+ "addPoint((100, 200)) "
+ "addPoint((0, 100)) "
+ "endPath()",
+ repr(tpen),
+ )
def test_quadClosedOffCurveStart(self):
tpen = _TestPointPen()
@@ -442,32 +473,36 @@ class TestReverseContourPointPen(unittest.TestCase):
pen.addPoint((0, 0), segmentType="line")
pen.addPoint((0, 100))
pen.endPath()
- self.assertEqual("beginPath() "
- "addPoint((100, 200)) "
- "addPoint((0, 100)) "
- "addPoint((0, 0), segmentType='qcurve') "
- "addPoint((200, 200), segmentType='line') "
- "endPath()",
- repr(tpen))
+ self.assertEqual(
+ "beginPath() "
+ "addPoint((100, 200)) "
+ "addPoint((0, 100)) "
+ "addPoint((0, 0), segmentType='qcurve') "
+ "addPoint((200, 200), segmentType='line') "
+ "endPath()",
+ repr(tpen),
+ )
def test_quadNoOnCurve(self):
tpen = _TestPointPen()
pen = ReverseContourPointPen(tpen)
- pen.beginPath(identifier='bar')
+ pen.beginPath(identifier="bar")
pen.addPoint((0, 0))
- pen.addPoint((0, 100), identifier='foo', arbitrary='foo')
+ pen.addPoint((0, 100), identifier="foo", arbitrary="foo")
pen.addPoint((100, 200), arbitrary=123)
pen.addPoint((200, 200))
pen.endPath()
- pen.addComponent("base", [1, 0, 0, 1, 0, 0], identifier='foo')
- self.assertEqual("beginPath(identifier='bar') "
- "addPoint((0, 0)) "
- "addPoint((200, 200)) "
- "addPoint((100, 200), arbitrary=123) "
- "addPoint((0, 100), identifier='foo', arbitrary='foo') "
- "endPath() "
- "addComponent('base', [1, 0, 0, 1, 0, 0], identifier='foo')",
- repr(tpen))
+ pen.addComponent("base", [1, 0, 0, 1, 0, 0], identifier="foo")
+ self.assertEqual(
+ "beginPath(identifier='bar') "
+ "addPoint((0, 0)) "
+ "addPoint((200, 200)) "
+ "addPoint((100, 200), arbitrary=123) "
+ "addPoint((0, 100), identifier='foo', arbitrary='foo') "
+ "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
@@ -486,5 +521,5 @@ class TestReverseContourPointPen(unittest.TestCase):
"addPoint((0, 101), segmentType='line') "
"addPoint((0, 101), segmentType='line') "
"endPath()",
- repr(tpen)
+ repr(tpen),
)
diff --git a/Tests/pens/qu2cuPen_test.py b/Tests/pens/qu2cuPen_test.py
new file mode 100644
index 00000000..94449194
--- /dev/null
+++ b/Tests/pens/qu2cuPen_test.py
@@ -0,0 +1,253 @@
+# 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 sys
+import unittest
+
+from fontTools.pens.qu2cuPen import Qu2CuPen
+from fontTools.pens.recordingPen import RecordingPen
+from textwrap import dedent
+import pytest
+
+try:
+ from .utils import CUBIC_GLYPHS, QUAD_GLYPHS
+ from .utils import DummyGlyph
+ from .utils import DummyPen
+except ImportError as e:
+ pytest.skip(str(e), allow_module_level=True)
+
+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.
+ Note: We currently don't have a PointPen.
+ """
+
+ 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)()
+ cubicpen = self.Qu2CuPen(pen, MAX_ERR, all_cubic=True, **kwargs)
+ getattr(glyph, self.draw_method_name)(cubicpen)
+ 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(QUAD_GLYPHS["a"], CUBIC_GLYPHS["a"])
+ self.expect_glyph(QUAD_GLYPHS["A"], CUBIC_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_reverse_direction(self):
+ for name in ("a", "A", "Eacute"):
+ source = QUAD_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 QUAD_GLYPHS.keys():
+ source = QUAD_GLYPHS[name]
+ self.convert_glyph(source, stats=stats)
+
+ self.assertTrue(stats)
+ self.assertTrue("2" in stats)
+ self.assertEqual(type(stats["2"]), int)
+
+ def test_addComponent(self):
+ pen = self.Pen()
+ cubicpen = self.Qu2CuPen(pen, MAX_ERR)
+ cubicpen.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 TestQu2CuPen(unittest.TestCase, _TestPenMixin):
+ def __init__(self, *args, **kwargs):
+ super(TestQu2CuPen, self).__init__(*args, **kwargs)
+ self.Glyph = DummyGlyph
+ self.Pen = DummyPen
+ self.Qu2CuPen = Qu2CuPen
+ self.pen_getter_name = "getPen"
+ self.draw_method_name = "draw"
+
+ def test_qCurveTo_1_point(self):
+ pen = DummyPen()
+ cubicpen = Qu2CuPen(pen, MAX_ERR)
+ cubicpen.moveTo((0, 0))
+ cubicpen.qCurveTo((1, 1))
+ cubicpen.closePath()
+
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1))",
+ "pen.closePath()",
+ ],
+ )
+
+ def test_qCurveTo_2_points(self):
+ pen = DummyPen()
+ cubicpen = Qu2CuPen(pen, MAX_ERR)
+ cubicpen.moveTo((0, 0))
+ cubicpen.qCurveTo((1, 1), (2, 2))
+ cubicpen.closePath()
+
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((1, 1), (2, 2))",
+ "pen.closePath()",
+ ],
+ )
+
+ def test_qCurveTo_3_points_no_conversion(self):
+ pen = DummyPen()
+ cubicpen = Qu2CuPen(pen, MAX_ERR)
+ cubicpen.moveTo((0, 0))
+ cubicpen.qCurveTo((0, 3), (1, 3), (1, 0))
+ cubicpen.closePath()
+
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.qCurveTo((0, 3), (1, 3), (1, 0))",
+ "pen.closePath()",
+ ],
+ )
+
+ def test_qCurveTo_no_oncurve_points(self):
+ pen = DummyPen()
+ cubicpen = Qu2CuPen(pen, MAX_ERR)
+ cubicpen.qCurveTo((0, 0), (1, 0), (1, 1), (0, 1), None)
+ cubicpen.closePath()
+
+ self.assertEqual(
+ str(pen).splitlines(),
+ ["pen.qCurveTo((0, 0), (1, 0), (1, 1), (0, 1), None)", "pen.closePath()"],
+ )
+
+ def test_curveTo_1_point(self):
+ pen = DummyPen()
+ cubicpen = Qu2CuPen(pen, MAX_ERR)
+ cubicpen.moveTo((0, 0))
+ cubicpen.curveTo((1, 1))
+ cubicpen.closePath()
+
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.curveTo((1, 1))",
+ "pen.closePath()",
+ ],
+ )
+
+ def test_curveTo_2_points(self):
+ pen = DummyPen()
+ cubicpen = Qu2CuPen(pen, MAX_ERR)
+ cubicpen.moveTo((0, 0))
+ cubicpen.curveTo((1, 1), (2, 2))
+ cubicpen.closePath()
+
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.curveTo((1, 1), (2, 2))",
+ "pen.closePath()",
+ ],
+ )
+
+ def test_curveTo_3_points(self):
+ pen = DummyPen()
+ cubicpen = Qu2CuPen(pen, MAX_ERR)
+ cubicpen.moveTo((0, 0))
+ cubicpen.curveTo((1, 1), (2, 2), (3, 3))
+ cubicpen.closePath()
+
+ self.assertEqual(
+ str(pen).splitlines(),
+ [
+ "pen.moveTo((0, 0))",
+ "pen.curveTo((1, 1), (2, 2), (3, 3))",
+ "pen.closePath()",
+ ],
+ )
+
+ def test_all_cubic(self):
+ inPen = RecordingPen()
+ inPen.value = [
+ ("moveTo", ((1204, 347),)),
+ ("qCurveTo", ((1255, 347), (1323, 433), (1323, 467))),
+ ("qCurveTo", ((1323, 478), (1310, 492), (1302, 492))),
+ ("qCurveTo", ((1295, 492), (1289, 484))),
+ ("lineTo", ((1272, 461),)),
+ ("qCurveTo", ((1256, 439), (1221, 416), (1200, 416))),
+ ("qCurveTo", ((1181, 416), (1141, 440), (1141, 462))),
+ ("qCurveTo", ((1141, 484), (1190, 565), (1190, 594))),
+ ("qCurveTo", ((1190, 607), (1181, 634), (1168, 634))),
+ ("qCurveTo", ((1149, 634), (1146, 583), (1081, 496), (1081, 463))),
+ ("qCurveTo", ((1081, 417), (1164, 347), (1204, 347))),
+ ("closePath", ()),
+ ]
+
+ outPen = RecordingPen()
+ q2cPen = Qu2CuPen(outPen, 1.0, all_cubic=True)
+ inPen.replay(q2cPen)
+
+ print(outPen.value)
+
+ assert not any(typ == "qCurveTo" for typ, _ in outPen.value)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Tests/pens/quartzPen_test.py b/Tests/pens/quartzPen_test.py
index 3a81d97f..0caa379d 100644
--- a/Tests/pens/quartzPen_test.py
+++ b/Tests/pens/quartzPen_test.py
@@ -12,11 +12,11 @@ try:
PATH_ELEMENTS = {
# CG constant key desc num_points
- kCGPathElementMoveToPoint: ('moveto', 1),
- kCGPathElementAddLineToPoint: ('lineto', 1),
- kCGPathElementAddCurveToPoint: ('curveto', 3),
- kCGPathElementAddQuadCurveToPoint: ('qcurveto', 2),
- kCGPathElementCloseSubpath: ('close', 0),
+ kCGPathElementMoveToPoint: ("moveto", 1),
+ kCGPathElementAddLineToPoint: ("lineto", 1),
+ kCGPathElementAddCurveToPoint: ("curveto", 3),
+ kCGPathElementAddQuadCurveToPoint: ("qcurveto", 2),
+ kCGPathElementCloseSubpath: ("close", 0),
}
PYOBJC_AVAILABLE = True
@@ -65,7 +65,7 @@ class QuartzPenTest(unittest.TestCase):
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)
+ quartzPathToString(pen.path),
)
def test_empty(self):
@@ -73,6 +73,7 @@ class QuartzPenTest(unittest.TestCase):
self.assertEqual("", quartzPathToString(pen.path))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/pens/reverseContourPen_test.py b/Tests/pens/reverseContourPen_test.py
index 9c715404..c250847e 100644
--- a/Tests/pens/reverseContourPen_test.py
+++ b/Tests/pens/reverseContourPen_test.py
@@ -6,331 +6,512 @@ import pytest
TEST_DATA = [
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('lineTo', ((2, 2),)),
- ('lineTo', ((3, 3),)), # last not on move, line is implied
- ('closePath', ()),
- ],
- [
- ('moveTo', ((0, 0),)),
- ('lineTo', ((3, 3),)),
- ('lineTo', ((2, 2),)),
- ('lineTo', ((1, 1),)),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((3, 3),)), # last not on move, line is implied
+ ("closePath", ()),
+ ],
+ False, # outputImpliedClosingLine
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((3, 3),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((1, 1),)),
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('lineTo', ((2, 2),)),
- ('lineTo', ((0, 0),)), # last on move, no implied line
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((3, 3),)), # last line does not overlap move...
+ ("closePath", ()),
],
+ True, # outputImpliedClosingLine
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((3, 3),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((0, 0),)), # ... but closing line is NOT implied
+ ("closePath", ()),
+ ],
+ ),
+ (
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((0, 0),)), # last line overlaps move, explicit line
+ ("closePath", ()),
+ ],
+ False,
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((2, 2),)),
- ('lineTo', ((1, 1),)),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((1, 1),)),
+ ("closePath", ()), # closing line implied
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('lineTo', ((2, 2),)),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((0, 0),)), # last line overlaps move...
+ ("closePath", ()),
],
+ True,
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((2, 2),)),
- ('lineTo', ((1, 1),)),
- ('lineTo', ((0, 0),)),
- ('lineTo', ((0, 0),)),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((0, 0),)), # ... but line is NOT implied
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((0, 0),)), # duplicate lineTo following moveTo
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((2, 2),)),
+ ("closePath", ()),
+ ],
+ False,
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((0, 0),)), # extra explicit lineTo is always emitted to
+ ("lineTo", ((0, 0),)), # disambiguate from an implicit closing line
+ ("closePath", ()),
],
+ ),
+ (
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((0, 0),)), # duplicate lineTo following moveTo
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((2, 2),)),
+ ("closePath", ()),
+ ],
+ True,
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((2, 2),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((0, 0),)), # duplicate lineTo is retained also in this case,
+ ("lineTo", ((0, 0),)), # same result as with outputImpliedClosingLine=False
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('curveTo', ((1, 1), (2, 2), (3, 3))),
- ('curveTo', ((4, 4), (5, 5), (0, 0))),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("closePath", ()),
],
+ False,
[
- ('moveTo', ((0, 0),)),
- ('curveTo', ((5, 5), (4, 4), (3, 3))),
- ('curveTo', ((2, 2), (1, 1), (0, 0))),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('curveTo', ((1, 1), (2, 2), (3, 3))),
- ('curveTo', ((4, 4), (5, 5), (6, 6))),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("closePath", ()),
],
+ True,
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((6, 6),)), # implied line
- ('curveTo', ((5, 5), (4, 4), (3, 3))),
- ('curveTo', ((2, 2), (1, 1), (0, 0))),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("lineTo", ((0, 0),)),
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)), # this line becomes implied
- ('curveTo', ((2, 2), (3, 3), (4, 4))),
- ('curveTo', ((5, 5), (6, 6), (7, 7))),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 1), (2, 2), (3, 3))),
+ ("curveTo", ((4, 4), (5, 5), (0, 0))), # closed curveTo overlaps moveTo
+ ("closePath", ()),
],
+ False,
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((7, 7),)),
- ('curveTo', ((6, 6), (5, 5), (4, 4))),
- ('curveTo', ((3, 3), (2, 2), (1, 1))),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)), # no extra lineTo added here
+ ("curveTo", ((5, 5), (4, 4), (3, 3))),
+ ("curveTo", ((2, 2), (1, 1), (0, 0))),
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('qCurveTo', ((1, 1), (2, 2))),
- ('qCurveTo', ((3, 3), (0, 0))),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 1), (2, 2), (3, 3))),
+ ("curveTo", ((4, 4), (5, 5), (0, 0))), # closed curveTo overlaps moveTo
+ ("closePath", ()),
],
+ True,
[
- ('moveTo', ((0, 0),)),
- ('qCurveTo', ((3, 3), (2, 2))),
- ('qCurveTo', ((1, 1), (0, 0))),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)), # no extra lineTo added here, same as preceding
+ ("curveTo", ((5, 5), (4, 4), (3, 3))),
+ ("curveTo", ((2, 2), (1, 1), (0, 0))),
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('qCurveTo', ((1, 1), (2, 2))),
- ('qCurveTo', ((3, 3), (4, 4))),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 1), (2, 2), (3, 3))),
+ ("curveTo", ((4, 4), (5, 5), (6, 6))), # closed curve not overlapping move
+ ("closePath", ()),
],
+ False,
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((4, 4),)),
- ('qCurveTo', ((3, 3), (2, 2))),
- ('qCurveTo', ((1, 1), (0, 0))),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((6, 6),)), # the previously implied line
+ ("curveTo", ((5, 5), (4, 4), (3, 3))),
+ ("curveTo", ((2, 2), (1, 1), (0, 0))),
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('qCurveTo', ((2, 2), (3, 3))),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 1), (2, 2), (3, 3))),
+ ("curveTo", ((4, 4), (5, 5), (6, 6))), # closed curve not overlapping move
+ ("closePath", ()),
],
+ True,
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((3, 3),)),
- ('qCurveTo', ((2, 2), (1, 1))),
- ('closePath', ()),
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((6, 6),)), # the previously implied line (same as above)
+ ("curveTo", ((5, 5), (4, 4), (3, 3))),
+ ("curveTo", ((2, 2), (1, 1), (0, 0))),
+ ("closePath", ()),
+ ],
),
(
[
- ('addComponent', ('a', (1, 0, 0, 1, 0, 0)))
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)), # this line becomes implied
+ ("curveTo", ((2, 2), (3, 3), (4, 4))),
+ ("curveTo", ((5, 5), (6, 6), (7, 7))),
+ ("closePath", ()),
],
+ False,
[
- ('addComponent', ('a', (1, 0, 0, 1, 0, 0)))
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((7, 7),)),
+ ("curveTo", ((6, 6), (5, 5), (4, 4))),
+ ("curveTo", ((3, 3), (2, 2), (1, 1))),
+ ("closePath", ()),
+ ],
),
(
- [], []
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)), # this line...
+ ("curveTo", ((2, 2), (3, 3), (4, 4))),
+ ("curveTo", ((5, 5), (6, 6), (7, 7))),
+ ("closePath", ()),
+ ],
+ True,
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((7, 7),)),
+ ("curveTo", ((6, 6), (5, 5), (4, 4))),
+ ("curveTo", ((3, 3), (2, 2), (1, 1))),
+ ("lineTo", ((0, 0),)), # ... does NOT become implied
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('endPath', ()),
+ ("moveTo", ((0, 0),)),
+ ("qCurveTo", ((1, 1), (2, 2))),
+ ("qCurveTo", ((3, 3), (0, 0))), # closed qCurve overlaps move
+ ("closePath", ()),
],
+ False,
[
- ('moveTo', ((0, 0),)),
- ('endPath', ()),
+ ("moveTo", ((0, 0),)), # no extra lineTo added here
+ ("qCurveTo", ((3, 3), (2, 2))),
+ ("qCurveTo", ((1, 1), (0, 0))),
+ ("closePath", ()),
],
),
(
[
- ('moveTo', ((0, 0),)),
- ('closePath', ()),
+ ("moveTo", ((0, 0),)),
+ ("qCurveTo", ((1, 1), (2, 2))),
+ ("qCurveTo", ((3, 3), (0, 0))), # closed qCurve overlaps move
+ ("closePath", ()),
],
+ True, # <--
[
- ('moveTo', ((0, 0),)),
- ('endPath', ()), # single-point paths is always open
+ ("moveTo", ((0, 0),)), # no extra lineTo added here, same as above
+ ("qCurveTo", ((3, 3), (2, 2))),
+ ("qCurveTo", ((1, 1), (0, 0))),
+ ("closePath", ()),
],
),
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('endPath', ())
+ ("moveTo", ((0, 0),)),
+ ("qCurveTo", ((1, 1), (2, 2))),
+ ("qCurveTo", ((3, 3), (4, 4))), # closed qCurve not overlapping move
+ ("closePath", ()),
],
+ False,
[
- ('moveTo', ((1, 1),)),
- ('lineTo', ((0, 0),)),
- ('endPath', ())
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((4, 4),)), # the previously implied line
+ ("qCurveTo", ((3, 3), (2, 2))),
+ ("qCurveTo", ((1, 1), (0, 0))),
+ ("closePath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('curveTo', ((1, 1), (2, 2), (3, 3))),
- ('endPath', ())
+ ("moveTo", ((0, 0),)),
+ ("qCurveTo", ((1, 1), (2, 2))),
+ ("qCurveTo", ((3, 3), (4, 4))), # closed qCurve not overlapping move
+ ("closePath", ()),
],
+ True,
[
- ('moveTo', ((3, 3),)),
- ('curveTo', ((2, 2), (1, 1), (0, 0))),
- ('endPath', ())
- ]
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((4, 4),)), # the previously implied line (same as above)
+ ("qCurveTo", ((3, 3), (2, 2))),
+ ("qCurveTo", ((1, 1), (0, 0))),
+ ("closePath", ()),
+ ],
+ ),
+ (
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("qCurveTo", ((2, 2), (3, 3))),
+ ("closePath", ()),
+ ],
+ False,
+ [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((3, 3),)),
+ ("qCurveTo", ((2, 2), (1, 1))),
+ ("closePath", ()),
+ ],
+ ),
+ (
+ [("addComponent", ("a", (1, 0, 0, 1, 0, 0)))],
+ False,
+ [("addComponent", ("a", (1, 0, 0, 1, 0, 0)))],
),
+ ([], False, []),
(
[
- ('moveTo', ((0, 0),)),
- ('curveTo', ((1, 1), (2, 2), (3, 3))),
- ('lineTo', ((4, 4),)),
- ('endPath', ())
+ ("moveTo", ((0, 0),)),
+ ("endPath", ()),
],
+ False,
[
- ('moveTo', ((4, 4),)),
- ('lineTo', ((3, 3),)),
- ('curveTo', ((2, 2), (1, 1), (0, 0))),
- ('endPath', ())
- ]
+ ("moveTo", ((0, 0),)),
+ ("endPath", ()),
+ ],
),
(
[
- ('moveTo', ((0, 0),)),
- ('lineTo', ((1, 1),)),
- ('curveTo', ((2, 2), (3, 3), (4, 4))),
- ('endPath', ())
+ ("moveTo", ((0, 0),)),
+ ("closePath", ()),
],
+ False,
[
- ('moveTo', ((4, 4),)),
- ('curveTo', ((3, 3), (2, 2), (1, 1))),
- ('lineTo', ((0, 0),)),
- ('endPath', ())
- ]
+ ("moveTo", ((0, 0),)),
+ ("endPath", ()), # single-point paths is always open
+ ],
+ ),
+ (
+ [("moveTo", ((0, 0),)), ("lineTo", ((1, 1),)), ("endPath", ())],
+ False,
+ [("moveTo", ((1, 1),)), ("lineTo", ((0, 0),)), ("endPath", ())],
+ ),
+ (
+ [("moveTo", ((0, 0),)), ("curveTo", ((1, 1), (2, 2), (3, 3))), ("endPath", ())],
+ False,
+ [("moveTo", ((3, 3),)), ("curveTo", ((2, 2), (1, 1), (0, 0))), ("endPath", ())],
),
(
[
- ('qCurveTo', ((0, 0), (1, 1), (2, 2), None)),
- ('closePath', ())
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 1), (2, 2), (3, 3))),
+ ("lineTo", ((4, 4),)),
+ ("endPath", ()),
],
+ False,
[
- ('qCurveTo', ((0, 0), (2, 2), (1, 1), None)),
- ('closePath', ())
- ]
+ ("moveTo", ((4, 4),)),
+ ("lineTo", ((3, 3),)),
+ ("curveTo", ((2, 2), (1, 1), (0, 0))),
+ ("endPath", ()),
+ ],
),
(
[
- ('qCurveTo', ((0, 0), (1, 1), (2, 2), None)),
- ('endPath', ())
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((1, 1),)),
+ ("curveTo", ((2, 2), (3, 3), (4, 4))),
+ ("endPath", ()),
],
+ False,
[
- ('qCurveTo', ((0, 0), (2, 2), (1, 1), None)),
- ('closePath', ()) # this is always "closed"
- ]
+ ("moveTo", ((4, 4),)),
+ ("curveTo", ((3, 3), (2, 2), (1, 1))),
+ ("lineTo", ((0, 0),)),
+ ("endPath", ()),
+ ],
+ ),
+ (
+ [("qCurveTo", ((0, 0), (1, 1), (2, 2), None)), ("closePath", ())],
+ False,
+ [("qCurveTo", ((0, 0), (2, 2), (1, 1), None)), ("closePath", ())],
+ ),
+ (
+ [("qCurveTo", ((0, 0), (1, 1), (2, 2), None)), ("endPath", ())],
+ False,
+ [
+ ("qCurveTo", ((0, 0), (2, 2), (1, 1), None)),
+ ("closePath", ()), # this is always "closed"
+ ],
),
# Test case from:
# https://github.com/googlei18n/cu2qu/issues/51#issue-179370514
(
[
- ('moveTo', ((848, 348),)),
- ('lineTo', ((848, 348),)), # duplicate lineTo point after moveTo
- ('qCurveTo', ((848, 526), (649, 704), (449, 704))),
- ('qCurveTo', ((449, 704), (248, 704), (50, 526), (50, 348))),
- ('lineTo', ((50, 348),)),
- ('qCurveTo', ((50, 348), (50, 171), (248, -3), (449, -3))),
- ('qCurveTo', ((449, -3), (649, -3), (848, 171), (848, 348))),
- ('closePath', ())
- ],
- [
- ('moveTo', ((848, 348),)),
- ('qCurveTo', ((848, 171), (649, -3), (449, -3), (449, -3))),
- ('qCurveTo', ((248, -3), (50, 171), (50, 348), (50, 348))),
- ('lineTo', ((50, 348),)),
- ('qCurveTo', ((50, 526), (248, 704), (449, 704), (449, 704))),
- ('qCurveTo', ((649, 704), (848, 526), (848, 348))),
- ('lineTo', ((848, 348),)), # the duplicate point is kept
- ('closePath', ())
- ]
+ ("moveTo", ((848, 348),)),
+ ("lineTo", ((848, 348),)), # duplicate lineTo point after moveTo
+ ("qCurveTo", ((848, 526), (649, 704), (449, 704))),
+ ("qCurveTo", ((449, 704), (248, 704), (50, 526), (50, 348))),
+ ("lineTo", ((50, 348),)),
+ ("qCurveTo", ((50, 348), (50, 171), (248, -3), (449, -3))),
+ ("qCurveTo", ((449, -3), (649, -3), (848, 171), (848, 348))),
+ ("closePath", ()),
+ ],
+ False,
+ [
+ ("moveTo", ((848, 348),)),
+ ("qCurveTo", ((848, 171), (649, -3), (449, -3), (449, -3))),
+ ("qCurveTo", ((248, -3), (50, 171), (50, 348), (50, 348))),
+ ("lineTo", ((50, 348),)),
+ ("qCurveTo", ((50, 526), (248, 704), (449, 704), (449, 704))),
+ ("qCurveTo", ((649, 704), (848, 526), (848, 348))),
+ ("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, 101),)),
+ ("lineTo", ((0, 101),)),
+ ("lineTo", ((0, 651),)),
+ ("lineTo", ((0, 651),)),
+ ("closePath", ()),
],
+ False,
[
- ('moveTo', ((0, 651),)),
- ('lineTo', ((0, 651),)),
- ('lineTo', ((0, 101),)),
- ('lineTo', ((0, 101),)),
- ('closePath', ())
- ]
- )
+ ("moveTo", ((0, 651),)),
+ ("lineTo", ((0, 651),)),
+ ("lineTo", ((0, 101),)),
+ ("lineTo", ((0, 101),)),
+ ("closePath", ()),
+ ],
+ ),
+ (
+ [
+ ("moveTo", ((0, 651),)),
+ ("lineTo", ((0, 101),)),
+ ("lineTo", ((0, 101),)),
+ ("lineTo", ((0, 651),)),
+ ("lineTo", ((0, 651),)),
+ ("closePath", ()),
+ ],
+ True,
+ [
+ ("moveTo", ((0, 651),)),
+ ("lineTo", ((0, 651),)),
+ ("lineTo", ((0, 101),)),
+ ("lineTo", ((0, 101),)),
+ ("lineTo", ((0, 651),)), # closing line not implied
+ ("closePath", ()),
+ ],
+ ),
]
-@pytest.mark.parametrize("contour, expected", TEST_DATA)
-def test_reverse_pen(contour, expected):
+@pytest.mark.parametrize("contour, outputImpliedClosingLine, expected", TEST_DATA)
+def test_reverse_pen(contour, outputImpliedClosingLine, expected):
recpen = RecordingPen()
- revpen = ReverseContourPen(recpen)
+ revpen = ReverseContourPen(recpen, outputImpliedClosingLine)
for operator, operands in contour:
getattr(revpen, operator)(*operands)
assert recpen.value == expected
-@pytest.mark.parametrize("contour, expected", TEST_DATA)
-def test_reverse_point_pen(contour, expected):
- from fontTools.ufoLib.pointPen import (
- ReverseContourPointPen, PointToSegmentPen, SegmentToPointPen)
+def test_reverse_pen_outputImpliedClosingLine():
+ recpen = RecordingPen()
+ revpen = ReverseContourPen(recpen)
+ revpen.moveTo((0, 0))
+ revpen.lineTo((10, 0))
+ revpen.lineTo((0, 10))
+ revpen.lineTo((0, 0))
+ revpen.closePath()
+ assert recpen.value == [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((0, 10),)),
+ ("lineTo", ((10, 0),)),
+ # ("lineTo", ((0, 0),)), # implied
+ ("closePath", ()),
+ ]
+
+ recpen = RecordingPen()
+ revpen = ReverseContourPen(recpen, outputImpliedClosingLine=True)
+ revpen.moveTo((0, 0))
+ revpen.lineTo((10, 0))
+ revpen.lineTo((0, 10))
+ revpen.lineTo((0, 0))
+ revpen.closePath()
+ assert recpen.value == [
+ ("moveTo", ((0, 0),)),
+ ("lineTo", ((0, 10),)),
+ ("lineTo", ((10, 0),)),
+ ("lineTo", ((0, 0),)), # not implied
+ ("closePath", ()),
+ ]
+
+
+@pytest.mark.parametrize("contour, outputImpliedClosingLine, expected", TEST_DATA)
+def test_reverse_point_pen(contour, outputImpliedClosingLine, expected):
+ from fontTools.pens.pointPen import (
+ ReverseContourPointPen,
+ PointToSegmentPen,
+ SegmentToPointPen,
+ )
recpen = RecordingPen()
- pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine=True)
+ pt2seg = PointToSegmentPen(recpen, outputImpliedClosingLine)
revpen = ReverseContourPointPen(pt2seg)
seg2pt = SegmentToPointPen(revpen)
for operator, operands in contour:
getattr(seg2pt, operator)(*operands)
- # for closed contours that have a lineTo following the moveTo,
- # and whose points don't overlap, our current implementation diverges
- # from the ReverseContourPointPen as wrapped by ufoLib's pen converters.
- # In the latter case, an extra lineTo is added because of
- # outputImpliedClosingLine=True. This is redundant but not incorrect,
- # as the number of points is the same in both.
- if (contour and contour[-1][0] == "closePath" and
- contour[1][0] == "lineTo" and contour[1][1] != contour[0][1]):
- expected = expected[:-1] + [("lineTo", contour[0][1])] + expected[-1:]
-
assert recpen.value == expected
diff --git a/Tests/pens/t2CharStringPen_test.py b/Tests/pens/t2CharStringPen_test.py
index b710df55..74db2411 100644
--- a/Tests/pens/t2CharStringPen_test.py
+++ b/Tests/pens/t2CharStringPen_test.py
@@ -3,7 +3,6 @@ import unittest
class T2CharStringPenTest(unittest.TestCase):
-
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
@@ -31,13 +30,24 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertEqual(
- [100,
- 0, 'hmoveto',
- 10, 10, -10, 'hlineto',
- 10, 'hmoveto',
- 10, -10, -10, 'vlineto',
- 'endchar'],
- charstring.program)
+ [
+ 100,
+ 0,
+ "hmoveto",
+ 10,
+ 10,
+ -10,
+ "hlineto",
+ 10,
+ "hmoveto",
+ 10,
+ -10,
+ -10,
+ "vlineto",
+ "endchar",
+ ],
+ charstring.program,
+ )
def test_draw_lines(self):
pen = T2CharStringPen(100, {})
@@ -49,11 +59,9 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertEqual(
- [100,
- 5, 5, 'rmoveto',
- 20, 10, 10, 20, -20, -10, 'rlineto',
- 'endchar'],
- charstring.program)
+ [100, 5, 5, "rmoveto", 20, 10, 10, 20, -20, -10, "rlineto", "endchar"],
+ charstring.program,
+ )
def test_draw_h_v_curves(self):
pen = T2CharStringPen(100, {})
@@ -64,11 +72,23 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertEqual(
- [100,
- 0, 'hmoveto',
- 10, 10, 10, 10, 10, -10, 10, -10, 'hvcurveto',
- 'endchar'],
- charstring.program)
+ [
+ 100,
+ 0,
+ "hmoveto",
+ 10,
+ 10,
+ 10,
+ 10,
+ 10,
+ -10,
+ 10,
+ -10,
+ "hvcurveto",
+ "endchar",
+ ],
+ charstring.program,
+ )
def test_draw_curves(self):
pen = T2CharStringPen(100, {})
@@ -79,11 +99,28 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertEqual(
- [100,
- 95, 25, 'rmoveto',
- 20, 19, 0, 32, -20, 19, -19, 19, -32, 1, -19, -20, 'rrcurveto',
- 'endchar'],
- charstring.program)
+ [
+ 100,
+ 95,
+ 25,
+ "rmoveto",
+ 20,
+ 19,
+ 0,
+ 32,
+ -20,
+ 19,
+ -19,
+ 19,
+ -32,
+ 1,
+ -19,
+ -20,
+ "rrcurveto",
+ "endchar",
+ ],
+ charstring.program,
+ )
def test_draw_more_curves(self):
pen = T2CharStringPen(100, {})
@@ -99,22 +136,61 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertEqual(
- [100,
- 10, 10, 'rmoveto',
- 10, 30, 0, 10, 'hhcurveto',
- 10, 0, 30, 10, 'vvcurveto',
- -10, -10, -10, 10, -10, 'hhcurveto',
- 10, -10, -10, -10, -10, 'vvcurveto',
- -5, -5, -6, -5, 1, 'vhcurveto',
- -5, -6, 5, 5, 1, 'hvcurveto',
- -3, -5, -1, -10, 4, -5, 'rrcurveto',
- 'endchar'],
- charstring.program)
+ [
+ 100,
+ 10,
+ 10,
+ "rmoveto",
+ 10,
+ 30,
+ 0,
+ 10,
+ "hhcurveto",
+ 10,
+ 0,
+ 30,
+ 10,
+ "vvcurveto",
+ -10,
+ -10,
+ -10,
+ 10,
+ -10,
+ "hhcurveto",
+ 10,
+ -10,
+ -10,
+ -10,
+ -10,
+ "vvcurveto",
+ -5,
+ -5,
+ -6,
+ -5,
+ 1,
+ "vhcurveto",
+ -5,
+ -6,
+ 5,
+ 5,
+ 1,
+ "hvcurveto",
+ -3,
+ -5,
+ -1,
+ -10,
+ 4,
+ -5,
+ "rrcurveto",
+ "endchar",
+ ],
+ charstring.program,
+ )
def test_default_width(self):
pen = T2CharStringPen(None, {})
charstring = pen.getCharString(None, None)
- self.assertEqual(['endchar'], charstring.program)
+ self.assertEqual(["endchar"], charstring.program)
def test_no_round(self):
pen = T2CharStringPen(100.1, {}, roundTolerance=0.0)
@@ -125,12 +201,27 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertAlmostEqualProgram(
- [100, # we always round the advance width
- 0, 'hmoveto',
- 10.1, 0.1, 9.8, 9.8, 0.59, 10.59, 'rrcurveto',
- 10, -10.59, 9.41, -9.8, 0.2, 'vhcurveto',
- 'endchar'],
- charstring.program)
+ [
+ 100, # we always round the advance width
+ 0,
+ "hmoveto",
+ 10.1,
+ 0.1,
+ 9.8,
+ 9.8,
+ 0.59,
+ 10.59,
+ "rrcurveto",
+ 10,
+ -10.59,
+ 9.41,
+ -9.8,
+ 0.2,
+ "vhcurveto",
+ "endchar",
+ ],
+ charstring.program,
+ )
def test_round_all(self):
pen = T2CharStringPen(100.1, {}, roundTolerance=0.5)
@@ -141,11 +232,23 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertEqual(
- [100,
- 0, 'hmoveto',
- 10, 10, 10, 10, 11, -10, 9, -10, 'hvcurveto',
- 'endchar'],
- charstring.program)
+ [
+ 100,
+ 0,
+ "hmoveto",
+ 10,
+ 10,
+ 10,
+ 10,
+ 11,
+ -10,
+ 9,
+ -10,
+ "hvcurveto",
+ "endchar",
+ ],
+ charstring.program,
+ )
def test_round_some(self):
pen = T2CharStringPen(100, {}, roundTolerance=0.2)
@@ -159,20 +262,34 @@ class T2CharStringPenTest(unittest.TestCase):
charstring = pen.getCharString(None, None)
self.assertAlmostEqualProgram(
- [100,
- 0, 'hmoveto',
- 10, 'hlineto',
- 10, 10, 0.49, 10.49, 'rlineto',
- 'endchar'],
- charstring.program)
+ [
+ 100,
+ 0,
+ "hmoveto",
+ 10,
+ "hlineto",
+ 10,
+ 10,
+ 0.49,
+ 10.49,
+ "rlineto",
+ "endchar",
+ ],
+ charstring.program,
+ )
def test_invalid_tolerance(self):
self.assertRaisesRegex(
ValueError,
"Rounding tolerance must be positive",
- T2CharStringPen, None, {}, roundTolerance=-0.1)
+ T2CharStringPen,
+ None,
+ {},
+ roundTolerance=-0.1,
+ )
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/pens/ttGlyphPen_test.py b/Tests/pens/ttGlyphPen_test.py
index 96d75a19..6a74cb25 100644
--- a/Tests/pens/ttGlyphPen_test.py
+++ b/Tests/pens/ttGlyphPen_test.py
@@ -4,6 +4,7 @@ import struct
from fontTools import ttLib
from fontTools.pens.basePen import PenError
+from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
from fontTools.pens.ttGlyphPen import TTGlyphPen, TTGlyphPointPen, MAX_F2DOT14
@@ -21,12 +22,11 @@ class TTGlyphPenTestBase:
glyphSet = font.getGlyphSet()
glyfTable = font["glyf"]
- pen = self.penClass(font.getGlyphSet())
+ pen = self.penClass(glyphSet)
for name in font.getGlyphOrder():
- oldGlyph = glyphSet[name]
- getattr(oldGlyph, self.drawMethod)(pen)
- oldGlyph = oldGlyph._glyph
+ getattr(glyphSet[name], self.drawMethod)(pen)
+ oldGlyph = glyfTable[name]
newGlyph = pen.glyph()
if hasattr(oldGlyph, "program"):
@@ -295,6 +295,27 @@ class TTGlyphPenTest(TTGlyphPenTestBase):
uni0302_uni0300.recalcBounds(glyphSet)
self.assertGlyphBoundsEqual(uni0302_uni0300, (-278, 745, 148, 1025))
+ def test_outputImpliedClosingLine(self):
+ glyphSet = {}
+
+ pen = TTGlyphPen(glyphSet)
+ pen.moveTo((0, 0))
+ pen.lineTo((10, 0))
+ pen.lineTo((0, 10))
+ pen.lineTo((0, 0))
+ pen.closePath()
+ glyph = pen.glyph()
+ assert len(glyph.coordinates) == 3
+
+ pen = TTGlyphPen(glyphSet, outputImpliedClosingLine=True)
+ pen.moveTo((0, 0))
+ pen.lineTo((10, 0))
+ pen.lineTo((0, 10))
+ pen.lineTo((0, 0))
+ pen.closePath()
+ glyph = pen.glyph()
+ assert len(glyph.coordinates) == 4
+
class TTGlyphPointPenTest(TTGlyphPenTestBase):
penClass = TTGlyphPointPen
@@ -312,11 +333,11 @@ class TTGlyphPointPenTest(TTGlyphPenTestBase):
assert glyph.numberOfContours == 1
assert glyph.endPtsOfContours == [3]
- def test_addPoint_errorOnCurve(self):
+ def test_addPoint_noErrorOnCurve(self):
pen = TTGlyphPointPen(None)
pen.beginPath()
- with pytest.raises(NotImplementedError):
- pen.addPoint((0, 0), "curve")
+ pen.addPoint((0, 0), "curve")
+ pen.endPath()
def test_beginPath_beginPathOnOpenPath(self):
pen = TTGlyphPointPen(None)
@@ -332,12 +353,6 @@ class TTGlyphPointPenTest(TTGlyphPenTestBase):
with pytest.raises(PenError):
pen.glyph()
- def test_glyph_errorOnEmptyContour(self):
- pen = TTGlyphPointPen(None)
- pen.beginPath()
- with pytest.raises(PenError):
- pen.endPath()
-
def test_glyph_decomposes(self):
componentName = "a"
glyphSet = {}
@@ -574,6 +589,221 @@ class TTGlyphPointPenTest(TTGlyphPenTestBase):
assert pen1.points == pen2.points == [(0, 0), (10, 10), (20, 20), (20, 0)]
assert pen1.types == pen2.types == [1, 1, 0, 1]
+ def test_skip_empty_contours(self):
+ pen = TTGlyphPointPen(None)
+ pen.beginPath()
+ pen.endPath()
+ pen.beginPath()
+ pen.endPath()
+ glyph = pen.glyph()
+ assert glyph.numberOfContours == 0
+
+
+class CubicGlyfTest:
+ def test_cubic_simple(self):
+ spen = TTGlyphPen(None)
+ spen.moveTo((0, 0))
+ spen.curveTo((0, 1), (1, 1), (1, 0))
+ spen.closePath()
+
+ ppen = TTGlyphPointPen(None)
+ ppen.beginPath()
+ ppen.addPoint((0, 0), "line")
+ ppen.addPoint((0, 1))
+ ppen.addPoint((1, 1))
+ ppen.addPoint((1, 0), "curve")
+ ppen.endPath()
+
+ for pen in (spen, ppen):
+ glyph = pen.glyph()
+
+ for i in range(2):
+ if i == 1:
+ glyph.compile(None)
+
+ assert list(glyph.coordinates) == [(0, 0), (0, 1), (1, 1), (1, 0)]
+ assert list(glyph.flags) == [0x01, 0x80, 0x80, 0x01]
+
+ rpen = RecordingPen()
+ glyph.draw(rpen, None)
+ assert rpen.value == [
+ ("moveTo", ((0, 0),)),
+ (
+ "curveTo",
+ (
+ (0, 1),
+ (1, 1),
+ (1, 0),
+ ),
+ ),
+ ("closePath", ()),
+ ]
+
+ @pytest.mark.parametrize(
+ "dropImpliedOnCurves, segment_pen_commands, point_pen_commands, expected_coordinates, expected_flags, expected_endPts",
+ [
+ ( # Two curves that do NOT merge; request merging
+ True,
+ [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 1), (1, 2), (2, 2))),
+ ("curveTo", ((3, 3), (4, 1), (4, 0))),
+ ("closePath", ()),
+ ],
+ [
+ ("beginPath", (), {}),
+ ("addPoint", ((0, 0), "line", None, None), {}),
+ ("addPoint", ((0, 1), None, None, None), {}),
+ ("addPoint", ((1, 2), None, None, None), {}),
+ ("addPoint", ((2, 2), "curve", None, None), {}),
+ ("addPoint", ((3, 3), None, None, None), {}),
+ ("addPoint", ((4, 1), None, None, None), {}),
+ ("addPoint", ((4, 0), "curve", None, None), {}),
+ ("endPath", (), {}),
+ ],
+ [(0, 0), (0, 1), (1, 2), (2, 2), (3, 3), (4, 1), (4, 0)],
+ [0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x01],
+ [6],
+ ),
+ ( # Two curves that merge; request merging
+ True,
+ [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 1), (1, 2), (2, 2))),
+ ("curveTo", ((3, 2), (4, 1), (4, 0))),
+ ("closePath", ()),
+ ],
+ [
+ ("beginPath", (), {}),
+ ("addPoint", ((0, 0), "line", None, None), {}),
+ ("addPoint", ((0, 1), None, None, None), {}),
+ ("addPoint", ((1, 2), None, None, None), {}),
+ ("addPoint", ((2, 2), "curve", None, None), {}),
+ ("addPoint", ((3, 2), None, None, None), {}),
+ ("addPoint", ((4, 1), None, None, None), {}),
+ ("addPoint", ((4, 0), "curve", None, None), {}),
+ ("endPath", (), {}),
+ ],
+ [(0, 0), (0, 1), (1, 2), (3, 2), (4, 1), (4, 0)],
+ [0x01, 0x80, 0x80, 0x80, 0x80, 0x01],
+ [5],
+ ),
+ ( # Two curves that merge; request NOT merging
+ False,
+ [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 1), (1, 2), (2, 2))),
+ ("curveTo", ((3, 2), (4, 1), (4, 0))),
+ ("closePath", ()),
+ ],
+ [
+ ("beginPath", (), {}),
+ ("addPoint", ((0, 0), "line", None, None), {}),
+ ("addPoint", ((0, 1), None, None, None), {}),
+ ("addPoint", ((1, 2), None, None, None), {}),
+ ("addPoint", ((2, 2), "curve", None, None), {}),
+ ("addPoint", ((3, 2), None, None, None), {}),
+ ("addPoint", ((4, 1), None, None, None), {}),
+ ("addPoint", ((4, 0), "curve", None, None), {}),
+ ("endPath", (), {}),
+ ],
+ [(0, 0), (0, 1), (1, 2), (2, 2), (3, 2), (4, 1), (4, 0)],
+ [0x01, 0x80, 0x80, 0x01, 0x80, 0x80, 0x01],
+ [6],
+ ),
+ ( # Two (duplicate) contours
+ True,
+ [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 1), (1, 2), (2, 2))),
+ ("curveTo", ((3, 2), (4, 1), (4, 0))),
+ ("closePath", ()),
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 1), (1, 2), (2, 2))),
+ ("curveTo", ((3, 2), (4, 1), (4, 0))),
+ ("closePath", ()),
+ ],
+ [
+ ("beginPath", (), {}),
+ ("addPoint", ((0, 0), "line", None, None), {}),
+ ("addPoint", ((0, 1), None, None, None), {}),
+ ("addPoint", ((1, 2), None, None, None), {}),
+ ("addPoint", ((2, 2), "curve", None, None), {}),
+ ("addPoint", ((3, 2), None, None, None), {}),
+ ("addPoint", ((4, 1), None, None, None), {}),
+ ("addPoint", ((4, 0), "curve", None, None), {}),
+ ("endPath", (), {}),
+ ("beginPath", (), {}),
+ ("addPoint", ((0, 0), "line", None, None), {}),
+ ("addPoint", ((0, 1), None, None, None), {}),
+ ("addPoint", ((1, 2), None, None, None), {}),
+ ("addPoint", ((2, 2), "curve", None, None), {}),
+ ("addPoint", ((3, 2), None, None, None), {}),
+ ("addPoint", ((4, 1), None, None, None), {}),
+ ("addPoint", ((4, 0), "curve", None, None), {}),
+ ("endPath", (), {}),
+ ],
+ [
+ (0, 0),
+ (0, 1),
+ (1, 2),
+ (3, 2),
+ (4, 1),
+ (4, 0),
+ (0, 0),
+ (0, 1),
+ (1, 2),
+ (3, 2),
+ (4, 1),
+ (4, 0),
+ ],
+ [
+ 0x01,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x01,
+ 0x01,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x01,
+ ],
+ [5, 11],
+ ),
+ ],
+ )
+ def test_cubic_topology(
+ self,
+ dropImpliedOnCurves,
+ segment_pen_commands,
+ point_pen_commands,
+ expected_coordinates,
+ expected_flags,
+ expected_endPts,
+ ):
+ spen = TTGlyphPen(None)
+ rpen = RecordingPen()
+ rpen.value = segment_pen_commands
+ rpen.replay(spen)
+
+ ppen = TTGlyphPointPen(None)
+ rpen = RecordingPointPen()
+ rpen.value = point_pen_commands
+ rpen.replay(ppen)
+
+ for pen in (spen, ppen):
+ glyph = pen.glyph(dropImpliedOnCurves=dropImpliedOnCurves)
+
+ assert list(glyph.coordinates) == expected_coordinates
+ assert list(glyph.flags) == expected_flags
+ assert list(glyph.endPtsOfContours) == expected_endPts
+
+ rpen = RecordingPen()
+ glyph.draw(rpen, None)
+ assert rpen.value == segment_pen_commands
class _TestGlyph(object):
diff --git a/Tests/pens/utils.py b/Tests/pens/utils.py
index dced3c1b..c52fddd4 100644
--- a/Tests/pens/utils.py
+++ b/Tests/pens/utils.py
@@ -12,12 +12,18 @@
# 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 fontTools.ufoLib.glifLib import GlyphSet
from math import isclose
+import os
import unittest
+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"))
+
+
class BaseDummyPen(object):
"""Base class for pens that record the commands they are called with."""
@@ -29,45 +35,45 @@ class BaseDummyPen(object):
return _repr_pen_commands(self.commands)
def addComponent(self, glyphName, transformation, **kwargs):
- self.commands.append(('addComponent', (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,), {}))
+ self.commands.append(("moveTo", (pt,), {}))
def lineTo(self, pt):
- self.commands.append(('lineTo', (pt,), {}))
+ self.commands.append(("lineTo", (pt,), {}))
def curveTo(self, *points):
- self.commands.append(('curveTo', points, {}))
+ self.commands.append(("curveTo", points, {}))
def qCurveTo(self, *points):
- self.commands.append(('qCurveTo', points, {}))
+ self.commands.append(("qCurveTo", points, {}))
def closePath(self):
- self.commands.append(('closePath', tuple(), {}))
+ self.commands.append(("closePath", tuple(), {}))
def endPath(self):
- self.commands.append(('endPath', tuple(), {}))
+ 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))
+ self.commands.append(("beginPath", tuple(), kwargs))
def endPath(self):
- self.commands.append(('endPath', tuple(), {}))
+ 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))
+ kwargs["segmentType"] = str(segmentType) if segmentType else None
+ kwargs["smooth"] = smooth
+ kwargs["name"] = name
+ self.commands.append(("addPoint", (pt,), kwargs))
class DummyGlyph(object):
@@ -115,9 +121,9 @@ class DummyGlyph(object):
def __eq__(self, other):
"""Return True if 'other' glyph's outline is the same as self."""
- if hasattr(other, 'outline'):
+ if hasattr(other, "outline"):
return self.outline == other.outline
- elif hasattr(other, 'draw'):
+ elif hasattr(other, "draw"):
return self.outline == self.__class__(other).outline
return NotImplemented
@@ -126,9 +132,9 @@ class DummyGlyph(object):
return not (self == other)
def approx(self, other, rel_tol=1e-12):
- if hasattr(other, 'outline'):
+ if hasattr(other, "outline"):
outline2 == other.outline
- elif hasattr(other, 'draw'):
+ elif hasattr(other, "draw"):
outline2 = self.__class__(other).outline
else:
raise TypeError(type(other).__name__)
@@ -145,9 +151,8 @@ class DummyGlyph(object):
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)
+ if not isclose(x1, x2, rel_tol=rel_tol) or not isclose(
+ y1, y2, rel_tol=rel_tol
):
return False
elif arg1 != arg2:
@@ -227,13 +232,16 @@ def _repr_pen_commands(commands):
# 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)
+ (
+ tuple((int(v) if int(v) == v else round(v, 12)) for v in pt)
+ if pt is not None
+ else None
+ )
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()))
+ 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:
@@ -246,11 +254,10 @@ def _repr_pen_commands(commands):
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']
+ source = CUBIC_GLYPHS["a"]
copy = DummyGlyph(source)
copy2 = DummyGlyph(copy)
self.assertEqual(source, copy)
@@ -263,10 +270,9 @@ class TestDummyGlyph(unittest.TestCase):
class TestDummyPointGlyph(unittest.TestCase):
-
def test_equal(self):
# same as above but using the PointPen protocol
- source = CUBIC_GLYPHS['a']
+ source = CUBIC_GLYPHS["a"]
copy = DummyPointGlyph(source)
copy2 = DummyPointGlyph(copy)
self.assertEqual(source, copy)
diff --git a/Tests/qu2cu/data/NotoSansArabic-Regular.quadratic.subset.ttf b/Tests/qu2cu/data/NotoSansArabic-Regular.quadratic.subset.ttf
new file mode 100644
index 00000000..7a318161
--- /dev/null
+++ b/Tests/qu2cu/data/NotoSansArabic-Regular.quadratic.subset.ttf
Binary files differ
diff --git a/Tests/qu2cu/qu2cu_cli_test.py b/Tests/qu2cu/qu2cu_cli_test.py
new file mode 100644
index 00000000..55cd2718
--- /dev/null
+++ b/Tests/qu2cu/qu2cu_cli_test.py
@@ -0,0 +1,62 @@
+import os
+
+import pytest
+import py
+
+from fontTools.qu2cu.cli import main
+from fontTools.ttLib import TTFont
+
+
+DATADIR = os.path.join(os.path.dirname(__file__), "data")
+
+TEST_TTFS = [
+ py.path.local(DATADIR).join("NotoSansArabic-Regular.quadratic.subset.ttf"),
+]
+
+
+@pytest.fixture
+def test_paths(tmpdir):
+ result = []
+ for path in TEST_TTFS:
+ 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_no_output(self, test_paths):
+ ttf_path = test_paths[0]
+
+ self.run_main(ttf_path)
+
+ output_path = str(ttf_path).replace(".ttf", ".cubic.ttf")
+ font = TTFont(output_path)
+ assert font["head"].glyphDataFormat == 1
+ assert os.stat(ttf_path).st_size > os.stat(output_path).st_size
+
+ def test_output_file(self, test_paths):
+ ttf_path = test_paths[0]
+ output_path = str(ttf_path) + ".cubic"
+
+ self.run_main(ttf_path, "-o", output_path)
+
+ font = TTFont(output_path)
+ assert font["head"].glyphDataFormat == 1
+
+ def test_stats(self, test_paths):
+ ttf_path = test_paths[0]
+ self.run_main(ttf_path, "--verbose")
+
+ def test_all_cubic(self, test_paths):
+ ttf_path = test_paths[0]
+
+ self.run_main(ttf_path, "-c")
+
+ output_path = str(ttf_path).replace(".ttf", ".cubic.ttf")
+ font = TTFont(output_path)
+ assert font["head"].glyphDataFormat == 1
diff --git a/Tests/qu2cu/qu2cu_test.py b/Tests/qu2cu/qu2cu_test.py
new file mode 100644
index 00000000..3ca7ab55
--- /dev/null
+++ b/Tests/qu2cu/qu2cu_test.py
@@ -0,0 +1,104 @@
+# Copyright 2023 Behdad Esfahbod. 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
+import pytest
+
+from fontTools.qu2cu import quadratic_to_curves
+from fontTools.qu2cu.qu2cu import main as qu2cu_main
+from fontTools.qu2cu.benchmark import main as benchmark_main
+
+import os
+import json
+from fontTools.cu2qu import curve_to_quadratic
+
+
+class Qu2CuTest:
+ @pytest.mark.parametrize(
+ "quadratics, expected, tolerance, cubic_only",
+ [
+ (
+ [
+ [(0, 0), (0, 1), (2, 1), (2, 0)],
+ ],
+ [
+ ((0, 0), (0, 4 / 3), (2, 4 / 3), (2, 0)),
+ ],
+ 0.1,
+ True,
+ ),
+ (
+ [
+ [(0, 0), (0, 1), (2, 1), (2, 2)],
+ ],
+ [
+ ((0, 0), (0, 4 / 3), (2, 2 / 3), (2, 2)),
+ ],
+ 0.2,
+ True,
+ ),
+ (
+ [
+ [(0, 0), (0, 1), (1, 1)],
+ [(1, 1), (3, 1), (3, 0)],
+ ],
+ [
+ ((0, 0), (0, 1), (1, 1)),
+ ((1, 1), (3, 1), (3, 0)),
+ ],
+ 0.2,
+ False,
+ ),
+ (
+ [
+ [(0, 0), (0, 1), (1, 1)],
+ [(1, 1), (3, 1), (3, 0)],
+ ],
+ [
+ ((0, 0), (0, 2 / 3), (1 / 3, 1), (1, 1)),
+ ((1, 1), (7 / 3, 1), (3, 2 / 3), (3, 0)),
+ ],
+ 0.2,
+ True,
+ ),
+ ],
+ )
+ def test_simple(self, quadratics, expected, tolerance, cubic_only):
+ expected = [
+ tuple((pytest.approx(p[0]), pytest.approx(p[1])) for p in curve)
+ for curve in expected
+ ]
+
+ c = quadratic_to_curves(quadratics, tolerance, cubic_only)
+ assert c == expected
+
+ def test_roundtrip(self):
+ DATADIR = os.path.join(os.path.dirname(__file__), "..", "cu2qu", "data")
+ with open(os.path.join(DATADIR, "curves.json"), "r") as fp:
+ curves = json.load(fp)
+
+ tolerance = 1
+
+ splines = [curve_to_quadratic(c, tolerance) for c in curves]
+ reconsts = [quadratic_to_curves([spline], tolerance) for spline in splines]
+
+ for curve, reconst in zip(curves, reconsts):
+ assert len(reconst) == 1
+ curve = tuple((pytest.approx(p[0]), pytest.approx(p[1])) for p in curve)
+ assert curve == reconst[0]
+
+ def test_main(self):
+ # Just for coverage
+ qu2cu_main()
+ benchmark_main()
diff --git a/Tests/subset/data/NotoSansCJKjp-Regular.subset.ttx b/Tests/subset/data/NotoSansCJKjp-Regular.subset.ttx
new file mode 100644
index 00000000..4dfc0b23
--- /dev/null
+++ b/Tests/subset/data/NotoSansCJKjp-Regular.subset.ttx
@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.43">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="cid01404"/>
+ <GlyphID id="2" name="cid59004"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="2.004"/>
+ <checkSumAdjustment value="0x6e6d05f3"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Apr 29 16:22:51 2021"/>
+ <modified value="Thu Oct 19 10:07:59 2023"/>
+ <xMin value="34"/>
+ <yMin value="-86"/>
+ <xMax value="966"/>
+ <yMax value="846"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="1160"/>
+ <descent value="-288"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="34"/>
+ <minRightSideBearing value="34"/>
+ <xMaxExtent value="966"/>
+ <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="3"/>
+ <xAvgCharWidth value="979"/>
+ <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="325"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="11"/>
+ <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 00000000 00000000"/>
+ <ulUnicodeRange2 value="00000000 00000001 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="GOOG"/>
+ <fsSelection value="00000000 01000000"/>
+ <usFirstCharIndex value="9001"/>
+ <usLastCharIndex value="12296"/>
+ <sTypoAscender value="880"/>
+ <sTypoDescender value="-120"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="1160"/>
+ <usWinDescent value="288"/>
+ <ulCodePageRange1 value="01100000 00101110 00000001 00000111"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="543"/>
+ <sCapHeight value="733"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="6"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+ © 2014-2021 Adobe (http://www.adobe.com/).
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Noto Sans CJK JP
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 2.004;GOOG;NotoSansCJKjp-Regular;ADOBE
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Noto Sans CJK JP
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 2.004;hotconv 1.0.118;makeotfexe 2.5.65603
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ NotoSansCJKjp-Regular
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x2329" name="cid01404"/><!-- LEFT-POINTING ANGLE BRACKET -->
+ <map code="0x3008" name="cid01404"/><!-- LEFT ANGLE BRACKET -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x2329" name="cid01404"/><!-- LEFT-POINTING ANGLE BRACKET -->
+ <map code="0x3008" name="cid01404"/><!-- LEFT ANGLE BRACKET -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-125"/>
+ <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="NotoSansCJKjp-Regular">
+ <ROS Registry="Adobe" Order="Identity" Supplement="0"/>
+ <Notice value="Copyright 2014-2021 Adobe (http://www.adobe.com/). Noto is a trademark of Google Inc."/>
+ <FullName value="Noto Sans CJK JP Regular"/>
+ <FamilyName value="Noto Sans CJK JP"/>
+ <Weight value="Regular"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-150"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="34 -86 966 846"/>
+ <StrokeWidth value="0"/>
+ <CIDFontVersion value="2.0039999"/>
+ <CIDFontRevision value="0"/>
+ <CIDFontType value="0"/>
+ <CIDCount value="65535"/>
+ <!-- charset is dumped separately as the 'GlyphOrder' element -->
+ <FDSelect format="0"/>
+ <FDArray>
+ <FontDict index="0">
+ <FontName value="NotoSansCJKjp-Regular-Dingbats"/>
+ <Private>
+ <BlueValues value="-1100 -1100 1900 1900"/>
+ <BlueScale value="0.039625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="1"/>
+ <StdHW value="66"/>
+ <StdVW value="69"/>
+ <StemSnapH value="32 40 66"/>
+ <StemSnapV value="32 43 69"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="1"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="1000"/>
+ <nominalWidthX value="107"/>
+ </Private>
+ </FontDict>
+ <FontDict index="1">
+ <FontName value="NotoSansCJKjp-Regular-Generic"/>
+ <Private>
+ <BlueValues value="-250 -250 1100 1100"/>
+ <BlueScale value="0.039625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="1"/>
+ <StdHW value="40"/>
+ <StdVW value="40"/>
+ <StemSnapH value="40 120"/>
+ <StemSnapV value="40 120"/>
+ <ForceBold value="0"/>
+ <LanguageGroup value="1"/>
+ <ExpansionFactor value="0.06"/>
+ <initialRandomSeed value="0"/>
+ <defaultWidthX value="1000"/>
+ <nominalWidthX value="107"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef" fdSelectIndex="1">
+ endchar
+ </CharString>
+ <CharString name="cid01404" fdSelectIndex="0">
+ -86 932 hstem
+ 588 360 vstem
+ 948 -57 rmoveto
+ -280 437 280 437 -63 29 -297 -466 297 -466 rlineto
+ endchar
+ </CharString>
+ <CharString name="cid59004" fdSelectIndex="0">
+ -68 360 hstem
+ 34 932 vstem
+ 63 -68 rmoveto
+ 437 280 437 -280 29 63 -466 297 -466 -297 rlineto
+ 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="DFLT"/>
+ <Script>
+ <DefaultLangSys>
+ <ReqFeatureIndex value="65535"/>
+ <!-- FeatureCount=3 -->
+ <FeatureIndex index="0" value="0"/>
+ <FeatureIndex index="1" value="1"/>
+ <FeatureIndex index="2" value="2"/>
+ </DefaultLangSys>
+ <!-- LangSysCount=0 -->
+ </Script>
+ </ScriptRecord>
+ </ScriptList>
+ <FeatureList>
+ <!-- FeatureCount=3 -->
+ <FeatureRecord index="0">
+ <FeatureTag value="halt"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="vhal"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="1"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="2">
+ <FeatureTag value="vpal"/>
+ <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="cid01404"/>
+ </Coverage>
+ <ValueFormat value="5"/>
+ <Value XPlacement="-500" XAdvance="-500"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="cid59004"/>
+ </Coverage>
+ <ValueFormat value="10"/>
+ <Value YPlacement="500" YAdvance="-500"/>
+ </SinglePos>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="1"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <SinglePos index="0" Format="1">
+ <Coverage>
+ <Glyph value="cid59004"/>
+ </Coverage>
+ <ValueFormat value="10"/>
+ <Value YPlacement="475" YAdvance="-500"/>
+ </SinglePos>
+ </Lookup>
+ </LookupList>
+ </GPOS>
+
+ <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="vert"/>
+ <Feature>
+ <!-- LookupCount=1 -->
+ <LookupListIndex index="0" value="0"/>
+ </Feature>
+ </FeatureRecord>
+ <FeatureRecord index="1">
+ <FeatureTag value="vrt2"/>
+ <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="cid01404" out="cid59004"/>
+ </SingleSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+
+ <VORG>
+ <majorVersion value="1"/>
+ <minorVersion value="0"/>
+ <defaultVertOriginY value="880"/>
+ <numVertOriginYMetrics value="0"/>
+ </VORG>
+
+ <hmtx>
+ <mtx name=".notdef" width="1000" lsb="100"/>
+ <mtx name="cid01404" width="1000" lsb="588"/>
+ <mtx name="cid59004" width="1000" lsb="34"/>
+ </hmtx>
+
+ <vhea>
+ <tableVersion value="0x00011000"/>
+ <ascent value="500"/>
+ <descent value="-500"/>
+ <lineGap value="0"/>
+ <advanceHeightMax value="1000"/>
+ <minTopSideBearing value="34"/>
+ <minBottomSideBearing value="34"/>
+ <yMaxExtent value="966"/>
+ <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="1000" tsb="0"/>
+ <mtx name="cid01404" height="1000" tsb="34"/>
+ <mtx name="cid59004" height="1000" tsb="588"/>
+ </vmtx>
+
+</ttFont>
diff --git a/Tests/subset/data/TestGVAR.ttx b/Tests/subset/data/TestGVAR.ttx
index b14466da..2d2ee1e7 100644
--- a/Tests/subset/data/TestGVAR.ttx
+++ b/Tests/subset/data/TestGVAR.ttx
@@ -378,6 +378,7 @@
</GSUB>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/subset/data/TestHVVAR.ttx b/Tests/subset/data/TestHVVAR.ttx
index 3e746527..5906988e 100644
--- a/Tests/subset/data/TestHVVAR.ttx
+++ b/Tests/subset/data/TestHVVAR.ttx
@@ -406,6 +406,7 @@
</VVAR>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/subset/data/expect_HVVAR.ttx b/Tests/subset/data/expect_HVVAR.ttx
index 5fbc1770..2bd86221 100644
--- a/Tests/subset/data/expect_HVVAR.ttx
+++ b/Tests/subset/data/expect_HVVAR.ttx
@@ -129,6 +129,7 @@
</VVAR>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/subset/data/expect_HVVAR_retain_gids.ttx b/Tests/subset/data/expect_HVVAR_retain_gids.ttx
index 8e51ca7f..e5476170 100644
--- a/Tests/subset/data/expect_HVVAR_retain_gids.ttx
+++ b/Tests/subset/data/expect_HVVAR_retain_gids.ttx
@@ -140,6 +140,7 @@
</VVAR>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/subset/data/expect_keep_gvar.ttx b/Tests/subset/data/expect_keep_gvar.ttx
index 43e4a34a..09c066be 100644
--- a/Tests/subset/data/expect_keep_gvar.ttx
+++ b/Tests/subset/data/expect_keep_gvar.ttx
@@ -9,6 +9,7 @@
</GlyphOrder>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx b/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx
index d4cc79f3..7811b1bb 100644
--- a/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx
+++ b/Tests/subset/data/expect_keep_gvar_notdef_outline.ttx
@@ -8,6 +8,7 @@
</GlyphOrder>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py
index 7efcb698..2b57633c 100644
--- a/Tests/subset/subset_test.py
+++ b/Tests/subset/subset_test.py
@@ -32,17 +32,16 @@ class SubsetTest:
shutil.rmtree(cls.tempdir, ignore_errors=True)
@staticmethod
- def getpath(testfile):
+ def getpath(*testfile):
path, _ = os.path.split(__file__)
- return os.path.join(path, "data", testfile)
+ return os.path.join(path, "data", *testfile)
@classmethod
def temp_path(cls, suffix):
if not cls.tempdir:
cls.tempdir = tempfile.mkdtemp()
cls.num_tempfiles += 1
- return os.path.join(cls.tempdir,
- "tmp%d%s" % (cls.num_tempfiles, suffix))
+ return os.path.join(cls.tempdir, "tmp%d%s" % (cls.num_tempfiles, suffix))
@staticmethod
def read_ttx(path):
@@ -58,7 +57,8 @@ class SubsetTest:
expected = self.read_ttx(expected_ttx)
if actual != expected:
for line in difflib.unified_diff(
- expected, actual, fromfile=expected_ttx, tofile=path):
+ expected, actual, fromfile=expected_ttx, tofile=path
+ ):
sys.stdout.write(line)
pytest.fail("TTX output is different from expected")
@@ -69,40 +69,76 @@ class SubsetTest:
font.save(savepath, reorderTables=None)
return savepath
-# -----
-# Tests
-# -----
+ # -----
+ # 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])
+ 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"])
+ 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")
- subset.main([fontpath, "--no-notdef-outline", "--gids=0", "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--no-notdef-outline",
+ "--gids=0",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_no_notdef_outline_otf.ttx"), ["CFF "])
+ self.expect_ttx(
+ subsetfont, self.getpath("expect_no_notdef_outline_otf.ttx"), ["CFF "]
+ )
def test_no_notdef_outline_cid(self):
fontpath = self.compile_font(self.getpath("TestCID-Regular.ttx"), ".otf")
subsetpath = self.temp_path(".otf")
- subset.main([fontpath, "--no-notdef-outline", "--gids=0", "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--no-notdef-outline",
+ "--gids=0",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_no_notdef_outline_cid.ttx"), ["CFF "])
+ self.expect_ttx(
+ subsetfont, self.getpath("expect_no_notdef_outline_cid.ttx"), ["CFF "]
+ )
def test_no_notdef_outline_ttf(self):
fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--no-notdef-outline", "--gids=0", "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--no-notdef-outline",
+ "--gids=0",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_no_notdef_outline_ttf.ttx"), ["glyf", "hmtx"])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_no_notdef_outline_ttf.ttx"),
+ ["glyf", "hmtx"],
+ )
def test_subset_ankr(self):
fontpath = self.compile_font(self.getpath("TestANKR.ttx"), ".ttf")
@@ -133,8 +169,9 @@ class SubsetTest:
# we expect a format 0 'bsln' table because it is the most compact.
fontpath = self.compile_font(self.getpath("TestBSLN-1.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0030-0031",
- "--output-file=%s" % subsetpath])
+ subset.main(
+ [fontpath, "--unicodes=U+0030-0031", "--output-file=%s" % subsetpath]
+ )
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_bsln_0.ttx"), ["bsln"])
@@ -149,8 +186,9 @@ class SubsetTest:
# for uni2EA2.
fontpath = self.compile_font(self.getpath("TestBSLN-1.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0030-0031,U+2EA2",
- "--output-file=%s" % subsetpath])
+ subset.main(
+ [fontpath, "--unicodes=U+0030-0031,U+2EA2", "--output-file=%s" % subsetpath]
+ )
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_bsln_1.ttx"), ["bsln"])
@@ -174,8 +212,7 @@ class SubsetTest:
# is the most compact encoding.
fontpath = self.compile_font(self.getpath("TestBSLN-3.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0030",
- "--output-file=%s" % subsetpath])
+ subset.main([fontpath, "--unicodes=U+0030", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_bsln_2.ttx"), ["bsln"])
@@ -190,8 +227,9 @@ class SubsetTest:
# for uni2EA2.
fontpath = self.compile_font(self.getpath("TestBSLN-3.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0030-0031,U+2EA2",
- "--output-file=%s" % subsetpath])
+ subset.main(
+ [fontpath, "--unicodes=U+0030-0031,U+2EA2", "--output-file=%s" % subsetpath]
+ )
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_bsln_3.ttx"), ["bsln"])
@@ -200,21 +238,42 @@ class SubsetTest:
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--glyphs=smileface", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_keep_colr.ttx"), ["GlyphOrder", "hmtx", "glyf", "COLR", "CPAL"])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_keep_colr.ttx"),
+ ["GlyphOrder", "hmtx", "glyf", "COLR", "CPAL"],
+ )
def test_subset_gvar(self):
fontpath = self.compile_font(self.getpath("TestGVAR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+002B,U+2212", "--output-file=%s" % subsetpath])
+ subset.main(
+ [fontpath, "--unicodes=U+002B,U+2212", "--output-file=%s" % subsetpath]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_keep_gvar.ttx"), ["GlyphOrder", "avar", "fvar", "gvar", "name"])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_keep_gvar.ttx"),
+ ["GlyphOrder", "avar", "fvar", "gvar", "name"],
+ )
def test_subset_gvar_notdef_outline(self):
fontpath = self.compile_font(self.getpath("TestGVAR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0030", "--notdef_outline", "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--unicodes=U+0030",
+ "--notdef_outline",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_keep_gvar_notdef_outline.ttx"), ["GlyphOrder", "avar", "fvar", "gvar", "name"])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_keep_gvar_notdef_outline.ttx"),
+ ["GlyphOrder", "avar", "fvar", "gvar", "name"],
+ )
def test_subset_lcar_remove(self):
fontpath = self.compile_font(self.getpath("TestLCAR-0.ttx"), ".ttf")
@@ -226,25 +285,33 @@ class SubsetTest:
def test_subset_lcar_format_0(self):
fontpath = self.compile_font(self.getpath("TestLCAR-0.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+FB01",
- "--output-file=%s" % subsetpath])
+ subset.main([fontpath, "--unicodes=U+FB01", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_lcar_0.ttx"), ["lcar"])
def test_subset_lcar_format_1(self):
fontpath = self.compile_font(self.getpath("TestLCAR-1.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+FB01",
- "--output-file=%s" % subsetpath])
+ subset.main([fontpath, "--unicodes=U+FB01", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_lcar_1.ttx"), ["lcar"])
def test_subset_math(self):
fontpath = self.compile_font(self.getpath("TestMATH-Regular.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0041,U+0028,U+0302,U+1D400,U+1D435", "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--unicodes=U+0041,U+0028,U+0302,U+1D400,U+1D435",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_keep_math.ttx"), ["GlyphOrder", "CFF ", "MATH", "hmtx"])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_keep_math.ttx"),
+ ["GlyphOrder", "CFF ", "MATH", "hmtx"],
+ )
def test_subset_math_partial(self):
fontpath = self.compile_font(self.getpath("test_math_partial.ttx"), ".ttf")
@@ -283,8 +350,7 @@ class SubsetTest:
# the "prop" table should be removed from the subsetted font.
fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0041",
- "--output-file=%s" % subsetpath])
+ subset.main([fontpath, "--unicodes=U+0041", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
assert "prop" not in subsetfont
@@ -297,8 +363,14 @@ class SubsetTest:
# tested above in test_subset_prop_remove_default_zero().
fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0030-0032", "--no-notdef-glyph",
- "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--unicodes=U+0030-0032",
+ "--no-notdef-glyph",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_prop_0.ttx"), ["prop"])
@@ -308,19 +380,25 @@ class SubsetTest:
# DefaultProperties should be set to the most frequent value.
fontpath = self.compile_font(self.getpath("TestPROP.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--unicodes=U+0030-0032", "--notdef-outline",
- "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--unicodes=U+0030-0032",
+ "--notdef-outline",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
self.expect_ttx(subsetfont, self.getpath("expect_prop_1.ttx"), ["prop"])
def test_options(self):
# https://github.com/fonttools/fonttools/issues/413
opt1 = subset.Options()
- assert 'Xyz-' not in opt1.layout_features
+ assert "Xyz-" not in opt1.layout_features
opt2 = subset.Options()
- opt2.layout_features.append('Xyz-')
- assert 'Xyz-' in opt2.layout_features
- assert 'Xyz-' not in opt1.layout_features
+ opt2.layout_features.append("Xyz-")
+ assert "Xyz-" in opt2.layout_features
+ assert "Xyz-" not in opt1.layout_features
def test_google_color(self):
fontpath = self.compile_font(self.getpath("google_color.ttx"), ".ttf")
@@ -329,24 +407,35 @@ class SubsetTest:
subsetfont = TTFont(subsetpath)
assert "CBDT" in subsetfont
assert "CBLC" in subsetfont
- assert "x" in subsetfont['CBDT'].strikeData[0]
- assert "y" not in subsetfont['CBDT'].strikeData[0]
+ assert "x" in subsetfont["CBDT"].strikeData[0]
+ assert "y" not in subsetfont["CBDT"].strikeData[0]
def test_google_color_all(self):
fontpath = self.compile_font(self.getpath("google_color.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--unicodes=*", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- assert "x" in subsetfont['CBDT'].strikeData[0]
- assert "y" in subsetfont['CBDT'].strikeData[0]
+ assert "x" in subsetfont["CBDT"].strikeData[0]
+ assert "y" in subsetfont["CBDT"].strikeData[0]
def test_sbix(self):
fontpath = self.compile_font(self.getpath("sbix.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--gids=0,1", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath(
- "expect_sbix.ttx"), ["sbix"])
+ self.expect_ttx(subsetfont, self.getpath("expect_sbix.ttx"), ["sbix"])
+
+ def test_varComposite(self):
+ fontpath = self.getpath("..", "..", "ttLib", "data", "varc-ac00-ac01.ttf")
+ origfont = TTFont(fontpath)
+ assert len(origfont.getGlyphOrder()) == 6
+ subsetpath = self.temp_path(".ttf")
+ subset.main([fontpath, "--unicodes=ac00", "--output-file=%s" % subsetpath])
+ subsetfont = TTFont(subsetpath)
+ assert len(subsetfont.getGlyphOrder()) == 4
+ subset.main([fontpath, "--unicodes=ac01", "--output-file=%s" % subsetpath])
+ subsetfont = TTFont(subsetpath)
+ assert len(subsetfont.getGlyphOrder()) == 5
def test_timing_publishes_parts(self):
fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
@@ -354,25 +443,27 @@ class SubsetTest:
options = subset.Options()
options.timing = True
subsetter = subset.Subsetter(options)
- subsetter.populate(text='ABC')
+ subsetter.populate(text="ABC")
font = TTFont(fontpath)
- with CapturingLogHandler('fontTools.subset.timer', logging.DEBUG) as captor:
+ with CapturingLogHandler("fontTools.subset.timer", logging.DEBUG) as captor:
subsetter.subset(font)
logs = captor.records
assert len(logs) > 5
- assert len(logs) == len([l for l in logs if 'msg' in l.args and 'time' in l.args])
+ assert len(logs) == len(
+ [l for l in logs if "msg" in l.args and "time" in l.args]
+ )
# Look for a few things we know should happen
- assert filter(lambda l: l.args['msg'] == "load 'cmap'", logs)
- assert filter(lambda l: l.args['msg'] == "subset 'cmap'", logs)
- assert filter(lambda l: l.args['msg'] == "subset 'glyf'", logs)
+ assert filter(lambda l: l.args["msg"] == "load 'cmap'", logs)
+ assert filter(lambda l: l.args["msg"] == "subset 'cmap'", logs)
+ assert filter(lambda l: l.args["msg"] == "subset 'glyf'", logs)
def test_passthrough_tables(self):
fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
font = TTFont(fontpath)
- unknown_tag = 'ZZZZ'
+ unknown_tag = "ZZZZ"
unknown_table = newTable(unknown_tag)
- unknown_table.data = b'\0'*10
+ unknown_table.data = b"\0" * 10
font[unknown_tag] = unknown_table
font.save(fontpath)
@@ -392,92 +483,144 @@ class SubsetTest:
def test_non_BMP_text_arg_input(self):
fontpath = self.compile_font(
- self.getpath("TestTTF-Regular_non_BMP_char.ttx"), ".ttf")
+ self.getpath("TestTTF-Regular_non_BMP_char.ttx"), ".ttf"
+ )
subsetpath = self.temp_path(".ttf")
- text = tostr(u"A\U0001F6D2", encoding='utf-8')
+ text = tostr("A\U0001F6D2", encoding="utf-8")
subset.main([fontpath, "--text=%s" % text, "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- assert subsetfont['maxp'].numGlyphs == 3
- assert subsetfont.getGlyphOrder() == ['.notdef', 'A', 'u1F6D2']
+ assert subsetfont["maxp"].numGlyphs == 3
+ assert subsetfont.getGlyphOrder() == [".notdef", "A", "u1F6D2"]
def test_non_BMP_text_file_input(self):
fontpath = self.compile_font(
- self.getpath("TestTTF-Regular_non_BMP_char.ttx"), ".ttf")
+ self.getpath("TestTTF-Regular_non_BMP_char.ttx"), ".ttf"
+ )
subsetpath = self.temp_path(".ttf")
- text = tobytes(u"A\U0001F6D2", encoding='utf-8')
+ text = tobytes("A\U0001F6D2", encoding="utf-8")
with tempfile.NamedTemporaryFile(delete=False) as tmp:
tmp.write(text)
try:
- subset.main([fontpath, "--text-file=%s" % tmp.name,
- "--output-file=%s" % subsetpath])
+ subset.main(
+ [fontpath, "--text-file=%s" % tmp.name, "--output-file=%s" % subsetpath]
+ )
subsetfont = TTFont(subsetpath)
finally:
os.remove(tmp.name)
- assert subsetfont['maxp'].numGlyphs == 3
- assert subsetfont.getGlyphOrder() == ['.notdef', 'A', 'u1F6D2']
+ assert subsetfont["maxp"].numGlyphs == 3
+ assert subsetfont.getGlyphOrder() == [".notdef", "A", "u1F6D2"]
def test_no_hinting_CFF(self):
ttxpath = self.getpath("Lobster.subset.ttx")
fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
- subset.main([fontpath, "--no-hinting", "--notdef-outline",
- "--output-file=%s" % subsetpath, "*"])
+ subset.main(
+ [
+ fontpath,
+ "--no-hinting",
+ "--notdef-outline",
+ "--output-file=%s" % subsetpath,
+ "*",
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath(
- "expect_no_hinting_CFF.ttx"), ["CFF "])
+ self.expect_ttx(subsetfont, self.getpath("expect_no_hinting_CFF.ttx"), ["CFF "])
def test_desubroutinize_CFF(self):
ttxpath = self.getpath("Lobster.subset.ttx")
fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
- subset.main([fontpath, "--desubroutinize", "--notdef-outline",
- "--output-file=%s" % subsetpath, "*"])
+ subset.main(
+ [
+ fontpath,
+ "--desubroutinize",
+ "--notdef-outline",
+ "--output-file=%s" % subsetpath,
+ "*",
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath(
- "expect_desubroutinize_CFF.ttx"), ["CFF "])
+ self.expect_ttx(
+ subsetfont, self.getpath("expect_desubroutinize_CFF.ttx"), ["CFF "]
+ )
def test_desubroutinize_hinted_subrs_CFF(self):
ttxpath = self.getpath("test_hinted_subrs_CFF.ttx")
fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
- subset.main([fontpath, "--desubroutinize", "--notdef-outline",
- "--output-file=%s" % subsetpath, "*"])
+ subset.main(
+ [
+ fontpath,
+ "--desubroutinize",
+ "--notdef-outline",
+ "--output-file=%s" % subsetpath,
+ "*",
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath(
- "test_hinted_subrs_CFF.desub.ttx"), ["CFF "])
+ self.expect_ttx(
+ subsetfont, self.getpath("test_hinted_subrs_CFF.desub.ttx"), ["CFF "]
+ )
def test_desubroutinize_cntrmask_CFF(self):
ttxpath = self.getpath("test_cntrmask_CFF.ttx")
fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
- subset.main([fontpath, "--desubroutinize", "--notdef-outline",
- "--output-file=%s" % subsetpath, "*"])
+ subset.main(
+ [
+ fontpath,
+ "--desubroutinize",
+ "--notdef-outline",
+ "--output-file=%s" % subsetpath,
+ "*",
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath(
- "test_cntrmask_CFF.desub.ttx"), ["CFF "])
+ self.expect_ttx(
+ subsetfont, self.getpath("test_cntrmask_CFF.desub.ttx"), ["CFF "]
+ )
def test_no_hinting_desubroutinize_CFF(self):
ttxpath = self.getpath("test_hinted_subrs_CFF.ttx")
fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
- subset.main([fontpath, "--no-hinting", "--desubroutinize", "--notdef-outline",
- "--output-file=%s" % subsetpath, "*"])
+ subset.main(
+ [
+ fontpath,
+ "--no-hinting",
+ "--desubroutinize",
+ "--notdef-outline",
+ "--output-file=%s" % subsetpath,
+ "*",
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath(
- "expect_no_hinting_desubroutinize_CFF.ttx"), ["CFF "])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_no_hinting_desubroutinize_CFF.ttx"),
+ ["CFF "],
+ )
def test_no_hinting_TTF(self):
fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--no-hinting", "--notdef-outline",
- "--output-file=%s" % subsetpath, "*"])
+ subset.main(
+ [
+ fontpath,
+ "--no-hinting",
+ "--notdef-outline",
+ "--output-file=%s" % subsetpath,
+ "*",
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath(
- "expect_no_hinting_TTF.ttx"), ["glyf", "maxp"])
+ self.expect_ttx(
+ subsetfont, self.getpath("expect_no_hinting_TTF.ttx"), ["glyf", "maxp"]
+ )
for tag in subset.Options().hinting_tables:
assert tag not in subsetfont
@@ -485,15 +628,24 @@ class SubsetTest:
# https://github.com/fonttools/fonttools/pull/845
fontpath = self.compile_font(self.getpath("NotdefWidthCID-Regular.ttx"), ".otf")
subsetpath = self.temp_path(".otf")
- subset.main([fontpath, "--no-notdef-outline", "--gids=0,1", "--output-file=%s" % subsetpath])
+ subset.main(
+ [
+ fontpath,
+ "--no-notdef-outline",
+ "--gids=0,1",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_notdef_width_cid.ttx"), ["CFF "])
+ self.expect_ttx(
+ subsetfont, self.getpath("expect_notdef_width_cid.ttx"), ["CFF "]
+ )
def test_recalc_bounds_ttf(self):
ttxpath = self.getpath("TestTTF-Regular.ttx")
font = TTFont()
font.importXML(ttxpath)
- head = font['head']
+ head = font["head"]
bounds = [head.xMin, head.yMin, head.xMax, head.yMax]
fontpath = self.compile_font(ttxpath, ".ttf")
@@ -501,11 +653,11 @@ class SubsetTest:
# by default, the subsetter does not recalculate the bounding box
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
- head = TTFont(subsetpath)['head']
+ head = TTFont(subsetpath)["head"]
assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
subset.main([fontpath, "--recalc-bounds", "--output-file=%s" % subsetpath, "*"])
- head = TTFont(subsetpath)['head']
+ head = TTFont(subsetpath)["head"]
bounds = [132, 304, 365, 567]
assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
@@ -513,7 +665,7 @@ class SubsetTest:
ttxpath = self.getpath("TestOTF-Regular.ttx")
font = TTFont()
font.importXML(ttxpath)
- head = font['head']
+ head = font["head"]
bounds = [head.xMin, head.yMin, head.xMax, head.yMax]
fontpath = self.compile_font(ttxpath, ".otf")
@@ -521,11 +673,11 @@ class SubsetTest:
# by default, the subsetter does not recalculate the bounding box
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
- head = TTFont(subsetpath)['head']
+ head = TTFont(subsetpath)["head"]
assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
subset.main([fontpath, "--recalc-bounds", "--output-file=%s" % subsetpath, "*"])
- head = TTFont(subsetpath)['head']
+ head = TTFont(subsetpath)["head"]
bounds = [132, 304, 365, 567]
assert bounds == [head.xMin, head.yMin, head.xMax, head.yMax]
@@ -533,49 +685,59 @@ class SubsetTest:
ttxpath = self.getpath("TestTTF-Regular.ttx")
font = TTFont()
font.importXML(ttxpath)
- modified = font['head'].modified
+ modified = font["head"].modified
fontpath = self.compile_font(ttxpath, ".ttf")
subsetpath = self.temp_path(".ttf")
# by default, the subsetter does not recalculate the modified timestamp
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
- assert modified == TTFont(subsetpath)['head'].modified
+ assert modified == TTFont(subsetpath)["head"].modified
- subset.main([fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"])
- assert modified < TTFont(subsetpath)['head'].modified
+ subset.main(
+ [fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"]
+ )
+ assert modified < TTFont(subsetpath)["head"].modified
def test_recalc_timestamp_otf(self):
ttxpath = self.getpath("TestOTF-Regular.ttx")
font = TTFont()
font.importXML(ttxpath)
- modified = font['head'].modified
+ modified = font["head"].modified
fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
# by default, the subsetter does not recalculate the modified timestamp
subset.main([fontpath, "--output-file=%s" % subsetpath, "*"])
- assert modified == TTFont(subsetpath)['head'].modified
+ assert modified == TTFont(subsetpath)["head"].modified
- subset.main([fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"])
- assert modified < TTFont(subsetpath)['head'].modified
+ subset.main(
+ [fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"]
+ )
+ assert modified < TTFont(subsetpath)["head"].modified
def test_recalc_max_context(self):
ttxpath = self.getpath("Lobster.subset.ttx")
font = TTFont()
font.importXML(ttxpath)
- max_context = font['OS/2'].usMaxContext
+ max_context = font["OS/2"].usMaxContext
fontpath = self.compile_font(ttxpath, ".otf")
subsetpath = self.temp_path(".otf")
# by default, the subsetter does not recalculate the usMaxContext
- subset.main([fontpath, "--drop-tables+=GSUB,GPOS",
- "--output-file=%s" % subsetpath])
- assert max_context == TTFont(subsetpath)['OS/2'].usMaxContext
+ subset.main(
+ [fontpath, "--drop-tables+=GSUB,GPOS", "--output-file=%s" % subsetpath]
+ )
+ assert max_context == TTFont(subsetpath)["OS/2"].usMaxContext
- subset.main([fontpath, "--recalc-max-context",
- "--drop-tables+=GSUB,GPOS",
- "--output-file=%s" % subsetpath])
- assert 0 == TTFont(subsetpath)['OS/2'].usMaxContext
+ subset.main(
+ [
+ fontpath,
+ "--recalc-max-context",
+ "--drop-tables+=GSUB,GPOS",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
+ assert 0 == TTFont(subsetpath)["OS/2"].usMaxContext
def test_retain_gids_ttf(self):
fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
@@ -648,7 +810,9 @@ class SubsetTest:
assert len(cs["B"].program) > 0
def test_retain_gids_cff2(self):
- ttx_path = self.getpath("../../varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx")
+ ttx_path = self.getpath(
+ "../../varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx"
+ )
fontpath = self.compile_font(ttx_path, ".otf")
font = TTFont(fontpath)
@@ -687,20 +851,29 @@ class SubsetTest:
subsetpath = self.temp_path(".ttf")
subset.main([fontpath, "--text=BD", "--output-file=%s" % subsetpath])
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_HVVAR.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_HVVAR.ttx"),
+ ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"],
+ )
def test_HVAR_VVAR_retain_gids(self):
fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")
subsetpath = self.temp_path(".ttf")
- subset.main([fontpath, "--text=BD", "--retain-gids", "--output-file=%s" % subsetpath])
+ subset.main(
+ [fontpath, "--text=BD", "--retain-gids", "--output-file=%s" % subsetpath]
+ )
subsetfont = TTFont(subsetpath)
- self.expect_ttx(subsetfont, self.getpath("expect_HVVAR_retain_gids.ttx"), ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"])
+ self.expect_ttx(
+ subsetfont,
+ self.getpath("expect_HVVAR_retain_gids.ttx"),
+ ["GlyphOrder", "HVAR", "VVAR", "avar", "fvar"],
+ )
- def test_subset_flavor(self):
+ def test_subset_flavor_woff(self):
fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
- font = TTFont(fontpath)
-
woff_path = self.temp_path(".woff")
+
subset.main(
[
fontpath,
@@ -713,10 +886,16 @@ class SubsetTest:
assert woff.flavor == "woff"
+ def test_subset_flavor_woff2(self):
+ # skip if brotli is not importable, required for woff2
+ pytest.importorskip("brotli")
+
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
woff2_path = self.temp_path(".woff2")
+
subset.main(
[
- woff_path,
+ fontpath,
"*",
"--flavor=woff2",
"--output-file=%s" % woff2_path,
@@ -726,10 +905,13 @@ class SubsetTest:
assert woff2.flavor == "woff2"
+ def test_subset_flavor_none(self):
+ fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf")
ttf_path = self.temp_path(".ttf")
+
subset.main(
[
- woff2_path,
+ fontpath,
"*",
"--output-file=%s" % ttf_path,
]
@@ -885,12 +1067,33 @@ class SubsetTest:
# test we emit a log.error if hb.repack fails (and we don't if successful)
assert (
- (
+ (
"hb.repack failed to serialize 'GSUB', attempting fonttools resolutions "
"; the error message was: RepackerError: mocking"
- ) in caplog.text
+ )
+ in caplog.text
) ^ ok
+ def test_retain_east_asian_spacing_features(self):
+ # This test font contains halt and vhal features, check that
+ # they are retained by default after subsetting.
+ ttx_path = self.getpath("NotoSansCJKjp-Regular.subset.ttx")
+ ttx = pathlib.Path(ttx_path).read_text()
+ assert 'FeatureTag value="halt"' in ttx
+ assert 'FeatureTag value="vhal"' in ttx
+
+ fontpath = self.compile_font(ttx_path, ".otf")
+ subsetpath = self.temp_path(".otf")
+ subset.main(
+ [
+ fontpath,
+ "--unicodes=*",
+ "--output-file=%s" % subsetpath,
+ ]
+ )
+ # subset output is the same as the input
+ self.expect_ttx(TTFont(subsetpath), ttx_path)
+
@pytest.fixture
def featureVarsTestFont():
@@ -900,14 +1103,15 @@ def featureVarsTestFont():
fb.setupNameTable({"familyName": "TestFeatureVars", "styleName": "Regular"})
fb.setupPost()
fb.setupFvar(axes=[("wght", 100, 400, 900, "Weight")], instances=[])
- fb.addOpenTypeFeatures("""\
+ fb.addOpenTypeFeatures(
+ """\
feature dlig {
sub f f by f_f;
} dlig;
- """)
+ """
+ )
fb.addFeatureVariations(
- [([{"wght": (0.20886, 1.0)}], {"dollar": "dollar.rvrn"})],
- featureTag="rvrn"
+ [([{"wght": (0.20886, 1.0)}], {"dollar": "dollar.rvrn"})], featureTag="rvrn"
)
buf = io.BytesIO()
fb.save(buf)
@@ -924,9 +1128,7 @@ def test_subset_feature_variations_keep_all(featureVarsTestFont):
subsetter.populate(unicodes=[ord("f"), ord("$")])
subsetter.subset(font)
- featureTags = {
- r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord
- }
+ 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()
@@ -944,9 +1146,7 @@ def test_subset_feature_variations_drop_all(featureVarsTestFont):
subsetter.populate(unicodes=[ord("f"), ord("$")])
subsetter.subset(font)
- featureTags = {
- r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord
- }
+ featureTags = {r.FeatureTag for r in font["GSUB"].table.FeatureList.FeatureRecord}
glyphs = set(font.getGlyphOrder())
assert "rvrn" not in featureTags
@@ -967,13 +1167,15 @@ def singlepos2_font():
fb.setupCharacterMap({ord("a"): "a", ord("b"): "b", ord("c"): "c"})
fb.setupNameTable({"familyName": "TestSingePosFormat", "styleName": "Regular"})
fb.setupPost()
- fb.addOpenTypeFeatures("""
+ fb.addOpenTypeFeatures(
+ """
feature kern {
pos a -50;
pos b -40;
pos c -50;
} kern;
- """)
+ """
+ )
buf = io.BytesIO()
fb.save(buf)
@@ -987,23 +1189,23 @@ def test_subset_single_pos_format(singlepos2_font):
# 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>',
+ "<Lookup>",
' <LookupType value="1"/>',
' <LookupFlag value="0"/>',
- ' <!-- SubTableCount=1 -->',
+ " <!-- SubTableCount=1 -->",
' <SinglePos index="0" Format="2">',
- ' <Coverage>',
+ " <Coverage>",
' <Glyph value="a"/>',
' <Glyph value="b"/>',
' <Glyph value="c"/>',
- ' </Coverage>',
+ " </Coverage>",
' <ValueFormat value="4"/>',
- ' <!-- ValueCount=3 -->',
+ " <!-- ValueCount=3 -->",
' <Value index="0" XAdvance="-50"/>',
' <Value index="1" XAdvance="-40"/>',
' <Value index="2" XAdvance="-50"/>',
- ' </SinglePos>',
- '</Lookup>',
+ " </SinglePos>",
+ "</Lookup>",
]
options = subset.Options()
@@ -1014,21 +1216,22 @@ def test_subset_single_pos_format(singlepos2_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>',
+ "<Lookup>",
' <LookupType value="1"/>',
' <LookupFlag value="0"/>',
- ' <!-- SubTableCount=1 -->',
+ " <!-- SubTableCount=1 -->",
' <SinglePos index="0" Format="1">',
- ' <Coverage>',
+ " <Coverage>",
' <Glyph value="a"/>',
' <Glyph value="c"/>',
- ' </Coverage>',
+ " </Coverage>",
' <ValueFormat value="4"/>',
' <Value XAdvance="-50"/>',
- ' </SinglePos>',
- '</Lookup>',
+ " </SinglePos>",
+ "</Lookup>",
]
+
def test_subset_single_pos_format2_all_None(singlepos2_font):
# https://github.com/fonttools/fonttools/issues/2602
font = singlepos2_font
@@ -1043,14 +1246,14 @@ def test_subset_single_pos_format2_all_None(singlepos2_font):
assert getXML(subtable.toXML, font) == [
'<SinglePos Format="2">',
- ' <Coverage>',
+ " <Coverage>",
' <Glyph value="a"/>',
' <Glyph value="b"/>',
' <Glyph value="c"/>',
- ' </Coverage>',
+ " </Coverage>",
' <ValueFormat value="0"/>',
- ' <!-- ValueCount=3 -->',
- '</SinglePos>',
+ " <!-- ValueCount=3 -->",
+ "</SinglePos>",
]
options = subset.Options()
@@ -1061,12 +1264,12 @@ def test_subset_single_pos_format2_all_None(singlepos2_font):
# Check it was downgraded to Format1 after subsetting
assert getXML(font["GPOS"].table.LookupList.Lookup[0].SubTable[0].toXML, font) == [
'<SinglePos Format="1">',
- ' <Coverage>',
+ " <Coverage>",
' <Glyph value="a"/>',
' <Glyph value="c"/>',
- ' </Coverage>',
+ " </Coverage>",
' <ValueFormat value="0"/>',
- '</SinglePos>',
+ "</SinglePos>",
]
@@ -1096,7 +1299,7 @@ def test_subset_empty_glyf(tmp_path, ttf_path):
subset_font = TTFont(subset_path)
assert subset_font.getGlyphOrder() == [".notdef", "space"]
- assert subset_font.reader['glyf'] == b"\x00"
+ assert subset_font.reader["glyf"] == b"\x00"
glyf = subset_font["glyf"]
assert all(glyf[g].numberOfContours == 0 for g in subset_font.getGlyphOrder())
@@ -1225,8 +1428,8 @@ def colrv1_path(tmp_path):
clipBoxes={
"uniE000": (0, 0, 200, 300),
"uniE001": (0, 0, 500, 500),
- "uniE002": (100, 100, 400, 400),
- "uniE003": (-50, -50, 350, 350),
+ "uniE002": (-50, -50, 400, 400),
+ "uniE003": (-50, -50, 400, 400),
},
)
fb.setupCPAL(
@@ -1245,6 +1448,54 @@ def colrv1_path(tmp_path):
return output_path
+@pytest.fixture
+def colrv1_cpalv1_path(colrv1_path):
+ # upgrade CPAL from v0 to v1 by adding labels
+ font = TTFont(colrv1_path)
+ fb = FontBuilder(font=font)
+ 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
+ ],
+ ],
+ paletteLabels=["test palette"],
+ paletteEntryLabels=["first color", "second color", "third color"],
+ )
+
+ output_path = colrv1_path.parent / "TestCOLRv1CPALv1.ttf"
+ fb.save(output_path)
+
+ return output_path
+
+
+@pytest.fixture
+def colrv1_cpalv1_share_nameID_path(colrv1_path):
+ font = TTFont(colrv1_path)
+ fb = FontBuilder(font=font)
+ 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
+ ],
+ ],
+ paletteLabels=["test palette"],
+ paletteEntryLabels=["first color", "second color", "third color"],
+ )
+
+ # Set the name ID of the first color to use nameID 1 = familyName = "TestCOLRv1"
+ fb.font["CPAL"].paletteEntryLabels[0] = 1
+
+ output_path = colrv1_path.parent / "TestCOLRv1CPALv1.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")
@@ -1290,7 +1541,7 @@ def test_subset_COLRv1_and_CPAL(colrv1_path):
base = colr.BaseGlyphList.BaseGlyphPaintRecord[0]
assert base.BaseGlyph == "uniE001"
layers = colr.LayerList.Paint[
- base.Paint.FirstLayerIndex: base.Paint.FirstLayerIndex + base.Paint.NumLayers
+ base.Paint.FirstLayerIndex : base.Paint.FirstLayerIndex + base.Paint.NumLayers
]
assert len(layers) == 2
# check v1 palette indices were remapped
@@ -1309,6 +1560,7 @@ def test_subset_COLRv1_and_CPAL(colrv1_path):
clipBoxes = colr.ClipList.clips
assert {"uniE001", "uniE002", "uniE003"} == set(clipBoxes)
+ assert clipBoxes["uniE002"] == clipBoxes["uniE003"]
assert "CPAL" in subset_font
cpal = subset_font["CPAL"]
@@ -1322,6 +1574,110 @@ def test_subset_COLRv1_and_CPAL(colrv1_path):
]
+def test_subset_COLRv1_and_CPALv1(colrv1_cpalv1_path):
+ subset_path = colrv1_cpalv1_path.parent / (colrv1_cpalv1_path.name + ".subset")
+
+ subset.main(
+ [
+ str(colrv1_cpalv1_path),
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--unicodes=E002,E003,E004",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ assert "CPAL" in subset_font
+ cpal = subset_font["CPAL"]
+ name_table = subset_font["name"]
+ assert [
+ name_table.getDebugName(name_id) for name_id in cpal.paletteEntryLabels
+ ] == [
+ # "first color", # The first color was pruned
+ "second color",
+ "third color",
+ ]
+ # check that the "first color" name is dropped from name table
+ font = TTFont(colrv1_cpalv1_path)
+
+ first_color_nameID = None
+ for n in font["name"].names:
+ if n.toUnicode() == "first color":
+ first_color_nameID = n.nameID
+ break
+ assert first_color_nameID is not None
+ assert all(n.nameID != first_color_nameID for n in name_table.names)
+
+
+def test_subset_COLRv1_and_CPALv1_keep_nameID(colrv1_cpalv1_path):
+ subset_path = colrv1_cpalv1_path.parent / (colrv1_cpalv1_path.name + ".subset")
+
+ # figure out the name ID of first color so we can keep it
+ font = TTFont(colrv1_cpalv1_path)
+
+ first_color_nameID = None
+ for n in font["name"].names:
+ if n.toUnicode() == "first color":
+ first_color_nameID = n.nameID
+ break
+ assert first_color_nameID is not None
+
+ subset.main(
+ [
+ str(colrv1_cpalv1_path),
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--unicodes=E002,E003,E004",
+ f"--name-IDs={first_color_nameID}",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ assert "CPAL" in subset_font
+ cpal = subset_font["CPAL"]
+ name_table = subset_font["name"]
+ assert [
+ name_table.getDebugName(name_id) for name_id in cpal.paletteEntryLabels
+ ] == [
+ # "first color", # The first color was pruned
+ "second color",
+ "third color",
+ ]
+
+ # Check that the name ID is kept
+ assert any(n.nameID == first_color_nameID for n in name_table.names)
+
+
+def test_subset_COLRv1_and_CPALv1_share_nameID(colrv1_cpalv1_share_nameID_path):
+ subset_path = colrv1_cpalv1_share_nameID_path.parent / (
+ colrv1_cpalv1_share_nameID_path.name + ".subset"
+ )
+
+ subset.main(
+ [
+ str(colrv1_cpalv1_share_nameID_path),
+ "--glyph-names",
+ f"--output-file={subset_path}",
+ "--unicodes=E002,E003,E004",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+
+ assert "CPAL" in subset_font
+ cpal = subset_font["CPAL"]
+ name_table = subset_font["name"]
+ assert [
+ name_table.getDebugName(name_id) for name_id in cpal.paletteEntryLabels
+ ] == [
+ # "first color", # The first color was pruned
+ "second color",
+ "third color",
+ ]
+
+ # Check that the name ID 1 is kept
+ assert any(n.nameID == 1 for n in name_table.names)
+
+
def test_subset_COLRv1_and_CPAL_drop_empty(colrv1_path):
subset_path = colrv1_path.parent / (colrv1_path.name + ".subset")
@@ -1427,7 +1783,8 @@ def test_subset_keep_size_drop_empty_stylistic_set():
fb.setupOS2()
fb.setupPost()
fb.setupNameTable({"familyName": "TestKeepSizeFeature", "styleName": "Regular"})
- fb.addOpenTypeFeatures("""
+ fb.addOpenTypeFeatures(
+ """
feature size {
parameters 10.0 0;
} size;
@@ -1437,7 +1794,8 @@ def test_subset_keep_size_drop_empty_stylistic_set():
};
sub b by b.ss01;
} ss01;
- """)
+ """
+ )
buf = io.BytesIO()
fb.save(buf)
@@ -1529,6 +1887,139 @@ def test_subset_COLR_glyph_closure(tmp_path):
assert "grave" not in color_layers
+def test_subset_recalc_xAvgCharWidth(ttf_path):
+ # Note that the font in in the *ttLib*/data/TestTTF-Regular.ttx file,
+ # not this subset/data folder.
+ font = TTFont(ttf_path)
+ xAvgCharWidth_before = font["OS/2"].xAvgCharWidth
+
+ subset_path = ttf_path.with_suffix(".subset.ttf")
+ subset.main(
+ [
+ str(ttf_path),
+ f"--output-file={subset_path}",
+ # Keep only the ellipsis, which is very wide, that ought to bump up the average
+ "--glyphs=ellipsis",
+ "--recalc-average-width",
+ "--no-prune-unicode-ranges",
+ ]
+ )
+ subset_font = TTFont(subset_path)
+ xAvgCharWidth_after = subset_font["OS/2"].xAvgCharWidth
+
+ # Check that the value gets updated
+ assert xAvgCharWidth_after != xAvgCharWidth_before
+
+ # Check that the value gets updated to the actual new value
+ subset_font["OS/2"].recalcAvgCharWidth(subset_font)
+ assert xAvgCharWidth_after == subset_font["OS/2"].xAvgCharWidth
+
if __name__ == "__main__":
sys.exit(unittest.main())
+
+
+def test_subset_prune_gdef_markglyphsetsdef():
+ # GDEF_MarkGlyphSetsDef
+ fb = FontBuilder(unitsPerEm=1000, isTTF=True)
+ glyph_order = [
+ ".notdef",
+ "A",
+ "Aacute",
+ "Acircumflex",
+ "Adieresis",
+ "a",
+ "aacute",
+ "acircumflex",
+ "adieresis",
+ "dieresiscomb",
+ "acutecomb",
+ "circumflexcomb",
+ ]
+ fb.setupGlyphOrder(glyph_order)
+ fb.setupGlyf({g: TTGlyphPen(None).glyph() for g in glyph_order})
+ fb.setupHorizontalMetrics({g: (500, 0) for g in glyph_order})
+ fb.setupHorizontalHeader()
+ fb.setupPost()
+ fb.setupNameTable(
+ {"familyName": "TestGDEFMarkGlyphSetsDef", "styleName": "Regular"}
+ )
+ fb.addOpenTypeFeatures(
+ """
+ feature ccmp {
+ lookup ccmp_1 {
+ lookupflag UseMarkFilteringSet [acutecomb];
+ sub a acutecomb by aacute;
+ sub A acutecomb by Aacute;
+ } ccmp_1;
+ lookup ccmp_2 {
+ lookupflag UseMarkFilteringSet [circumflexcomb];
+ sub a circumflexcomb by acircumflex;
+ sub A circumflexcomb by Acircumflex;
+ } ccmp_2;
+ lookup ccmp_3 {
+ lookupflag UseMarkFilteringSet [dieresiscomb];
+ sub a dieresiscomb by adieresis;
+ sub A dieresiscomb by Adieresis;
+ sub A acutecomb by Aacute;
+ } ccmp_3;
+ } ccmp;
+ """
+ )
+
+ buf = io.BytesIO()
+ fb.save(buf)
+ buf.seek(0)
+
+ font = TTFont(buf)
+
+ features = font["GSUB"].table.FeatureList.FeatureRecord
+ assert features[0].FeatureTag == "ccmp"
+ lookups = font["GSUB"].table.LookupList.Lookup
+ assert lookups[0].LookupFlag == 16
+ assert lookups[0].MarkFilteringSet == 0
+ assert lookups[1].LookupFlag == 16
+ assert lookups[1].MarkFilteringSet == 1
+ assert lookups[2].LookupFlag == 16
+ assert lookups[2].MarkFilteringSet == 2
+ marksets = font["GDEF"].table.MarkGlyphSetsDef.Coverage
+ assert marksets[0].glyphs == ["acutecomb"]
+ assert marksets[1].glyphs == ["circumflexcomb"]
+ assert marksets[2].glyphs == ["dieresiscomb"]
+
+ options = subset.Options(layout_features=["*"])
+ subsetter = subset.Subsetter(options)
+ subsetter.populate(glyphs=["A", "a", "acutecomb", "dieresiscomb"])
+ subsetter.subset(font)
+
+ features = font["GSUB"].table.FeatureList.FeatureRecord
+ assert features[0].FeatureTag == "ccmp"
+ lookups = font["GSUB"].table.LookupList.Lookup
+ assert lookups[0].LookupFlag == 16
+ assert lookups[0].MarkFilteringSet == 0
+ assert lookups[1].LookupFlag == 16
+ assert lookups[1].MarkFilteringSet == 1
+ marksets = font["GDEF"].table.MarkGlyphSetsDef.Coverage
+ assert marksets[0].glyphs == ["acutecomb"]
+ assert marksets[1].glyphs == ["dieresiscomb"]
+
+ buf = io.BytesIO()
+ fb.save(buf)
+ buf.seek(0)
+
+ font = TTFont(buf)
+
+ options = subset.Options(layout_features=["*"], layout_closure=False)
+ subsetter = subset.Subsetter(options)
+ subsetter.populate(glyphs=["A", "acutecomb", "Aacute"])
+ subsetter.subset(font)
+
+ features = font["GSUB"].table.FeatureList.FeatureRecord
+ assert features[0].FeatureTag == "ccmp"
+ lookups = font["GSUB"].table.LookupList.Lookup
+ assert lookups[0].LookupFlag == 16
+ assert lookups[0].MarkFilteringSet == 0
+ assert lookups[1].LookupFlag == 0
+ assert lookups[1].MarkFilteringSet == None
+ marksets = font["GDEF"].table.MarkGlyphSetsDef.Coverage
+ assert marksets[0].glyphs == ["acutecomb"]
diff --git a/Tests/svgLib/path/parser_test.py b/Tests/svgLib/path/parser_test.py
index d33043fc..4db64919 100644
--- a/Tests/svgLib/path/parser_test.py
+++ b/Tests/svgLib/path/parser_test.py
@@ -7,9 +7,7 @@ import pytest
@pytest.mark.parametrize(
"pathdef, expected",
[
-
# Examples from the SVG spec
-
(
"M 100 100 L 300 100 L 200 300 z",
[
@@ -18,7 +16,7 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
# for Z command behavior when there is multiple subpaths
(
@@ -32,97 +30,76 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
(
"M100,200 C100,100 250,100 250,200 S400,300 400,200",
[
("moveTo", ((100.0, 200.0),)),
- ("curveTo", ((100.0, 100.0),
- (250.0, 100.0),
- (250.0, 200.0))),
- ("curveTo", ((250.0, 300.0),
- (400.0, 300.0),
- (400.0, 200.0))),
+ ("curveTo", ((100.0, 100.0), (250.0, 100.0), (250.0, 200.0))),
+ ("curveTo", ((250.0, 300.0), (400.0, 300.0), (400.0, 200.0))),
("endPath", ()),
- ]
+ ],
),
(
"M100,200 C100,100 400,100 400,200",
[
("moveTo", ((100.0, 200.0),)),
- ("curveTo", ((100.0, 100.0),
- (400.0, 100.0),
- (400.0, 200.0))),
+ ("curveTo", ((100.0, 100.0), (400.0, 100.0), (400.0, 200.0))),
("endPath", ()),
- ]
+ ],
),
(
"M100,500 C25,400 475,400 400,500",
[
("moveTo", ((100.0, 500.0),)),
- ("curveTo", ((25.0, 400.0),
- (475.0, 400.0),
- (400.0, 500.0))),
+ ("curveTo", ((25.0, 400.0), (475.0, 400.0), (400.0, 500.0))),
("endPath", ()),
- ]
+ ],
),
(
"M100,800 C175,700 325,700 400,800",
[
("moveTo", ((100.0, 800.0),)),
- ("curveTo", ((175.0, 700.0),
- (325.0, 700.0),
- (400.0, 800.0))),
+ ("curveTo", ((175.0, 700.0), (325.0, 700.0), (400.0, 800.0))),
("endPath", ()),
- ]
+ ],
),
(
"M600,200 C675,100 975,100 900,200",
[
("moveTo", ((600.0, 200.0),)),
- ("curveTo", ((675.0, 100.0),
- (975.0, 100.0),
- (900.0, 200.0))),
+ ("curveTo", ((675.0, 100.0), (975.0, 100.0), (900.0, 200.0))),
("endPath", ()),
- ]
+ ],
),
(
"M600,500 C600,350 900,650 900,500",
[
("moveTo", ((600.0, 500.0),)),
- ("curveTo", ((600.0, 350.0),
- (900.0, 650.0),
- (900.0, 500.0))),
+ ("curveTo", ((600.0, 350.0), (900.0, 650.0), (900.0, 500.0))),
("endPath", ()),
- ]
+ ],
),
(
"M600,800 C625,700 725,700 750,800 S875,900 900,800",
[
("moveTo", ((600.0, 800.0),)),
- ("curveTo", ((625.0, 700.0),
- (725.0, 700.0),
- (750.0, 800.0))),
- ("curveTo", ((775.0, 900.0),
- (875.0, 900.0),
- (900.0, 800.0))),
+ ("curveTo", ((625.0, 700.0), (725.0, 700.0), (750.0, 800.0))),
+ ("curveTo", ((775.0, 900.0), (875.0, 900.0), (900.0, 800.0))),
("endPath", ()),
- ]
+ ],
),
(
"M200,300 Q400,50 600,300 T1000,300",
[
("moveTo", ((200.0, 300.0),)),
- ("qCurveTo", ((400.0, 50.0),
- (600.0, 300.0))),
- ("qCurveTo", ((800.0, 550.0),
- (1000.0, 300.0))),
+ ("qCurveTo", ((400.0, 50.0), (600.0, 300.0))),
+ ("qCurveTo", ((800.0, 550.0), (1000.0, 300.0))),
("endPath", ()),
- ]
+ ],
),
# End examples from SVG spec
-
# Relative moveto
(
"M 0 0 L 50 20 m 50 80 L 300 100 L 200 300 z",
@@ -135,28 +112,25 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
# Initial smooth and relative curveTo
(
"M100,200 s 150,-100 150,0",
[
("moveTo", ((100.0, 200.0),)),
- ("curveTo", ((100.0, 200.0),
- (250.0, 100.0),
- (250.0, 200.0))),
+ ("curveTo", ((100.0, 200.0), (250.0, 100.0), (250.0, 200.0))),
("endPath", ()),
- ]
+ ],
),
# Initial smooth and relative qCurveTo
(
"M100,200 t 150,0",
[
("moveTo", ((100.0, 200.0),)),
- ("qCurveTo", ((100.0, 200.0),
- (250.0, 200.0))),
+ ("qCurveTo", ((100.0, 200.0), (250.0, 200.0))),
("endPath", ()),
- ]
+ ],
),
# relative l command
(
@@ -167,17 +141,16 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
# relative q command
(
"M200,300 q200,-250 400,0",
[
("moveTo", ((200.0, 300.0),)),
- ("qCurveTo", ((400.0, 50.0),
- (600.0, 300.0))),
+ ("qCurveTo", ((400.0, 50.0), (600.0, 300.0))),
("endPath", ()),
- ]
+ ],
),
# absolute H command
(
@@ -188,7 +161,7 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
# relative h command
(
@@ -199,7 +172,7 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
# absolute V command
(
@@ -210,7 +183,7 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
# relative v command
(
@@ -221,9 +194,9 @@ import pytest
("lineTo", ((200.0, 300.0),)),
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
- ]
+ ],
),
- ]
+ ],
)
def test_parse_path(pathdef, expected):
pen = RecordingPen()
@@ -241,22 +214,16 @@ def test_parse_path(pathdef, expected):
"M100 100L200 200",
),
# repeated implicit command
- (
- "M 100 200 L 200 100 L -100 -200",
- "M 100 200 L 200 100 -100 -200"
- ),
+ ("M 100 200 L 200 100 L -100 -200", "M 100 200 L 200 100 -100 -200"),
# don't need spaces before a minus-sign
- (
- "M100,200c10-5,20-10,30-20",
- "M 100 200 c 10 -5 20 -10 30 -20"
- ),
+ ("M100,200c10-5,20-10,30-20", "M 100 200 c 10 -5 20 -10 30 -20"),
# closed paths have an implicit lineTo if they don't
# end on the same point as the initial moveTo
(
"M 100 100 L 300 100 L 200 300 z",
- "M 100 100 L 300 100 L 200 300 L 100 100 z"
- )
- ]
+ "M 100 100 L 300 100 L 200 300 L 100 100 z",
+ ),
+ ],
)
def test_equivalent_paths(pathdef1, pathdef2):
pen1 = RecordingPen()
@@ -273,7 +240,7 @@ def test_exponents():
pen = RecordingPen()
parse_path("M-3.4e38 3.4E+38L-3.4E-38,3.4e-38", pen)
expected = [
- ("moveTo", ((-3.4e+38, 3.4e+38),)),
+ ("moveTo", ((-3.4e38, 3.4e38),)),
("lineTo", ((-3.4e-38, 3.4e-38),)),
("endPath", ()),
]
@@ -283,13 +250,14 @@ def test_exponents():
pen = RecordingPen()
parse_path("M-3e38 3E+38L-3E-38,3e-38", pen)
expected = [
- ("moveTo", ((-3e+38, 3e+38),)),
+ ("moveTo", ((-3e38, 3e38),)),
("lineTo", ((-3e-38, 3e-38),)),
("endPath", ()),
]
assert pen.value == expected
+
def test_invalid_implicit_command():
with pytest.raises(ValueError) as exc_info:
parse_path("M 100 100 L 200 200 Z 100 200", RecordingPen())
@@ -300,34 +268,13 @@ def test_arc_to_cubic_bezier():
pen = RecordingPen()
parse_path("M300,200 h-150 a150,150 0 1,0 150,-150 z", pen)
expected = [
- ('moveTo', ((300.0, 200.0),)),
- ('lineTo', ((150.0, 200.0),)),
- (
- 'curveTo',
- (
- (150.0, 282.842),
- (217.157, 350.0),
- (300.0, 350.0)
- )
- ),
- (
- 'curveTo',
- (
- (382.842, 350.0),
- (450.0, 282.842),
- (450.0, 200.0)
- )
- ),
- (
- 'curveTo',
- (
- (450.0, 117.157),
- (382.842, 50.0),
- (300.0, 50.0)
- )
- ),
- ('lineTo', ((300.0, 200.0),)),
- ('closePath', ())
+ ("moveTo", ((300.0, 200.0),)),
+ ("lineTo", ((150.0, 200.0),)),
+ ("curveTo", ((150.0, 282.842), (217.157, 350.0), (300.0, 350.0))),
+ ("curveTo", ((382.842, 350.0), (450.0, 282.842), (450.0, 200.0))),
+ ("curveTo", ((450.0, 117.157), (382.842, 50.0), (300.0, 50.0))),
+ ("lineTo", ((300.0, 200.0),)),
+ ("closePath", ()),
]
result = list(pen.value)
@@ -339,9 +286,7 @@ def test_arc_to_cubic_bezier():
assert pt1 == pytest.approx(pt2, rel=1e-5)
-
class ArcRecordingPen(RecordingPen):
-
def arcTo(self, rx, ry, rotation, arc_large, arc_sweep, end_point):
self.value.append(
("arcTo", (rx, ry, rotation, arc_large, arc_sweep, end_point))
@@ -352,11 +297,11 @@ def test_arc_pen_with_arcTo():
pen = ArcRecordingPen()
parse_path("M300,200 h-150 a150,150 0 1,0 150,-150 z", pen)
expected = [
- ('moveTo', ((300.0, 200.0),)),
- ('lineTo', ((150.0, 200.0),)),
- ('arcTo', (150.0, 150.0, 0.0, True, False, (300.0, 50.0))),
- ('lineTo', ((300.0, 200.0),)),
- ('closePath', ())
+ ("moveTo", ((300.0, 200.0),)),
+ ("lineTo", ((150.0, 200.0),)),
+ ("arcTo", (150.0, 150.0, 0.0, True, False, (300.0, 50.0))),
+ ("lineTo", ((300.0, 200.0),)),
+ ("closePath", ()),
]
assert pen.value == expected
@@ -400,13 +345,12 @@ def test_arc_pen_with_arcTo():
(
"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):
@@ -415,9 +359,7 @@ def test_arc_flags_without_spaces(path, expected):
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"]
-)
+@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:
diff --git a/Tests/svgLib/path/path_test.py b/Tests/svgLib/path/path_test.py
index caf351d4..0b82193d 100644
--- a/Tests/svgLib/path/path_test.py
+++ b/Tests/svgLib/path/path_test.py
@@ -24,18 +24,13 @@ EXPECTED_PEN_COMMANDS = [
("lineTo", ((100.0, 100.0),)),
("closePath", ()),
("moveTo", ((100.0, 200.0),)),
- ("curveTo", ((100.0, 100.0),
- (250.0, 100.0),
- (250.0, 200.0))),
- ("curveTo", ((250.0, 300.0),
- (400.0, 300.0),
- (400.0, 200.0))),
- ("endPath", ())
+ ("curveTo", ((100.0, 100.0), (250.0, 100.0), (250.0, 200.0))),
+ ("curveTo", ((250.0, 300.0), (400.0, 300.0), (400.0, 200.0))),
+ ("endPath", ()),
]
class SVGPathTest(object):
-
def test_from_svg_file(self):
pen = RecordingPen()
with NamedTemporaryFile(delete=False) as tmp:
@@ -57,8 +52,7 @@ class SVGPathTest(object):
def test_transform(self):
pen = RecordingPen()
- svg = SVGPath.fromstring(SVG_DATA,
- transform=(1.0, 0, 0, -1.0, 0, 1000))
+ svg = SVGPath.fromstring(SVG_DATA, transform=(1.0, 0, 0, -1.0, 0, 1000))
svg.draw(pen)
assert pen.value == [
@@ -68,11 +62,7 @@ class SVGPathTest(object):
("lineTo", ((100.0, 900.0),)),
("closePath", ()),
("moveTo", ((100.0, 800.0),)),
- ("curveTo", ((100.0, 900.0),
- (250.0, 900.0),
- (250.0, 800.0))),
- ("curveTo", ((250.0, 700.0),
- (400.0, 700.0),
- (400.0, 800.0))),
- ("endPath", ())
+ ("curveTo", ((100.0, 900.0), (250.0, 900.0), (250.0, 800.0))),
+ ("curveTo", ((250.0, 700.0), (400.0, 700.0), (400.0, 800.0))),
+ ("endPath", ()),
]
diff --git a/Tests/svgLib/path/shapes_test.py b/Tests/svgLib/path/shapes_test.py
index 24e3dd2e..0d5be842 100644
--- a/Tests/svgLib/path/shapes_test.py
+++ b/Tests/svgLib/path/shapes_test.py
@@ -7,115 +7,87 @@ import pytest
"svg_xml, expected_path, expected_transform",
[
# path: direct passthrough
- (
- "<path d='I love kittens'/>",
- "I love kittens",
- None
- ),
+ ("<path d='I love kittens'/>", "I love kittens", None),
# path no @d
- (
- "<path duck='Mallard'/>",
- None,
- None
- ),
+ ("<path duck='Mallard'/>", None, None),
# line
- (
- '<line x1="10" x2="50" y1="110" y2="150"/>',
- 'M10,110 L50,150',
- None
- ),
+ ('<line x1="10" x2="50" y1="110" y2="150"/>', "M10,110 L50,150", None),
# line, decimal positioning
(
'<line x1="10.0" x2="50.5" y1="110.2" y2="150.7"/>',
- 'M10,110.2 L50.5,150.7',
- None
+ "M10,110.2 L50.5,150.7",
+ None,
),
# rect: minimal valid example
- (
- "<rect width='1' height='1'/>",
- "M0,0 H1 V1 H0 V0 z",
- None
- ),
+ ("<rect width='1' height='1'/>", "M0,0 H1 V1 H0 V0 z", None),
# rect: sharp corners
(
"<rect x='10' y='11' width='17' height='11'/>",
"M10,11 H27 V22 H10 V11 z",
- None
+ None,
),
# rect: round corners
(
"<rect x='9' y='9' width='11' height='7' rx='2'/>",
"M11,9 H18 A2,2 0 0 1 20,11 V14 A2,2 0 0 1 18,16 H11"
" A2,2 0 0 1 9,14 V11 A2,2 0 0 1 11,9 z",
- None
+ None,
),
# rect: simple
(
"<rect x='11.5' y='16' width='11' height='2'/>",
"M11.5,16 H22.5 V18 H11.5 V16 z",
- None
+ None,
),
# rect: the one above plus a rotation
(
"<rect x='11.5' y='16' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -7.0416 16.9999)' width='11' height='2'/>",
"M11.5,16 H22.5 V18 H11.5 V16 z",
- (0.7071, -0.7071, 0.7071, 0.7071, -7.0416, 16.9999)
+ (0.7071, -0.7071, 0.7071, 0.7071, -7.0416, 16.9999),
),
# polygon
- (
- "<polygon points='30,10 50,30 10,30'/>",
- "M30,10 50,30 10,30 z",
- None
- ),
+ ("<polygon points='30,10 50,30 10,30'/>", "M30,10 50,30 10,30 z", None),
# polyline
- (
- "<polyline points='30,10 50,30 10,30'/>",
- "M30,10 50,30 10,30",
- None
- ),
+ ("<polyline points='30,10 50,30 10,30'/>", "M30,10 50,30 10,30", None),
# circle, minimal valid example
- (
- "<circle r='1'/>",
- "M-1,0 A1,1 0 1 1 1,0 A1,1 0 1 1 -1,0",
- None
- ),
+ ("<circle r='1'/>", "M-1,0 A1,1 0 1 1 1,0 A1,1 0 1 1 -1,0", None),
# circle
(
"<circle cx='600' cy='200' r='100'/>",
"M500,200 A100,100 0 1 1 700,200 A100,100 0 1 1 500,200",
- None
+ None,
),
# circle, decimal positioning
(
"<circle cx='12' cy='6.5' r='1.5'></circle>",
"M10.5,6.5 A1.5,1.5 0 1 1 13.5,6.5 A1.5,1.5 0 1 1 10.5,6.5",
- None
+ None,
),
# circle, with transform
(
'<circle transform="matrix(0.9871 -0.1602 0.1602 0.9871 -7.525 8.6516)" cx="49.9" cy="51" r="14.3"/>',
- 'M35.6,51 A14.3,14.3 0 1 1 64.2,51 A14.3,14.3 0 1 1 35.6,51',
- (0.9871, -0.1602, 0.1602, 0.9871, -7.525, 8.6516)
+ "M35.6,51 A14.3,14.3 0 1 1 64.2,51 A14.3,14.3 0 1 1 35.6,51",
+ (0.9871, -0.1602, 0.1602, 0.9871, -7.525, 8.6516),
),
# ellipse
(
'<ellipse cx="100" cy="50" rx="100" ry="50"/>',
- 'M0,50 A100,50 0 1 1 200,50 A100,50 0 1 1 0,50',
- None
+ "M0,50 A100,50 0 1 1 200,50 A100,50 0 1 1 0,50",
+ None,
),
# ellipse, decimal positioning
(
'<ellipse cx="100.5" cy="50" rx="10" ry="50.5"/>',
- 'M90.5,50 A10,50.5 0 1 1 110.5,50 A10,50.5 0 1 1 90.5,50',
- None
+ "M90.5,50 A10,50.5 0 1 1 110.5,50 A10,50.5 0 1 1 90.5,50",
+ None,
),
# ellipse, with transform
(
'<ellipse transform="matrix(0.9557 -0.2945 0.2945 0.9557 -14.7694 20.1454)" cx="59.5" cy="59.1" rx="30.9" ry="11.9"/>',
- 'M28.6,59.1 A30.9,11.9 0 1 1 90.4,59.1 A30.9,11.9 0 1 1 28.6,59.1',
- (0.9557, -0.2945, 0.2945, 0.9557, -14.7694, 20.1454)
+ "M28.6,59.1 A30.9,11.9 0 1 1 90.4,59.1 A30.9,11.9 0 1 1 28.6,59.1",
+ (0.9557, -0.2945, 0.2945, 0.9557, -14.7694, 20.1454),
),
- ]
+ ],
)
def test_el_to_path(svg_xml, expected_path, expected_transform):
pb = shapes.PathBuilder()
diff --git a/Tests/t1Lib/t1Lib_test.py b/Tests/t1Lib/t1Lib_test.py
index 3e639a58..bfc186da 100644
--- a/Tests/t1Lib/t1Lib_test.py
+++ b/Tests/t1Lib/t1Lib_test.py
@@ -8,194 +8,191 @@ import random
CWD = os.path.abspath(os.path.dirname(__file__))
-DATADIR = os.path.join(CWD, 'data')
+DATADIR = os.path.join(CWD, "data")
# I used `tx` to convert PFA to LWFN (stored in the data fork)
-LWFN = os.path.join(DATADIR, 'TestT1-Regular.lwfn')
-PFA = os.path.join(DATADIR, 'TestT1-Regular.pfa')
-PFB = os.path.join(DATADIR, 'TestT1-Regular.pfb')
-WEIRD_ZEROS = os.path.join(DATADIR, 'TestT1-weird-zeros.pfa')
+LWFN = os.path.join(DATADIR, "TestT1-Regular.lwfn")
+PFA = os.path.join(DATADIR, "TestT1-Regular.pfa")
+PFB = os.path.join(DATADIR, "TestT1-Regular.pfb")
+WEIRD_ZEROS = os.path.join(DATADIR, "TestT1-weird-zeros.pfa")
# ellipsis is hinted with 55 131 296 131 537 131 vstem3 0 122 hstem
-ELLIPSIS_HINTED = os.path.join(DATADIR, 'TestT1-ellipsis-hinted.pfa')
+ELLIPSIS_HINTED = os.path.join(DATADIR, "TestT1-ellipsis-hinted.pfa")
class FindEncryptedChunksTest(unittest.TestCase):
+ def test_findEncryptedChunks(self):
+ with open(PFA, "rb") as f:
+ data = f.read()
+ chunks = t1Lib.findEncryptedChunks(data)
+ self.assertEqual(len(chunks), 3)
+ self.assertFalse(chunks[0][0])
+ # the second chunk is encrypted
+ self.assertTrue(chunks[1][0])
+ self.assertFalse(chunks[2][0])
- def test_findEncryptedChunks(self):
- with open(PFA, "rb") as f:
- data = f.read()
- chunks = t1Lib.findEncryptedChunks(data)
- self.assertEqual(len(chunks), 3)
- self.assertFalse(chunks[0][0])
- # the second chunk is encrypted
- self.assertTrue(chunks[1][0])
- self.assertFalse(chunks[2][0])
+ def test_findEncryptedChunks_weird_zeros(self):
+ with open(WEIRD_ZEROS, "rb") as f:
+ data = f.read()
- def test_findEncryptedChunks_weird_zeros(self):
- with open(WEIRD_ZEROS, 'rb') as f:
- data = f.read()
-
- # Just assert that this doesn't raise any exception for not finding the
- # end of eexec
- t1Lib.findEncryptedChunks(data)
+ # Just assert that this doesn't raise any exception for not finding the
+ # end of eexec
+ t1Lib.findEncryptedChunks(data)
class DecryptType1Test(unittest.TestCase):
-
- def test_decryptType1(self):
- with open(PFA, "rb") as f:
- data = f.read()
- decrypted = t1Lib.decryptType1(data)
- self.assertNotEqual(decrypted, data)
+ def test_decryptType1(self):
+ with open(PFA, "rb") as f:
+ data = f.read()
+ decrypted = t1Lib.decryptType1(data)
+ self.assertNotEqual(decrypted, data)
class ReadWriteTest(unittest.TestCase):
-
- def test_read_pfa_write_pfb(self):
- font = t1Lib.T1Font(PFA)
- data = self.write(font, 'PFB')
- self.assertEqual(font.getData(), data)
-
- def test_read_and_parse_pfa_write_pfb(self):
- font = t1Lib.T1Font(PFA)
- font.parse()
- saved_font = self.write(font, 'PFB', dohex=False, doparse=True)
- self.assertTrue(same_dicts(font.font, saved_font))
-
- def test_read_pfb_write_pfa(self):
- font = t1Lib.T1Font(PFB)
- # 'OTHER' == 'PFA'
- data = self.write(font, 'OTHER', dohex=True)
- self.assertEqual(font.getData(), data)
-
- def test_read_and_parse_pfb_write_pfa(self):
- font = t1Lib.T1Font(PFB)
- font.parse()
- # 'OTHER' == 'PFA'
- saved_font = self.write(font, 'OTHER', dohex=True, doparse=True)
- self.assertTrue(same_dicts(font.font, saved_font))
-
- def test_read_with_path(self):
- import pathlib
- font = t1Lib.T1Font(pathlib.Path(PFB))
-
- @staticmethod
- def write(font, outtype, dohex=False, doparse=False):
- temp = os.path.join(DATADIR, 'temp.' + outtype.lower())
- try:
- font.saveAs(temp, outtype, dohex=dohex)
- newfont = t1Lib.T1Font(temp)
- if doparse:
- newfont.parse()
- data = newfont.font
- else:
- data = newfont.getData()
- finally:
- if os.path.exists(temp):
- os.remove(temp)
- return data
+ def test_read_pfa_write_pfb(self):
+ font = t1Lib.T1Font(PFA)
+ data = self.write(font, "PFB")
+ self.assertEqual(font.getData(), data)
+
+ def test_read_and_parse_pfa_write_pfb(self):
+ font = t1Lib.T1Font(PFA)
+ font.parse()
+ saved_font = self.write(font, "PFB", dohex=False, doparse=True)
+ self.assertTrue(same_dicts(font.font, saved_font))
+
+ def test_read_pfb_write_pfa(self):
+ font = t1Lib.T1Font(PFB)
+ # 'OTHER' == 'PFA'
+ data = self.write(font, "OTHER", dohex=True)
+ self.assertEqual(font.getData(), data)
+
+ def test_read_and_parse_pfb_write_pfa(self):
+ font = t1Lib.T1Font(PFB)
+ font.parse()
+ # 'OTHER' == 'PFA'
+ saved_font = self.write(font, "OTHER", dohex=True, doparse=True)
+ self.assertTrue(same_dicts(font.font, saved_font))
+
+ def test_read_with_path(self):
+ import pathlib
+
+ font = t1Lib.T1Font(pathlib.Path(PFB))
+
+ @staticmethod
+ def write(font, outtype, dohex=False, doparse=False):
+ temp = os.path.join(DATADIR, "temp." + outtype.lower())
+ try:
+ font.saveAs(temp, outtype, dohex=dohex)
+ newfont = t1Lib.T1Font(temp)
+ if doparse:
+ newfont.parse()
+ data = newfont.font
+ else:
+ data = newfont.getData()
+ finally:
+ if os.path.exists(temp):
+ os.remove(temp)
+ return data
class T1FontTest(unittest.TestCase):
-
- def test_parse_lwfn(self):
- # the extended attrs are lost on git so we can't auto-detect 'LWFN'
- font = t1Lib.T1Font(LWFN, kind="LWFN")
- font.parse()
- self.assertEqual(font['FontName'], 'TestT1-Regular')
- self.assertTrue('Subrs' in font['Private'])
-
- def test_parse_pfa(self):
- font = t1Lib.T1Font(PFA)
- font.parse()
- self.assertEqual(font['FontName'], 'TestT1-Regular')
- self.assertTrue('Subrs' in font['Private'])
-
- def test_parse_pfb(self):
- font = t1Lib.T1Font(PFB)
- font.parse()
- self.assertEqual(font['FontName'], 'TestT1-Regular')
- self.assertTrue('Subrs' in font['Private'])
-
- def test_getGlyphSet(self):
- font = t1Lib.T1Font(PFA)
- glyphs = font.getGlyphSet()
- i = random.randrange(len(glyphs))
- aglyph = list(glyphs.values())[i]
- self.assertTrue(hasattr(aglyph, 'draw'))
- self.assertFalse(hasattr(aglyph, 'width'))
- aglyph.draw(NullPen())
- self.assertTrue(hasattr(aglyph, 'width'))
+ def test_parse_lwfn(self):
+ # the extended attrs are lost on git so we can't auto-detect 'LWFN'
+ font = t1Lib.T1Font(LWFN, kind="LWFN")
+ font.parse()
+ self.assertEqual(font["FontName"], "TestT1-Regular")
+ self.assertTrue("Subrs" in font["Private"])
+
+ def test_parse_pfa(self):
+ font = t1Lib.T1Font(PFA)
+ font.parse()
+ self.assertEqual(font["FontName"], "TestT1-Regular")
+ self.assertTrue("Subrs" in font["Private"])
+
+ def test_parse_pfb(self):
+ font = t1Lib.T1Font(PFB)
+ font.parse()
+ self.assertEqual(font["FontName"], "TestT1-Regular")
+ self.assertTrue("Subrs" in font["Private"])
+
+ def test_getGlyphSet(self):
+ font = t1Lib.T1Font(PFA)
+ glyphs = font.getGlyphSet()
+ i = random.randrange(len(glyphs))
+ aglyph = list(glyphs.values())[i]
+ self.assertTrue(hasattr(aglyph, "draw"))
+ self.assertFalse(hasattr(aglyph, "width"))
+ aglyph.draw(NullPen())
+ self.assertTrue(hasattr(aglyph, "width"))
class EditTest(unittest.TestCase):
-
- def test_edit_pfa(self):
- font = t1Lib.T1Font(PFA)
- ellipsis = font.getGlyphSet()["ellipsis"]
- ellipsis.decompile()
- program = []
- for v in ellipsis.program:
- try:
- program.append(int(v))
- except:
- program.append(v)
- if v == 'hsbw':
- hints = [55, 131, 296, 131, 537, 131, 'vstem3', 0, 122, 'hstem']
- program.extend(hints)
- ellipsis.program = program
- # 'OTHER' == 'PFA'
- saved_font = self.write(font, 'OTHER', dohex=True, doparse=True)
- hinted_font = t1Lib.T1Font(ELLIPSIS_HINTED)
- hinted_font.parse()
- self.assertTrue(same_dicts(hinted_font.font, saved_font))
-
- @staticmethod
- def write(font, outtype, dohex=False, doparse=False):
- temp = os.path.join(DATADIR, 'temp.' + outtype.lower())
- try:
- font.saveAs(temp, outtype, dohex=dohex)
- newfont = t1Lib.T1Font(temp)
- if doparse:
- newfont.parse()
- data = newfont.font
- else:
- data = newfont.getData()
- finally:
- if os.path.exists(temp):
- os.remove(temp)
- return data
+ def test_edit_pfa(self):
+ font = t1Lib.T1Font(PFA)
+ ellipsis = font.getGlyphSet()["ellipsis"]
+ ellipsis.decompile()
+ program = []
+ for v in ellipsis.program:
+ try:
+ program.append(int(v))
+ except:
+ program.append(v)
+ if v == "hsbw":
+ hints = [55, 131, 296, 131, 537, 131, "vstem3", 0, 122, "hstem"]
+ program.extend(hints)
+ ellipsis.program = program
+ # 'OTHER' == 'PFA'
+ saved_font = self.write(font, "OTHER", dohex=True, doparse=True)
+ hinted_font = t1Lib.T1Font(ELLIPSIS_HINTED)
+ hinted_font.parse()
+ self.assertTrue(same_dicts(hinted_font.font, saved_font))
+
+ @staticmethod
+ def write(font, outtype, dohex=False, doparse=False):
+ temp = os.path.join(DATADIR, "temp." + outtype.lower())
+ try:
+ font.saveAs(temp, outtype, dohex=dohex)
+ newfont = t1Lib.T1Font(temp)
+ if doparse:
+ newfont.parse()
+ data = newfont.font
+ else:
+ data = newfont.getData()
+ finally:
+ if os.path.exists(temp):
+ os.remove(temp)
+ return data
def same_dicts(dict1, dict2):
- if dict1.keys() != dict2.keys():
- return False
- for key, value in dict1.items():
- if isinstance(value, dict):
- if not same_dicts(value, dict2[key]):
- return False
- elif isinstance(value, list):
- if len(value) != len(dict2[key]):
- return False
- for elem1, elem2 in zip(value, dict2[key]):
- if isinstance(elem1, T1CharString):
- elem1.compile()
- elem2.compile()
- if elem1.bytecode != elem2.bytecode:
- return False
- else:
- if elem1 != elem2:
- return False
- elif isinstance(value, T1CharString):
- value.compile()
- dict2[key].compile()
- if value.bytecode != dict2[key].bytecode:
- return False
- else:
- if value != dict2[key]:
- return False
- return True
-
-
-if __name__ == '__main__':
- import sys
- sys.exit(unittest.main())
+ if dict1.keys() != dict2.keys():
+ return False
+ for key, value in dict1.items():
+ if isinstance(value, dict):
+ if not same_dicts(value, dict2[key]):
+ return False
+ elif isinstance(value, list):
+ if len(value) != len(dict2[key]):
+ return False
+ for elem1, elem2 in zip(value, dict2[key]):
+ if isinstance(elem1, T1CharString):
+ elem1.compile()
+ elem2.compile()
+ if elem1.bytecode != elem2.bytecode:
+ return False
+ else:
+ if elem1 != elem2:
+ return False
+ elif isinstance(value, T1CharString):
+ value.compile()
+ dict2[key].compile()
+ if value.bytecode != dict2[key].bytecode:
+ return False
+ else:
+ if value != dict2[key]:
+ return False
+ return True
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/data/I-512upem.ttx b/Tests/ttLib/data/I-512upem.ttx
index 34795b1f..400685e3 100644
--- a/Tests/ttLib/data/I-512upem.ttx
+++ b/Tests/ttLib/data/I-512upem.ttx
@@ -2277,7 +2277,7 @@
</VarData>
<VarData index="1">
<!-- ItemCount=1 -->
- <NumShorts value="7"/>
+ <NumShorts value="0"/>
<!-- VarRegionCount=25 -->
<VarRegionIndex index="0" value="1"/>
<VarRegionIndex index="1" value="5"/>
@@ -2668,6 +2668,7 @@
</STAT>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/ttLib/data/I.otf b/Tests/ttLib/data/I.otf
new file mode 100644
index 00000000..41c65347
--- /dev/null
+++ b/Tests/ttLib/data/I.otf
Binary files differ
diff --git a/Tests/ttLib/data/TestOTF-Regular.otx b/Tests/ttLib/data/TestOTF-Regular.otx
index 92e0b2f6..4e459158 100644
--- a/Tests/ttLib/data/TestOTF-Regular.otx
+++ b/Tests/ttLib/data/TestOTF-Regular.otx
@@ -148,7 +148,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@@ -190,7 +190,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
</name>
diff --git a/Tests/ttLib/data/TestTTF-Regular.ttx b/Tests/ttLib/data/TestTTF-Regular.ttx
index 1f1dd2b4..d18be46f 100644
--- a/Tests/ttLib/data/TestTTF-Regular.ttx
+++ b/Tests/ttLib/data/TestTTF-Regular.ttx
@@ -468,7 +468,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@@ -510,7 +510,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
</name>
diff --git a/Tests/ttLib/data/TestTTF_normalizeLocation.ttx b/Tests/ttLib/data/TestTTF_normalizeLocation.ttx
new file mode 100644
index 00000000..0cb99593
--- /dev/null
+++ b/Tests/ttLib/data/TestTTF_normalizeLocation.ttx
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.36">
+
+ <avar>
+ <version major="1" minor="0"/>
+ <segment axis="wght">
+ <mapping from="-1.0" to="-1.0"/>
+ <mapping from="-0.5" to="-0.75"/>
+ <mapping from="0.0" to="0.0"/>
+ <mapping from="0.5" to="0.75"/>
+ <mapping from="1.0" to="1.0"/>
+ </segment>
+ </avar>
+
+ <fvar>
+
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>100.0</MinValue>
+ <DefaultValue>400.0</DefaultValue>
+ <MaxValue>700.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+
+ </fvar>
+
+</ttFont>
diff --git a/Tests/ttLib/data/bogus_post_format_1.ttf b/Tests/ttLib/data/bogus_post_format_1.ttf
new file mode 100644
index 00000000..62b80213
--- /dev/null
+++ b/Tests/ttLib/data/bogus_post_format_1.ttf
Binary files differ
diff --git a/Tests/ttLib/data/dot-cubic.ttf b/Tests/ttLib/data/dot-cubic.ttf
new file mode 100644
index 00000000..5adb11cc
--- /dev/null
+++ b/Tests/ttLib/data/dot-cubic.ttf
Binary files differ
diff --git a/Tests/ttLib/data/issue2824.ttf b/Tests/ttLib/data/issue2824.ttf
new file mode 100644
index 00000000..de110958
--- /dev/null
+++ b/Tests/ttLib/data/issue2824.ttf
Binary files differ
diff --git a/Tests/ttLib/data/varc-6868.ttf b/Tests/ttLib/data/varc-6868.ttf
new file mode 100644
index 00000000..aa55df21
--- /dev/null
+++ b/Tests/ttLib/data/varc-6868.ttf
Binary files differ
diff --git a/Tests/ttLib/data/varc-ac00-ac01-500upem.ttx b/Tests/ttLib/data/varc-ac00-ac01-500upem.ttx
new file mode 100644
index 00000000..db32c06e
--- /dev/null
+++ b/Tests/ttLib/data/varc-ac00-ac01-500upem.ttx
@@ -0,0 +1,2055 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.38">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="uniAC00"/>
+ <GlyphID id="2" name="uniAC01"/>
+ <GlyphID id="3" name="glyph00003"/>
+ <GlyphID id="4" name="glyph00004"/>
+ <GlyphID id="5" name="glyph00005"/>
+ </GlyphOrder>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="576"/>
+ <descent value="-143"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="0"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="0"/>
+ <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="6"/>
+ <maxPoints value="0"/>
+ <maxContours value="0"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="2"/>
+ <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="3"/>
+ <xAvgCharWidth value="483"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000100"/>
+ <ySubscriptXSize value="0"/>
+ <ySubscriptYSize value="0"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="0"/>
+ <ySuperscriptXSize value="0"/>
+ <ySuperscriptYSize value="0"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="0"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="0"/>
+ <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 00000000"/>
+ <ulUnicodeRange2 value="00000001 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="????"/>
+ <fsSelection value="00000000 00000000"/>
+ <usFirstCharIndex value="44032"/>
+ <usLastCharIndex value="44033"/>
+ <sTypoAscender value="440"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="576"/>
+ <usWinDescent value="143"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="0"/>
+ <sCapHeight value="0"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="0"/>
+ <mtx name="glyph00003" width="483" lsb="-207"/>
+ <mtx name="glyph00004" width="483" lsb="-155"/>
+ <mtx name="glyph00005" width="483" lsb="-258"/>
+ <mtx name="uniAC00" width="483" lsb="0"/>
+ <mtx name="uniAC01" width="483" lsb="0"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0xac00" name="uniAC00"/><!-- HANGUL SYLLABLE GA -->
+ <map code="0xac01" name="uniAC01"/><!-- HANGUL SYLLABLE GAG -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0xac00" name="uniAC00"/><!-- HANGUL SYLLABLE GA -->
+ <map code="0xac01" name="uniAC01"/><!-- HANGUL SYLLABLE GAG -->
+ </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="glyph00003" xMin="-207" yMin="-76" xMax="34" yMax="7">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="-15" on="1"/>
+ <pt x="-8" y="-15" on="0"/>
+ <pt x="-38" y="-15" on="0"/>
+ <pt x="-74" y="-15" on="0"/>
+ <pt x="-108" y="-16" on="0"/>
+ <pt x="-133" y="-17" on="0"/>
+ <pt x="-139" y="-17" on="1"/>
+ <pt x="-143" y="-17" on="0"/>
+ <pt x="-155" y="-19" on="0"/>
+ <pt x="-168" y="-22" on="0"/>
+ <pt x="-172" y="-24" on="1"/>
+ <pt x="-181" y="-21" on="0"/>
+ <pt x="-198" y="-13" on="0"/>
+ <pt x="-205" y="-8" on="1"/>
+ <pt x="-206" y="-7" on="0"/>
+ <pt x="-207" y="-5" on="0"/>
+ <pt x="-206" y="-4" on="1"/>
+ <pt x="-204" y="1" on="1"/>
+ <pt x="-195" y="-1" on="0"/>
+ <pt x="-174" y="-3" on="0"/>
+ <pt x="-166" y="-3" on="1"/>
+ <pt x="-161" y="-3" on="0"/>
+ <pt x="-136" y="-3" on="0"/>
+ <pt x="-103" y="-2" on="0"/>
+ <pt x="-66" y="-2" on="0"/>
+ <pt x="-31" y="-1" on="0"/>
+ <pt x="-5" y="0" on="0"/>
+ <pt x="0" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="27" y="-18" on="1"/>
+ <pt x="0" y="-15" on="1"/>
+ <pt x="-2" y="0" on="1"/>
+ <pt x="10" y="6" on="1"/>
+ <pt x="12" y="7" on="0"/>
+ <pt x="15" y="7" on="0"/>
+ <pt x="16" y="6" on="1"/>
+ <pt x="18" y="6" on="0"/>
+ <pt x="23" y="2" on="0"/>
+ <pt x="28" y="-1" on="0"/>
+ <pt x="29" y="-2" on="1"/>
+ <pt x="30" y="-3" on="0"/>
+ <pt x="31" y="-5" on="0"/>
+ <pt x="30" y="-7" on="1"/>
+ <pt x="27" y="-18" on="1"/>
+ </contour>
+ <contour>
+ <pt x="29" y="-76" on="1"/>
+ <pt x="22" y="-76" on="1"/>
+ <pt x="19" y="-76" on="0"/>
+ <pt x="20" y="-72" on="1"/>
+ <pt x="15" y="-63" on="0"/>
+ <pt x="7" y="-41" on="0"/>
+ <pt x="1" y="-21" on="0"/>
+ <pt x="0" y="-15" on="1"/>
+ <pt x="15" y="-11" on="1"/>
+ <pt x="27" y="-18" on="1"/>
+ <pt x="27" y="-26" on="0"/>
+ <pt x="28" y="-45" on="0"/>
+ <pt x="31" y="-65" on="0"/>
+ <pt x="34" y="-73" on="1"/>
+ <pt x="33" y="-75" on="0"/>
+ <pt x="30" y="-76" on="0"/>
+ <pt x="29" y="-76" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="glyph00004" xMin="-155" yMin="-48" xMax="14" yMax="177">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="-100" y="-1" on="1"/>
+ <pt x="-100" y="16" on="1"/>
+ <pt x="-55" y="18" on="1"/>
+ <pt x="-38" y="27" on="1"/>
+ <pt x="-26" y="28" on="0"/>
+ <pt x="1" y="26" on="0"/>
+ <pt x="8" y="24" on="1"/>
+ <pt x="14" y="23" on="0"/>
+ <pt x="14" y="17" on="1"/>
+ <pt x="14" y="11" on="0"/>
+ <pt x="11" y="3" on="0"/>
+ <pt x="8" y="2" on="1"/>
+ <pt x="7" y="1" on="0"/>
+ <pt x="2" y="0" on="0"/>
+ <pt x="0" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="-91" y="-48" on="1"/>
+ <pt x="-108" y="-48" on="1"/>
+ <pt x="-108" y="-42" on="0"/>
+ <pt x="-109" y="-26" on="0"/>
+ <pt x="-109" y="-8" on="0"/>
+ <pt x="-109" y="9" on="0"/>
+ <pt x="-109" y="16" on="1"/>
+ <pt x="-109" y="139" on="1"/>
+ <pt x="-117" y="147" on="0"/>
+ <pt x="-141" y="160" on="0"/>
+ <pt x="-155" y="165" on="1"/>
+ <pt x="-152" y="175" on="1"/>
+ <pt x="-152" y="176" on="0"/>
+ <pt x="-150" y="177" on="0"/>
+ <pt x="-147" y="177" on="1"/>
+ <pt x="-138" y="177" on="0"/>
+ <pt x="-115" y="173" on="0"/>
+ <pt x="-93" y="168" on="0"/>
+ <pt x="-86" y="165" on="1"/>
+ <pt x="-81" y="163" on="0"/>
+ <pt x="-81" y="156" on="1"/>
+ <pt x="-82" y="130" on="1"/>
+ <pt x="-82" y="27" on="1"/>
+ <pt x="-82" y="15" on="0"/>
+ <pt x="-83" y="-7" on="0"/>
+ <pt x="-85" y="-26" on="0"/>
+ <pt x="-89" y="-42" on="0"/>
+ <pt x="-91" y="-48" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="glyph00005" xMin="-258" yMin="-122" xMax="32" yMax="7">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="-18" on="1"/>
+ <pt x="-11" y="-18" on="0"/>
+ <pt x="-49" y="-18" on="0"/>
+ <pt x="-94" y="-18" on="0"/>
+ <pt x="-138" y="-19" on="0"/>
+ <pt x="-171" y="-20" on="0"/>
+ <pt x="-178" y="-21" on="1"/>
+ <pt x="-186" y="-21" on="0"/>
+ <pt x="-210" y="-25" on="0"/>
+ <pt x="-218" y="-28" on="1"/>
+ <pt x="-228" y="-25" on="0"/>
+ <pt x="-248" y="-15" on="0"/>
+ <pt x="-256" y="-9" on="1"/>
+ <pt x="-258" y="-7" on="0"/>
+ <pt x="-257" y="-5" on="1"/>
+ <pt x="-254" y="0" on="1"/>
+ <pt x="-245" y="-2" on="0"/>
+ <pt x="-221" y="-4" on="0"/>
+ <pt x="-212" y="-4" on="1"/>
+ <pt x="-206" y="-4" on="0"/>
+ <pt x="-174" y="-4" on="0"/>
+ <pt x="-131" y="-3" on="0"/>
+ <pt x="-84" y="-2" on="0"/>
+ <pt x="-40" y="-1" on="0"/>
+ <pt x="-7" y="0" on="0"/>
+ <pt x="0" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="25" y="-21" on="1"/>
+ <pt x="-1" y="-18" on="1"/>
+ <pt x="-3" y="0" on="1"/>
+ <pt x="9" y="6" on="1"/>
+ <pt x="13" y="7" on="0"/>
+ <pt x="16" y="6" on="1"/>
+ <pt x="18" y="4" on="0"/>
+ <pt x="28" y="-1" on="0"/>
+ <pt x="30" y="-3" on="1"/>
+ <pt x="32" y="-5" on="0"/>
+ <pt x="30" y="-9" on="1"/>
+ <pt x="25" y="-21" on="1"/>
+ </contour>
+ <contour>
+ <pt x="17" y="-122" on="1"/>
+ <pt x="1" y="-122" on="1"/>
+ <pt x="1" y="-113" on="0"/>
+ <pt x="0" y="-92" on="0"/>
+ <pt x="-1" y="-70" on="0"/>
+ <pt x="-1" y="-62" on="1"/>
+ <pt x="-1" y="-18" on="1"/>
+ <pt x="25" y="-18" on="1"/>
+ <pt x="24" y="-55" on="1"/>
+ <pt x="24" y="-64" on="0"/>
+ <pt x="22" y="-89" on="0"/>
+ <pt x="19" y="-113" on="0"/>
+ <pt x="17" y="-122" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="uniAC00" xMin="23" yMin="-53" xMax="480" yMax="426">
+ <varComponent glyphName="glyph00003" flags="0x18" translateX="230.0" translateY="338.0">
+ <location>
+ <axis tag="0000" value="0.8466"/>
+ <axis tag="0001" value="0.98944"/>
+ <axis tag="0002" value="0.47284"/>
+ <axis tag="0003" value="0.44653"/>
+ </location>
+ </varComponent>
+ <varComponent glyphName="glyph00004" flags="0x18" translateX="466.0" translateY="191.0">
+ <location>
+ <axis tag="0000" value="0.9336"/>
+ <axis tag="0001" value="0.916"/>
+ <axis tag="0002" value="0.5232"/>
+ <axis tag="0003" value="0.32806"/>
+ <axis tag="0004" value="0.8509"/>
+ </location>
+ </varComponent>
+ </TTGlyph>
+
+ <TTGlyph name="uniAC01" xMin="17" yMin="-58" xMax="480" yMax="426">
+ <varComponent glyphName="glyph00003" flags="0x18" translateX="227.0" translateY="366.0">
+ <location>
+ <axis tag="0000" value="0.7492"/>
+ <axis tag="0001" value="0.9945"/>
+ <axis tag="0002" value="0.28485"/>
+ <axis tag="0003" value="0.297"/>
+ </location>
+ </varComponent>
+ <varComponent glyphName="glyph00004" flags="0x18" translateX="466.0" translateY="238.0">
+ <location>
+ <axis tag="0000" value="0.309"/>
+ <axis tag="0001" value="0.15155"/>
+ <axis tag="0002" value="0.2873"/>
+ <axis tag="0003" value="0.35034"/>
+ <axis tag="0004" value="0.7843"/>
+ </location>
+ </varComponent>
+ <varComponent glyphName="glyph00005" flags="0x18" translateX="346.0" translateY="120.0">
+ <location>
+ <axis tag="0000" value="0.3236"/>
+ <axis tag="0001" value="1.0"/>
+ </location>
+ </varComponent>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ butchered-hangul-serif
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ smarties-variable
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ 0000
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ 0001
+ </namerecord>
+ <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
+ 0002
+ </namerecord>
+ <namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
+ 0003
+ </namerecord>
+ <namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
+ 0004
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ 0005
+ </namerecord>
+ <namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
+ 0006
+ </namerecord>
+ <namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
+ 0007
+ </namerecord>
+ <namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <isFixedPitch value="0"/>
+ <minMemType42 value="0"/>
+ <maxMemType42 value="0"/>
+ <minMemType1 value="0"/>
+ <maxMemType1 value="0"/>
+ </post>
+
+ <avar>
+ <version major="1" minor="0"/>
+ <segment axis="0000">
+ </segment>
+ <segment axis="0001">
+ </segment>
+ <segment axis="0002">
+ </segment>
+ <segment axis="0003">
+ </segment>
+ <segment axis="0004">
+ </segment>
+ <segment axis="0005">
+ </segment>
+ <segment axis="0006">
+ </segment>
+ <segment axis="0007">
+ </segment>
+ <segment axis="wght">
+ <mapping from="-1.0" to="-1.0"/>
+ <mapping from="0.0" to="0.0"/>
+ <mapping from="0.1429" to="0.095"/>
+ <mapping from="0.2857" to="0.21"/>
+ <mapping from="0.4286" to="0.36"/>
+ <mapping from="0.5714" to="0.51"/>
+ <mapping from="0.7143" to="0.73"/>
+ <mapping from="1.0" to="1.0"/>
+ </segment>
+ </avar>
+
+ <fvar>
+
+ <!-- 0000 -->
+ <Axis>
+ <AxisTag>0000</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+
+ <!-- 0001 -->
+ <Axis>
+ <AxisTag>0001</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>257</AxisNameID>
+ </Axis>
+
+ <!-- 0002 -->
+ <Axis>
+ <AxisTag>0002</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>258</AxisNameID>
+ </Axis>
+
+ <!-- 0003 -->
+ <Axis>
+ <AxisTag>0003</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>259</AxisNameID>
+ </Axis>
+
+ <!-- 0004 -->
+ <Axis>
+ <AxisTag>0004</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>260</AxisNameID>
+ </Axis>
+
+ <!-- 0005 -->
+ <Axis>
+ <AxisTag>0005</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>261</AxisNameID>
+ </Axis>
+
+ <!-- 0006 -->
+ <Axis>
+ <AxisTag>0006</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>262</AxisNameID>
+ </Axis>
+
+ <!-- 0007 -->
+ <Axis>
+ <AxisTag>0007</AxisTag>
+ <Flags>0x1</Flags>
+ <MinValue>0.0</MinValue>
+ <DefaultValue>0.0</DefaultValue>
+ <MaxValue>1.0</MaxValue>
+ <AxisNameID>263</AxisNameID>
+ </Axis>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>200.0</MinValue>
+ <DefaultValue>200.0</DefaultValue>
+ <MaxValue>900.0</MaxValue>
+ <AxisNameID>264</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <gvar>
+ <version value="1"/>
+ <reserved value="0"/>
+ <glyphVariations glyph="glyph00003">
+ <tuple>
+ <coord axis="0000" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-11"/>
+ <delta pt="2" x="-5" y="-11"/>
+ <delta pt="3" x="-24" y="-11"/>
+ <delta pt="4" x="-46" y="-12"/>
+ <delta pt="5" x="-67" y="-12"/>
+ <delta pt="6" x="-84" y="-12"/>
+ <delta pt="7" x="-87" y="-13"/>
+ <delta pt="8" x="-90" y="-13"/>
+ <delta pt="9" x="-98" y="-14"/>
+ <delta pt="10" x="-107" y="-16"/>
+ <delta pt="11" x="-110" y="-17"/>
+ <delta pt="12" x="-116" y="-15"/>
+ <delta pt="13" x="-128" y="-9"/>
+ <delta pt="14" x="-133" y="-6"/>
+ <delta pt="15" x="-134" y="-5"/>
+ <delta pt="16" x="-134" y="-4"/>
+ <delta pt="17" x="-134" y="-3"/>
+ <delta pt="18" x="-132" y="0"/>
+ <delta pt="19" x="-126" y="-1"/>
+ <delta pt="20" x="-112" y="-2"/>
+ <delta pt="21" x="-106" y="-2"/>
+ <delta pt="22" x="-102" y="-2"/>
+ <delta pt="23" x="-87" y="-2"/>
+ <delta pt="24" x="-65" y="-2"/>
+ <delta pt="25" x="-42" y="-1"/>
+ <delta pt="26" x="-21" y="-1"/>
+ <delta pt="27" x="-4" y="0"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="20" y="-13"/>
+ <delta pt="30" x="0" y="-11"/>
+ <delta pt="31" x="-1" y="0"/>
+ <delta pt="32" x="9" y="5"/>
+ <delta pt="33" x="10" y="5"/>
+ <delta pt="34" x="12" y="5"/>
+ <delta pt="35" x="13" y="5"/>
+ <delta pt="36" x="15" y="4"/>
+ <delta pt="37" x="19" y="2"/>
+ <delta pt="38" x="23" y="0"/>
+ <delta pt="39" x="24" y="-1"/>
+ <delta pt="40" x="25" y="-2"/>
+ <delta pt="41" x="25" y="-3"/>
+ <delta pt="42" x="24" y="-4"/>
+ <delta pt="43" x="20" y="-13"/>
+ <delta pt="44" x="-64" y="-129"/>
+ <delta pt="45" x="-68" y="-127"/>
+ <delta pt="46" x="-70" y="-127"/>
+ <delta pt="47" x="-69" y="-125"/>
+ <delta pt="48" x="-53" y="-108"/>
+ <delta pt="49" x="-26" y="-69"/>
+ <delta pt="50" x="-5" y="-29"/>
+ <delta pt="51" x="0" y="-11"/>
+ <delta pt="52" x="11" y="-7"/>
+ <delta pt="53" x="20" y="-13"/>
+ <delta pt="54" x="15" y="-32"/>
+ <delta pt="55" x="-9" y="-74"/>
+ <delta pt="56" x="-43" y="-113"/>
+ <delta pt="57" x="-62" y="-128"/>
+ <delta pt="58" x="-62" y="-128"/>
+ <delta pt="59" x="-64" y="-129"/>
+ <delta pt="60" x="-64" y="-129"/>
+ <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="0001" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="5"/>
+ <delta pt="2" x="5" y="5"/>
+ <delta pt="3" x="22" y="5"/>
+ <delta pt="4" x="43" y="4"/>
+ <delta pt="5" x="62" y="5"/>
+ <delta pt="6" x="77" y="5"/>
+ <delta pt="7" x="80" y="5"/>
+ <delta pt="8" x="82" y="5"/>
+ <delta pt="9" x="87" y="5"/>
+ <delta pt="10" x="92" y="7"/>
+ <delta pt="11" x="93" y="7"/>
+ <delta pt="12" x="97" y="7"/>
+ <delta pt="13" x="102" y="4"/>
+ <delta pt="14" x="105" y="2"/>
+ <delta pt="15" x="105" y="2"/>
+ <delta pt="16" x="106" y="1"/>
+ <delta pt="17" x="105" y="1"/>
+ <delta pt="18" x="105" y="-1"/>
+ <delta pt="19" x="102" y="0"/>
+ <delta pt="20" x="94" y="2"/>
+ <delta pt="21" x="91" y="2"/>
+ <delta pt="22" x="89" y="2"/>
+ <delta pt="23" x="75" y="2"/>
+ <delta pt="24" x="56" y="1"/>
+ <delta pt="25" x="35" y="1"/>
+ <delta pt="26" x="16" y="1"/>
+ <delta pt="27" x="2" y="1"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="-6" y="6"/>
+ <delta pt="30" x="0" y="5"/>
+ <delta pt="31" x="1" y="0"/>
+ <delta pt="32" x="-1" y="-1"/>
+ <delta pt="33" x="-1" y="-1"/>
+ <delta pt="34" x="-2" y="-1"/>
+ <delta pt="35" x="-2" y="-1"/>
+ <delta pt="36" x="-3" y="-1"/>
+ <delta pt="37" x="-3" y="1"/>
+ <delta pt="38" x="-4" y="2"/>
+ <delta pt="39" x="-4" y="2"/>
+ <delta pt="40" x="-4" y="3"/>
+ <delta pt="41" x="-4" y="3"/>
+ <delta pt="42" x="-4" y="4"/>
+ <delta pt="43" x="-6" y="6"/>
+ <delta pt="44" x="-200" y="-57"/>
+ <delta pt="45" x="-195" y="-54"/>
+ <delta pt="46" x="-194" y="-52"/>
+ <delta pt="47" x="-192" y="-56"/>
+ <delta pt="48" x="-148" y="-49"/>
+ <delta pt="49" x="-73" y="-34"/>
+ <delta pt="50" x="-17" y="-12"/>
+ <delta pt="51" x="0" y="5"/>
+ <delta pt="52" x="-3" y="6"/>
+ <delta pt="53" x="-6" y="6"/>
+ <delta pt="54" x="-20" y="-11"/>
+ <delta pt="55" x="-78" y="-39"/>
+ <delta pt="56" x="-157" y="-57"/>
+ <delta pt="57" x="-202" y="-60"/>
+ <delta pt="58" x="-202" y="-59"/>
+ <delta pt="59" x="-201" y="-58"/>
+ <delta pt="60" x="-200" y="-57"/>
+ <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="0002" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-6"/>
+ <delta pt="2" x="2" y="-6"/>
+ <delta pt="3" x="8" y="-6"/>
+ <delta pt="4" x="15" y="-6"/>
+ <delta pt="5" x="22" y="-6"/>
+ <delta pt="6" x="27" y="-7"/>
+ <delta pt="7" x="28" y="-7"/>
+ <delta pt="8" x="28" y="-7"/>
+ <delta pt="9" x="27" y="-7"/>
+ <delta pt="10" x="26" y="-7"/>
+ <delta pt="11" x="25" y="-7"/>
+ <delta pt="12" x="25" y="-7"/>
+ <delta pt="13" x="22" y="-4"/>
+ <delta pt="14" x="22" y="-4"/>
+ <delta pt="15" x="22" y="-3"/>
+ <delta pt="16" x="23" y="-3"/>
+ <delta pt="17" x="23" y="-3"/>
+ <delta pt="18" x="24" y="-2"/>
+ <delta pt="19" x="24" y="-2"/>
+ <delta pt="20" x="25" y="-2"/>
+ <delta pt="21" x="26" y="-2"/>
+ <delta pt="22" x="25" y="-2"/>
+ <delta pt="23" x="21" y="-2"/>
+ <delta pt="24" x="15" y="-1"/>
+ <delta pt="25" x="8" y="-1"/>
+ <delta pt="26" x="3" y="-1"/>
+ <delta pt="27" x="0" y="0"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="7" y="-6"/>
+ <delta pt="30" x="-1" y="-6"/>
+ <delta pt="31" x="-2" y="0"/>
+ <delta pt="32" x="4" y="3"/>
+ <delta pt="33" x="5" y="3"/>
+ <delta pt="34" x="6" y="3"/>
+ <delta pt="35" x="6" y="3"/>
+ <delta pt="36" x="6" y="2"/>
+ <delta pt="37" x="6" y="2"/>
+ <delta pt="38" x="6" y="2"/>
+ <delta pt="39" x="6" y="1"/>
+ <delta pt="40" x="6" y="1"/>
+ <delta pt="41" x="7" y="1"/>
+ <delta pt="42" x="7" y="0"/>
+ <delta pt="43" x="7" y="-6"/>
+ <delta pt="44" x="41" y="-54"/>
+ <delta pt="45" x="38" y="-54"/>
+ <delta pt="46" x="37" y="-54"/>
+ <delta pt="47" x="37" y="-52"/>
+ <delta pt="48" x="30" y="-44"/>
+ <delta pt="49" x="16" y="-26"/>
+ <delta pt="50" x="5" y="-10"/>
+ <delta pt="51" x="-1" y="-6"/>
+ <delta pt="52" x="3" y="-5"/>
+ <delta pt="53" x="7" y="-6"/>
+ <delta pt="54" x="13" y="-10"/>
+ <delta pt="55" x="25" y="-26"/>
+ <delta pt="56" x="37" y="-44"/>
+ <delta pt="57" x="43" y="-52"/>
+ <delta pt="58" x="43" y="-52"/>
+ <delta pt="59" x="42" y="-53"/>
+ <delta pt="60" x="41" y="-54"/>
+ <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="0003" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="13"/>
+ <delta pt="2" x="0" y="13"/>
+ <delta pt="3" x="-1" y="13"/>
+ <delta pt="4" x="-3" y="13"/>
+ <delta pt="5" x="-4" y="14"/>
+ <delta pt="6" x="-6" y="15"/>
+ <delta pt="7" x="-7" y="15"/>
+ <delta pt="8" x="-5" y="15"/>
+ <delta pt="9" x="0" y="16"/>
+ <delta pt="10" x="6" y="17"/>
+ <delta pt="11" x="7" y="18"/>
+ <delta pt="12" x="11" y="17"/>
+ <delta pt="13" x="20" y="11"/>
+ <delta pt="14" x="24" y="8"/>
+ <delta pt="15" x="24" y="7"/>
+ <delta pt="16" x="24" y="7"/>
+ <delta pt="17" x="24" y="7"/>
+ <delta pt="18" x="21" y="2"/>
+ <delta pt="19" x="17" y="3"/>
+ <delta pt="20" x="8" y="3"/>
+ <delta pt="21" x="4" y="3"/>
+ <delta pt="22" x="3" y="3"/>
+ <delta pt="23" x="3" y="3"/>
+ <delta pt="24" x="4" y="3"/>
+ <delta pt="25" x="4" y="2"/>
+ <delta pt="26" x="4" y="2"/>
+ <delta pt="27" x="2" y="1"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="-19" y="13"/>
+ <delta pt="30" x="1" y="13"/>
+ <delta pt="31" x="2" y="1"/>
+ <delta pt="32" x="-9" y="-5"/>
+ <delta pt="33" x="-10" y="-6"/>
+ <delta pt="34" x="-13" y="-6"/>
+ <delta pt="35" x="-14" y="-5"/>
+ <delta pt="36" x="-15" y="-5"/>
+ <delta pt="37" x="-17" y="-3"/>
+ <delta pt="38" x="-18" y="-1"/>
+ <delta pt="39" x="-18" y="-1"/>
+ <delta pt="40" x="-18" y="0"/>
+ <delta pt="41" x="-19" y="2"/>
+ <delta pt="42" x="-19" y="3"/>
+ <delta pt="43" x="-19" y="13"/>
+ <delta pt="44" x="9" y="-22"/>
+ <delta pt="45" x="16" y="-22"/>
+ <delta pt="46" x="18" y="-23"/>
+ <delta pt="47" x="17" y="-26"/>
+ <delta pt="48" x="15" y="-19"/>
+ <delta pt="49" x="9" y="-5"/>
+ <delta pt="50" x="4" y="8"/>
+ <delta pt="51" x="1" y="13"/>
+ <delta pt="52" x="-10" y="9"/>
+ <delta pt="53" x="-19" y="13"/>
+ <delta pt="54" x="-16" y="11"/>
+ <delta pt="55" x="-8" y="-1"/>
+ <delta pt="56" x="1" y="-17"/>
+ <delta pt="57" x="5" y="-24"/>
+ <delta pt="58" x="6" y="-23"/>
+ <delta pt="59" x="8" y="-22"/>
+ <delta pt="60" x="9" y="-22"/>
+ <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"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-20"/>
+ <delta pt="2" x="1" y="-19"/>
+ <delta pt="3" x="8" y="-19"/>
+ <delta pt="4" x="19" y="-19"/>
+ <delta pt="5" x="29" y="-18"/>
+ <delta pt="6" x="36" y="-18"/>
+ <delta pt="7" x="36" y="-18"/>
+ <delta pt="8" x="35" y="-18"/>
+ <delta pt="9" x="32" y="-19"/>
+ <delta pt="10" x="29" y="-19"/>
+ <delta pt="11" x="27" y="-19"/>
+ <delta pt="12" x="24" y="-16"/>
+ <delta pt="13" x="17" y="-6"/>
+ <delta pt="14" x="16" y="-3"/>
+ <delta pt="15" x="15" y="-2"/>
+ <delta pt="16" x="15" y="-1"/>
+ <delta pt="17" x="15" y="1"/>
+ <delta pt="18" x="17" y="2"/>
+ <delta pt="19" x="21" y="2"/>
+ <delta pt="20" x="27" y="2"/>
+ <delta pt="21" x="30" y="2"/>
+ <delta pt="22" x="31" y="2"/>
+ <delta pt="23" x="27" y="2"/>
+ <delta pt="24" x="20" y="2"/>
+ <delta pt="25" x="11" y="1"/>
+ <delta pt="26" x="4" y="1"/>
+ <delta pt="27" x="0" y="1"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="30" y="-18"/>
+ <delta pt="30" x="-6" y="-19"/>
+ <delta pt="31" x="2" y="1"/>
+ <delta pt="32" x="14" y="4"/>
+ <delta pt="33" x="15" y="4"/>
+ <delta pt="34" x="17" y="4"/>
+ <delta pt="35" x="18" y="4"/>
+ <delta pt="36" x="20" y="3"/>
+ <delta pt="37" x="27" y="0"/>
+ <delta pt="38" x="34" y="-2"/>
+ <delta pt="39" x="36" y="-3"/>
+ <delta pt="40" x="36" y="-3"/>
+ <delta pt="41" x="36" y="-5"/>
+ <delta pt="42" x="36" y="-5"/>
+ <delta pt="43" x="30" y="-18"/>
+ <delta pt="44" x="10" y="-2"/>
+ <delta pt="45" x="-7" y="-1"/>
+ <delta pt="46" x="-10" y="-1"/>
+ <delta pt="47" x="-10" y="4"/>
+ <delta pt="48" x="-8" y="-2"/>
+ <delta pt="49" x="-7" y="-11"/>
+ <delta pt="50" x="-6" y="-17"/>
+ <delta pt="51" x="-6" y="-19"/>
+ <delta pt="52" x="12" y="-13"/>
+ <delta pt="53" x="30" y="-17"/>
+ <delta pt="54" x="28" y="-15"/>
+ <delta pt="55" x="23" y="-10"/>
+ <delta pt="56" x="18" y="-4"/>
+ <delta pt="57" x="16" y="0"/>
+ <delta pt="58" x="14" y="-1"/>
+ <delta pt="59" x="12" y="-2"/>
+ <delta pt="60" x="10" y="-2"/>
+ <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="0000" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-14"/>
+ <delta pt="2" x="1" y="-14"/>
+ <delta pt="3" x="7" y="-14"/>
+ <delta pt="4" x="14" y="-13"/>
+ <delta pt="5" x="22" y="-14"/>
+ <delta pt="6" x="27" y="-14"/>
+ <delta pt="7" x="27" y="-13"/>
+ <delta pt="8" x="26" y="-14"/>
+ <delta pt="9" x="24" y="-13"/>
+ <delta pt="10" x="22" y="-14"/>
+ <delta pt="11" x="21" y="-14"/>
+ <delta pt="12" x="19" y="-12"/>
+ <delta pt="13" x="14" y="-6"/>
+ <delta pt="14" x="13" y="-3"/>
+ <delta pt="15" x="13" y="-2"/>
+ <delta pt="16" x="12" y="-1"/>
+ <delta pt="17" x="13" y="0"/>
+ <delta pt="18" x="14" y="1"/>
+ <delta pt="19" x="16" y="1"/>
+ <delta pt="20" x="21" y="1"/>
+ <delta pt="21" x="23" y="1"/>
+ <delta pt="22" x="23" y="1"/>
+ <delta pt="23" x="20" y="1"/>
+ <delta pt="24" x="14" y="1"/>
+ <delta pt="25" x="9" y="1"/>
+ <delta pt="26" x="4" y="0"/>
+ <delta pt="27" x="0" y="0"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="20" y="-11"/>
+ <delta pt="30" x="-9" y="-14"/>
+ <delta pt="31" x="1" y="0"/>
+ <delta pt="32" x="6" y="2"/>
+ <delta pt="33" x="7" y="3"/>
+ <delta pt="34" x="9" y="3"/>
+ <delta pt="35" x="9" y="2"/>
+ <delta pt="36" x="11" y="2"/>
+ <delta pt="37" x="16" y="-1"/>
+ <delta pt="38" x="21" y="-3"/>
+ <delta pt="39" x="22" y="-4"/>
+ <delta pt="40" x="22" y="-5"/>
+ <delta pt="41" x="23" y="-6"/>
+ <delta pt="42" x="22" y="-6"/>
+ <delta pt="43" x="20" y="-11"/>
+ <delta pt="44" x="15" y="-2"/>
+ <delta pt="45" x="7" y="1"/>
+ <delta pt="46" x="6" y="2"/>
+ <delta pt="47" x="7" y="4"/>
+ <delta pt="48" x="3" y="0"/>
+ <delta pt="49" x="-2" y="-6"/>
+ <delta pt="50" x="-8" y="-11"/>
+ <delta pt="51" x="-9" y="-14"/>
+ <delta pt="52" x="5" y="-9"/>
+ <delta pt="53" x="20" y="-11"/>
+ <delta pt="54" x="20" y="-12"/>
+ <delta pt="55" x="19" y="-9"/>
+ <delta pt="56" x="19" y="-4"/>
+ <delta pt="57" x="19" y="-1"/>
+ <delta pt="58" x="18" y="-2"/>
+ <delta pt="59" x="16" y="-2"/>
+ <delta pt="60" x="15" y="-2"/>
+ <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="0001" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="8"/>
+ <delta pt="2" x="1" y="8"/>
+ <delta pt="3" x="-1" y="8"/>
+ <delta pt="4" x="-5" y="9"/>
+ <delta pt="5" x="-9" y="8"/>
+ <delta pt="6" x="-11" y="9"/>
+ <delta pt="7" x="-11" y="9"/>
+ <delta pt="8" x="-10" y="9"/>
+ <delta pt="9" x="-8" y="10"/>
+ <delta pt="10" x="-6" y="9"/>
+ <delta pt="11" x="-5" y="9"/>
+ <delta pt="12" x="-4" y="8"/>
+ <delta pt="13" x="-1" y="4"/>
+ <delta pt="14" x="-1" y="3"/>
+ <delta pt="15" x="0" y="3"/>
+ <delta pt="16" x="-1" y="3"/>
+ <delta pt="17" x="0" y="2"/>
+ <delta pt="18" x="-1" y="0"/>
+ <delta pt="19" x="-3" y="0"/>
+ <delta pt="20" x="-6" y="0"/>
+ <delta pt="21" x="-7" y="0"/>
+ <delta pt="22" x="-8" y="0"/>
+ <delta pt="23" x="-7" y="0"/>
+ <delta pt="24" x="-4" y="0"/>
+ <delta pt="25" x="-1" y="0"/>
+ <delta pt="26" x="1" y="0"/>
+ <delta pt="27" x="2" y="0"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="-11" y="6"/>
+ <delta pt="30" x="-7" y="8"/>
+ <delta pt="31" x="0" y="1"/>
+ <delta pt="32" x="-8" y="-2"/>
+ <delta pt="33" x="-9" y="-3"/>
+ <delta pt="34" x="-8" y="-3"/>
+ <delta pt="35" x="-9" y="-2"/>
+ <delta pt="36" x="-9" y="-2"/>
+ <delta pt="37" x="-12" y="-2"/>
+ <delta pt="38" x="-15" y="-3"/>
+ <delta pt="39" x="-16" y="-3"/>
+ <delta pt="40" x="-16" y="-4"/>
+ <delta pt="41" x="-16" y="-3"/>
+ <delta pt="42" x="-16" y="-3"/>
+ <delta pt="43" x="-11" y="6"/>
+ <delta pt="44" x="7" y="1"/>
+ <delta pt="45" x="24" y="6"/>
+ <delta pt="46" x="27" y="8"/>
+ <delta pt="47" x="28" y="4"/>
+ <delta pt="48" x="19" y="8"/>
+ <delta pt="49" x="6" y="11"/>
+ <delta pt="50" x="-4" y="10"/>
+ <delta pt="51" x="-7" y="8"/>
+ <delta pt="52" x="-9" y="5"/>
+ <delta pt="53" x="-11" y="6"/>
+ <delta pt="54" x="-11" y="4"/>
+ <delta pt="55" x="-5" y="1"/>
+ <delta pt="56" x="3" y="0"/>
+ <delta pt="57" x="5" y="-1"/>
+ <delta pt="58" x="5" y="0"/>
+ <delta pt="59" x="5" y="1"/>
+ <delta pt="60" x="7" 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="0002" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-7"/>
+ <delta pt="2" x="3" y="-8"/>
+ <delta pt="3" x="9" y="-8"/>
+ <delta pt="4" x="14" y="-8"/>
+ <delta pt="5" x="19" y="-8"/>
+ <delta pt="6" x="25" y="-8"/>
+ <delta pt="7" x="26" y="-8"/>
+ <delta pt="8" x="24" y="-8"/>
+ <delta pt="9" x="22" y="-8"/>
+ <delta pt="10" x="19" y="-8"/>
+ <delta pt="11" x="19" y="-9"/>
+ <delta pt="12" x="17" y="-7"/>
+ <delta pt="13" x="15" y="-5"/>
+ <delta pt="14" x="14" y="-3"/>
+ <delta pt="15" x="13" y="-3"/>
+ <delta pt="16" x="12" y="0"/>
+ <delta pt="17" x="13" y="-1"/>
+ <delta pt="18" x="14" y="2"/>
+ <delta pt="19" x="15" y="1"/>
+ <delta pt="20" x="18" y="1"/>
+ <delta pt="21" x="19" y="1"/>
+ <delta pt="22" x="18" y="1"/>
+ <delta pt="23" x="15" y="1"/>
+ <delta pt="24" x="12" y="0"/>
+ <delta pt="25" x="9" y="0"/>
+ <delta pt="26" x="5" y="0"/>
+ <delta pt="27" x="2" y="0"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="8" y="-6"/>
+ <delta pt="30" x="-2" y="-8"/>
+ <delta pt="31" x="3" y="0"/>
+ <delta pt="32" x="0" y="3"/>
+ <delta pt="33" x="0" y="3"/>
+ <delta pt="34" x="2" y="3"/>
+ <delta pt="35" x="2" y="3"/>
+ <delta pt="36" x="3" y="3"/>
+ <delta pt="37" x="5" y="-1"/>
+ <delta pt="38" x="8" y="-4"/>
+ <delta pt="39" x="8" y="-4"/>
+ <delta pt="40" x="9" y="-5"/>
+ <delta pt="41" x="9" y="-6"/>
+ <delta pt="42" x="9" y="-7"/>
+ <delta pt="43" x="8" y="-6"/>
+ <delta pt="44" x="11" y="-5"/>
+ <delta pt="45" x="4" y="-5"/>
+ <delta pt="46" x="3" y="-5"/>
+ <delta pt="47" x="3" y="-3"/>
+ <delta pt="48" x="1" y="-6"/>
+ <delta pt="49" x="-1" y="-7"/>
+ <delta pt="50" x="-3" y="-8"/>
+ <delta pt="51" x="-2" y="-8"/>
+ <delta pt="52" x="3" y="-5"/>
+ <delta pt="53" x="8" y="-6"/>
+ <delta pt="54" x="10" y="-6"/>
+ <delta pt="55" x="11" y="-6"/>
+ <delta pt="56" x="12" y="-6"/>
+ <delta pt="57" x="13" y="-5"/>
+ <delta pt="58" x="12" y="-5"/>
+ <delta pt="59" x="11" y="-5"/>
+ <delta pt="60" x="11" y="-5"/>
+ <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="0003" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="14"/>
+ <delta pt="2" x="-4" y="14"/>
+ <delta pt="3" x="-14" y="13"/>
+ <delta pt="4" x="-24" y="13"/>
+ <delta pt="5" x="-32" y="12"/>
+ <delta pt="6" x="-38" y="11"/>
+ <delta pt="7" x="-40" y="11"/>
+ <delta pt="8" x="-39" y="11"/>
+ <delta pt="9" x="-37" y="11"/>
+ <delta pt="10" x="-35" y="11"/>
+ <delta pt="11" x="-33" y="12"/>
+ <delta pt="12" x="-30" y="9"/>
+ <delta pt="13" x="-26" y="1"/>
+ <delta pt="14" x="-24" y="-2"/>
+ <delta pt="15" x="-23" y="-2"/>
+ <delta pt="16" x="-23" y="-5"/>
+ <delta pt="17" x="-24" y="-6"/>
+ <delta pt="18" x="-25" y="-5"/>
+ <delta pt="19" x="-27" y="-4"/>
+ <delta pt="20" x="-31" y="-3"/>
+ <delta pt="21" x="-33" y="-3"/>
+ <delta pt="22" x="-33" y="-3"/>
+ <delta pt="23" x="-29" y="-2"/>
+ <delta pt="24" x="-23" y="-2"/>
+ <delta pt="25" x="-16" y="-2"/>
+ <delta pt="26" x="-8" y="-1"/>
+ <delta pt="27" x="-2" y="-1"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="-24" y="17"/>
+ <delta pt="30" x="8" y="14"/>
+ <delta pt="31" x="-4" y="-1"/>
+ <delta pt="32" x="-11" y="-4"/>
+ <delta pt="33" x="-11" y="-4"/>
+ <delta pt="34" x="-13" y="-4"/>
+ <delta pt="35" x="-14" y="-4"/>
+ <delta pt="36" x="-15" y="-2"/>
+ <delta pt="37" x="-21" y="3"/>
+ <delta pt="38" x="-27" y="7"/>
+ <delta pt="39" x="-29" y="8"/>
+ <delta pt="40" x="-30" y="9"/>
+ <delta pt="41" x="-30" y="11"/>
+ <delta pt="42" x="-29" y="12"/>
+ <delta pt="43" x="-24" y="17"/>
+ <delta pt="44" x="-8" y="6"/>
+ <delta pt="45" x="0" y="2"/>
+ <delta pt="46" x="2" y="3"/>
+ <delta pt="47" x="2" y="-1"/>
+ <delta pt="48" x="3" y="-1"/>
+ <delta pt="49" x="7" y="5"/>
+ <delta pt="50" x="8" y="11"/>
+ <delta pt="51" x="8" y="14"/>
+ <delta pt="52" x="-10" y="10"/>
+ <delta pt="53" x="-24" y="17"/>
+ <delta pt="54" x="-20" y="15"/>
+ <delta pt="55" x="-15" y="10"/>
+ <delta pt="56" x="-12" y="7"/>
+ <delta pt="57" x="-13" y="5"/>
+ <delta pt="58" x="-10" y="6"/>
+ <delta pt="59" x="-8" y="6"/>
+ <delta pt="60" x="-8" y="6"/>
+ <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="glyph00004">
+ <tuple>
+ <coord axis="0000" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="-34" y="0"/>
+ <delta pt="2" x="-34" y="6"/>
+ <delta pt="3" x="-19" y="6"/>
+ <delta pt="4" x="-13" y="10"/>
+ <delta pt="5" x="-9" y="10"/>
+ <delta pt="6" x="0" y="9"/>
+ <delta pt="7" x="3" y="9"/>
+ <delta pt="8" x="5" y="8"/>
+ <delta pt="9" x="5" y="6"/>
+ <delta pt="10" x="5" y="4"/>
+ <delta pt="11" x="4" y="1"/>
+ <delta pt="12" x="3" y="1"/>
+ <delta pt="13" x="2" y="0"/>
+ <delta pt="14" x="1" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="-36" y="-41"/>
+ <delta pt="17" x="-41" y="-41"/>
+ <delta pt="18" x="-42" y="-38"/>
+ <delta pt="19" x="-42" y="-32"/>
+ <delta pt="20" x="-42" y="-25"/>
+ <delta pt="21" x="-42" y="-18"/>
+ <delta pt="22" x="-42" y="-16"/>
+ <delta pt="23" x="-42" y="52"/>
+ <delta pt="24" x="-44" y="55"/>
+ <delta pt="25" x="-53" y="60"/>
+ <delta pt="26" x="-58" y="61"/>
+ <delta pt="27" x="-57" y="65"/>
+ <delta pt="28" x="-57" y="65"/>
+ <delta pt="29" x="-56" y="66"/>
+ <delta pt="30" x="-55" y="66"/>
+ <delta pt="31" x="-52" y="65"/>
+ <delta pt="32" x="-44" y="64"/>
+ <delta pt="33" x="-36" y="62"/>
+ <delta pt="34" x="-34" y="61"/>
+ <delta pt="35" x="-32" y="61"/>
+ <delta pt="36" x="-32" y="58"/>
+ <delta pt="37" x="-32" y="49"/>
+ <delta pt="38" x="-32" y="-12"/>
+ <delta pt="39" x="-32" y="-17"/>
+ <delta pt="40" x="-33" y="-24"/>
+ <delta pt="41" x="-33" y="-32"/>
+ <delta pt="42" x="-35" y="-38"/>
+ <delta pt="43" x="-36" y="-41"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0001" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="28" y="1"/>
+ <delta pt="2" x="28" y="-4"/>
+ <delta pt="3" x="16" y="-5"/>
+ <delta pt="4" x="11" y="-7"/>
+ <delta pt="5" x="8" y="-7"/>
+ <delta pt="6" x="0" y="-7"/>
+ <delta pt="7" x="-2" y="-7"/>
+ <delta pt="8" x="-4" y="-6"/>
+ <delta pt="9" x="-4" y="-4"/>
+ <delta pt="10" x="-4" y="-3"/>
+ <delta pt="11" x="-3" y="0"/>
+ <delta pt="12" x="-2" y="0"/>
+ <delta pt="13" x="-1" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="33" y="-154"/>
+ <delta pt="17" x="37" y="-154"/>
+ <delta pt="18" x="37" y="-153"/>
+ <delta pt="19" x="38" y="-152"/>
+ <delta pt="20" x="38" y="-150"/>
+ <delta pt="21" x="38" y="-150"/>
+ <delta pt="22" x="38" y="-150"/>
+ <delta pt="23" x="38" y="25"/>
+ <delta pt="24" x="40" y="23"/>
+ <delta pt="25" x="46" y="19"/>
+ <delta pt="26" x="51" y="18"/>
+ <delta pt="27" x="50" y="15"/>
+ <delta pt="28" x="50" y="15"/>
+ <delta pt="29" x="49" y="15"/>
+ <delta pt="30" x="48" y="15"/>
+ <delta pt="31" x="46" y="15"/>
+ <delta pt="32" x="40" y="16"/>
+ <delta pt="33" x="33" y="18"/>
+ <delta pt="34" x="31" y="18"/>
+ <delta pt="35" x="30" y="18"/>
+ <delta pt="36" x="30" y="21"/>
+ <delta pt="37" x="30" y="28"/>
+ <delta pt="38" x="30" y="-153"/>
+ <delta pt="39" x="30" y="-149"/>
+ <delta pt="40" x="30" y="-146"/>
+ <delta pt="41" x="31" y="-148"/>
+ <delta pt="42" x="32" y="-152"/>
+ <delta pt="43" x="33" y="-154"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0002" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="-4" y="0"/>
+ <delta pt="2" x="-4" y="1"/>
+ <delta pt="3" x="-2" y="1"/>
+ <delta pt="4" x="-2" y="1"/>
+ <delta pt="5" x="-1" y="2"/>
+ <delta pt="6" x="0" y="1"/>
+ <delta pt="7" x="1" y="1"/>
+ <delta pt="8" x="1" y="1"/>
+ <delta pt="9" x="1" y="1"/>
+ <delta pt="10" x="1" y="1"/>
+ <delta pt="11" x="1" y="0"/>
+ <delta pt="12" x="1" y="0"/>
+ <delta pt="13" x="1" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="-12" y="-20"/>
+ <delta pt="17" x="-13" y="-20"/>
+ <delta pt="18" x="-13" y="-20"/>
+ <delta pt="19" x="-13" y="-19"/>
+ <delta pt="20" x="-13" y="-19"/>
+ <delta pt="21" x="-13" y="-18"/>
+ <delta pt="22" x="-13" y="-18"/>
+ <delta pt="23" x="-13" y="-19"/>
+ <delta pt="24" x="-13" y="-19"/>
+ <delta pt="25" x="-14" y="-18"/>
+ <delta pt="26" x="-15" y="-18"/>
+ <delta pt="27" x="-15" y="-18"/>
+ <delta pt="28" x="-15" y="-18"/>
+ <delta pt="29" x="-15" y="-18"/>
+ <delta pt="30" x="-14" y="-18"/>
+ <delta pt="31" x="-14" y="-18"/>
+ <delta pt="32" x="-13" y="-18"/>
+ <delta pt="33" x="-12" y="-18"/>
+ <delta pt="34" x="-12" y="-18"/>
+ <delta pt="35" x="-11" y="-18"/>
+ <delta pt="36" x="-11" y="-19"/>
+ <delta pt="37" x="-11" y="-20"/>
+ <delta pt="38" x="-11" y="-17"/>
+ <delta pt="39" x="-11" y="-18"/>
+ <delta pt="40" x="-11" y="-19"/>
+ <delta pt="41" x="-11" y="-19"/>
+ <delta pt="42" x="-12" y="-20"/>
+ <delta pt="43" x="-12" y="-20"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0003" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="2" y="0"/>
+ <delta pt="2" x="2" y="0"/>
+ <delta pt="3" x="1" y="0"/>
+ <delta pt="4" x="1" y="0"/>
+ <delta pt="5" x="1" y="0"/>
+ <delta pt="6" x="0" y="0"/>
+ <delta pt="7" x="0" y="0"/>
+ <delta pt="8" x="0" y="0"/>
+ <delta pt="9" x="0" y="0"/>
+ <delta pt="10" x="0" y="0"/>
+ <delta pt="11" x="0" y="0"/>
+ <delta pt="12" x="0" y="0"/>
+ <delta pt="13" x="0" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="-1" y="-27"/>
+ <delta pt="17" x="-1" y="-27"/>
+ <delta pt="18" x="-1" y="-28"/>
+ <delta pt="19" x="-1" y="-29"/>
+ <delta pt="20" x="-1" y="-29"/>
+ <delta pt="21" x="-1" y="-30"/>
+ <delta pt="22" x="-1" y="-30"/>
+ <delta pt="23" x="-1" y="-22"/>
+ <delta pt="24" x="-1" y="-22"/>
+ <delta pt="25" x="-1" y="-23"/>
+ <delta pt="26" x="0" y="-23"/>
+ <delta pt="27" x="0" y="-23"/>
+ <delta pt="28" x="0" y="-23"/>
+ <delta pt="29" x="0" y="-23"/>
+ <delta pt="30" x="0" y="-23"/>
+ <delta pt="31" x="0" y="-23"/>
+ <delta pt="32" x="-1" y="-23"/>
+ <delta pt="33" x="-2" y="-23"/>
+ <delta pt="34" x="-2" y="-23"/>
+ <delta pt="35" x="-2" y="-23"/>
+ <delta pt="36" x="-2" y="-23"/>
+ <delta pt="37" x="-2" y="-22"/>
+ <delta pt="38" x="-2" y="-30"/>
+ <delta pt="39" x="-2" y="-30"/>
+ <delta pt="40" x="-2" y="-30"/>
+ <delta pt="41" x="-2" y="-29"/>
+ <delta pt="42" x="-1" y="-28"/>
+ <delta pt="43" x="-1" y="-27"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0004" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="15" y="0"/>
+ <delta pt="2" x="15" y="-2"/>
+ <delta pt="3" x="8" y="-2"/>
+ <delta pt="4" x="6" y="-4"/>
+ <delta pt="5" x="4" y="-4"/>
+ <delta pt="6" x="0" y="-4"/>
+ <delta pt="7" x="-1" y="-3"/>
+ <delta pt="8" x="-2" y="-3"/>
+ <delta pt="9" x="-2" y="-2"/>
+ <delta pt="10" x="-2" y="-1"/>
+ <delta pt="11" x="-1" y="0"/>
+ <delta pt="12" x="-1" y="0"/>
+ <delta pt="13" x="-1" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="3" y="2"/>
+ <delta pt="17" x="6" y="2"/>
+ <delta pt="18" x="6" y="2"/>
+ <delta pt="19" x="6" y="0"/>
+ <delta pt="20" x="6" y="-2"/>
+ <delta pt="21" x="6" y="-3"/>
+ <delta pt="22" x="6" y="-4"/>
+ <delta pt="23" x="6" y="7"/>
+ <delta pt="24" x="7" y="6"/>
+ <delta pt="25" x="11" y="4"/>
+ <delta pt="26" x="13" y="4"/>
+ <delta pt="27" x="13" y="2"/>
+ <delta pt="28" x="12" y="2"/>
+ <delta pt="29" x="12" y="2"/>
+ <delta pt="30" x="12" y="2"/>
+ <delta pt="31" x="11" y="2"/>
+ <delta pt="32" x="7" y="2"/>
+ <delta pt="33" x="4" y="3"/>
+ <delta pt="34" x="3" y="4"/>
+ <delta pt="35" x="2" y="4"/>
+ <delta pt="36" x="2" y="5"/>
+ <delta pt="37" x="2" y="9"/>
+ <delta pt="38" x="2" y="-5"/>
+ <delta pt="39" x="2" y="-4"/>
+ <delta pt="40" x="2" y="-1"/>
+ <delta pt="41" x="2" y="1"/>
+ <delta pt="42" x="3" y="2"/>
+ <delta pt="43" x="3" y="2"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="19" y="-2"/>
+ <delta pt="2" x="19" y="25"/>
+ <delta pt="3" x="12" y="26"/>
+ <delta pt="4" x="11" y="28"/>
+ <delta pt="5" x="9" y="28"/>
+ <delta pt="6" x="5" y="28"/>
+ <delta pt="7" x="5" y="28"/>
+ <delta pt="8" x="11" y="27"/>
+ <delta pt="9" x="12" y="22"/>
+ <delta pt="10" x="13" y="19"/>
+ <delta pt="11" x="13" y="10"/>
+ <delta pt="12" x="12" y="7"/>
+ <delta pt="13" x="10" y="3"/>
+ <delta pt="14" x="4" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="16" y="-5"/>
+ <delta pt="17" x="-8" y="-5"/>
+ <delta pt="18" x="-9" y="-5"/>
+ <delta pt="19" x="-11" y="-5"/>
+ <delta pt="20" x="-14" y="-4"/>
+ <delta pt="21" x="-15" y="-4"/>
+ <delta pt="22" x="-15" y="-5"/>
+ <delta pt="23" x="-15" y="-11"/>
+ <delta pt="24" x="-11" y="-12"/>
+ <delta pt="25" x="-5" y="-10"/>
+ <delta pt="26" x="-2" y="-10"/>
+ <delta pt="27" x="3" y="1"/>
+ <delta pt="28" x="4" y="2"/>
+ <delta pt="29" x="6" y="3"/>
+ <delta pt="30" x="8" y="3"/>
+ <delta pt="31" x="11" y="3"/>
+ <delta pt="32" x="18" y="1"/>
+ <delta pt="33" x="23" y="-1"/>
+ <delta pt="34" x="25" y="-3"/>
+ <delta pt="35" x="28" y="-4"/>
+ <delta pt="36" x="27" y="-8"/>
+ <delta pt="37" x="26" y="-10"/>
+ <delta pt="38" x="26" y="-3"/>
+ <delta pt="39" x="26" y="1"/>
+ <delta pt="40" x="24" y="2"/>
+ <delta pt="41" x="20" y="-2"/>
+ <delta pt="42" x="17" y="-5"/>
+ <delta pt="43" x="16" y="-5"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0000" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="2" y="0"/>
+ <delta pt="2" x="2" y="9"/>
+ <delta pt="3" x="1" y="9"/>
+ <delta pt="4" x="1" y="10"/>
+ <delta pt="5" x="1" y="10"/>
+ <delta pt="6" x="2" y="10"/>
+ <delta pt="7" x="2" y="10"/>
+ <delta pt="8" x="4" y="10"/>
+ <delta pt="9" x="4" y="8"/>
+ <delta pt="10" x="4" y="6"/>
+ <delta pt="11" x="4" y="3"/>
+ <delta pt="12" x="4" y="2"/>
+ <delta pt="13" x="4" y="1"/>
+ <delta pt="14" x="1" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="2" y="6"/>
+ <delta pt="17" x="-7" y="6"/>
+ <delta pt="18" x="-7" y="5"/>
+ <delta pt="19" x="-8" y="8"/>
+ <delta pt="20" x="-9" y="10"/>
+ <delta pt="21" x="-9" y="12"/>
+ <delta pt="22" x="-9" y="13"/>
+ <delta pt="23" x="-9" y="-1"/>
+ <delta pt="24" x="-8" y="-1"/>
+ <delta pt="25" x="-7" y="1"/>
+ <delta pt="26" x="-7" y="1"/>
+ <delta pt="27" x="-5" y="5"/>
+ <delta pt="28" x="-4" y="5"/>
+ <delta pt="29" x="-4" y="6"/>
+ <delta pt="30" x="-4" y="6"/>
+ <delta pt="31" x="-2" y="6"/>
+ <delta pt="32" x="2" y="5"/>
+ <delta pt="33" x="5" y="4"/>
+ <delta pt="34" x="6" y="4"/>
+ <delta pt="35" x="7" y="2"/>
+ <delta pt="36" x="7" y="1"/>
+ <delta pt="37" x="7" y="-1"/>
+ <delta pt="38" x="7" y="15"/>
+ <delta pt="39" x="7" y="16"/>
+ <delta pt="40" x="6" y="13"/>
+ <delta pt="41" x="4" y="10"/>
+ <delta pt="42" x="3" y="7"/>
+ <delta pt="43" x="2" y="6"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0001" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="2" y="1"/>
+ <delta pt="2" x="2" y="-7"/>
+ <delta pt="3" x="2" y="-7"/>
+ <delta pt="4" x="2" y="-8"/>
+ <delta pt="5" x="1" y="-8"/>
+ <delta pt="6" x="-1" y="-8"/>
+ <delta pt="7" x="-1" y="-8"/>
+ <delta pt="8" x="-2" y="-7"/>
+ <delta pt="9" x="-3" y="-6"/>
+ <delta pt="10" x="-3" y="-5"/>
+ <delta pt="11" x="-2" y="-2"/>
+ <delta pt="12" x="-3" y="-1"/>
+ <delta pt="13" x="-2" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="0" y="-6"/>
+ <delta pt="17" x="10" y="-6"/>
+ <delta pt="18" x="11" y="-7"/>
+ <delta pt="19" x="11" y="-1"/>
+ <delta pt="20" x="12" y="7"/>
+ <delta pt="21" x="12" y="16"/>
+ <delta pt="22" x="12" y="18"/>
+ <delta pt="23" x="12" y="7"/>
+ <delta pt="24" x="11" y="8"/>
+ <delta pt="25" x="11" y="6"/>
+ <delta pt="26" x="10" y="5"/>
+ <delta pt="27" x="9" y="2"/>
+ <delta pt="28" x="9" y="1"/>
+ <delta pt="29" x="8" y="1"/>
+ <delta pt="30" x="8" y="1"/>
+ <delta pt="31" x="5" y="1"/>
+ <delta pt="32" x="2" y="2"/>
+ <delta pt="33" x="0" y="2"/>
+ <delta pt="34" x="-1" y="3"/>
+ <delta pt="35" x="-2" y="3"/>
+ <delta pt="36" x="-2" y="5"/>
+ <delta pt="37" x="-2" y="7"/>
+ <delta pt="38" x="-2" y="22"/>
+ <delta pt="39" x="-2" y="16"/>
+ <delta pt="40" x="0" y="7"/>
+ <delta pt="41" x="1" y="2"/>
+ <delta pt="42" x="1" y="-3"/>
+ <delta pt="43" x="0" y="-6"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0002" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="-8" y="0"/>
+ <delta pt="2" x="-8" y="2"/>
+ <delta pt="3" x="-5" y="1"/>
+ <delta pt="4" x="-5" y="2"/>
+ <delta pt="5" x="-3" y="2"/>
+ <delta pt="6" x="0" y="2"/>
+ <delta pt="7" x="0" y="2"/>
+ <delta pt="8" x="0" y="2"/>
+ <delta pt="9" x="0" y="1"/>
+ <delta pt="10" x="0" y="1"/>
+ <delta pt="11" x="0" y="0"/>
+ <delta pt="12" x="0" y="0"/>
+ <delta pt="13" x="0" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="-5" y="-3"/>
+ <delta pt="17" x="-8" y="-3"/>
+ <delta pt="18" x="-9" y="-3"/>
+ <delta pt="19" x="-9" y="-3"/>
+ <delta pt="20" x="-9" y="-5"/>
+ <delta pt="21" x="-9" y="-6"/>
+ <delta pt="22" x="-9" y="-6"/>
+ <delta pt="23" x="-9" y="-14"/>
+ <delta pt="24" x="-9" y="-14"/>
+ <delta pt="25" x="-11" y="-12"/>
+ <delta pt="26" x="-12" y="-11"/>
+ <delta pt="27" x="-12" y="-11"/>
+ <delta pt="28" x="-12" y="-11"/>
+ <delta pt="29" x="-12" y="-10"/>
+ <delta pt="30" x="-13" y="-10"/>
+ <delta pt="31" x="-11" y="-10"/>
+ <delta pt="32" x="-8" y="-11"/>
+ <delta pt="33" x="-5" y="-11"/>
+ <delta pt="34" x="-4" y="-11"/>
+ <delta pt="35" x="-4" y="-12"/>
+ <delta pt="36" x="-4" y="-12"/>
+ <delta pt="37" x="-4" y="-15"/>
+ <delta pt="38" x="-4" y="-6"/>
+ <delta pt="39" x="-4" y="-5"/>
+ <delta pt="40" x="-5" y="-5"/>
+ <delta pt="41" x="-5" y="-4"/>
+ <delta pt="42" x="-5" y="-3"/>
+ <delta pt="43" x="-5" y="-3"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0003" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="-4" y="0"/>
+ <delta pt="2" x="-4" y="0"/>
+ <delta pt="3" x="-3" y="0"/>
+ <delta pt="4" x="-3" y="0"/>
+ <delta pt="5" x="-2" y="0"/>
+ <delta pt="6" x="0" y="0"/>
+ <delta pt="7" x="0" y="0"/>
+ <delta pt="8" x="-1" y="0"/>
+ <delta pt="9" x="0" y="0"/>
+ <delta pt="10" x="0" y="0"/>
+ <delta pt="11" x="-1" y="0"/>
+ <delta pt="12" x="-1" y="0"/>
+ <delta pt="13" x="0" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="-1" y="46"/>
+ <delta pt="17" x="0" y="46"/>
+ <delta pt="18" x="0" y="47"/>
+ <delta pt="19" x="0" y="53"/>
+ <delta pt="20" x="0" y="58"/>
+ <delta pt="21" x="0" y="64"/>
+ <delta pt="22" x="0" y="66"/>
+ <delta pt="23" x="0" y="38"/>
+ <delta pt="24" x="0" y="38"/>
+ <delta pt="25" x="-1" y="39"/>
+ <delta pt="26" x="-1" y="39"/>
+ <delta pt="27" x="-2" y="39"/>
+ <delta pt="28" x="-2" y="38"/>
+ <delta pt="29" x="-2" y="39"/>
+ <delta pt="30" x="-2" y="39"/>
+ <delta pt="31" x="-2" y="39"/>
+ <delta pt="32" x="-1" y="39"/>
+ <delta pt="33" x="0" y="39"/>
+ <delta pt="34" x="0" y="39"/>
+ <delta pt="35" x="0" y="39"/>
+ <delta pt="36" x="0" y="39"/>
+ <delta pt="37" x="0" y="38"/>
+ <delta pt="38" x="0" y="68"/>
+ <delta pt="39" x="0" y="66"/>
+ <delta pt="40" x="0" y="61"/>
+ <delta pt="41" x="1" y="55"/>
+ <delta pt="42" x="0" y="49"/>
+ <delta pt="43" x="-1" y="46"/>
+ <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"/>
+ </tuple>
+ <tuple>
+ <coord axis="0004" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="-14" y="1"/>
+ <delta pt="2" x="-14" y="-3"/>
+ <delta pt="3" x="-10" y="-4"/>
+ <delta pt="4" x="-9" y="-4"/>
+ <delta pt="5" x="-6" y="-4"/>
+ <delta pt="6" x="-1" y="-4"/>
+ <delta pt="7" x="-1" y="-4"/>
+ <delta pt="8" x="-3" y="-3"/>
+ <delta pt="9" x="-2" y="-3"/>
+ <delta pt="10" x="-2" y="-2"/>
+ <delta pt="11" x="-3" y="-3"/>
+ <delta pt="12" x="-3" y="-2"/>
+ <delta pt="13" x="-2" y="-1"/>
+ <delta pt="14" x="-1" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="-12" y="-1"/>
+ <delta pt="17" x="-11" y="-1"/>
+ <delta pt="18" x="-11" y="-1"/>
+ <delta pt="19" x="-11" y="2"/>
+ <delta pt="20" x="-11" y="5"/>
+ <delta pt="21" x="-11" y="8"/>
+ <delta pt="22" x="-11" y="9"/>
+ <delta pt="23" x="-11" y="-6"/>
+ <delta pt="24" x="-11" y="-5"/>
+ <delta pt="25" x="-15" y="-2"/>
+ <delta pt="26" x="-17" y="-2"/>
+ <delta pt="27" x="-19" y="-4"/>
+ <delta pt="28" x="-18" y="-4"/>
+ <delta pt="29" x="-20" y="-4"/>
+ <delta pt="30" x="-20" y="-4"/>
+ <delta pt="31" x="-19" y="-4"/>
+ <delta pt="32" x="-16" y="-3"/>
+ <delta pt="33" x="-13" y="-3"/>
+ <delta pt="34" x="-12" y="-3"/>
+ <delta pt="35" x="-12" y="-4"/>
+ <delta pt="36" x="-12" y="-3"/>
+ <delta pt="37" x="-12" y="-7"/>
+ <delta pt="38" x="-12" y="10"/>
+ <delta pt="39" x="-12" y="9"/>
+ <delta pt="40" x="-12" y="5"/>
+ <delta pt="41" x="-12" y="3"/>
+ <delta pt="42" x="-12" y="0"/>
+ <delta pt="43" x="-12" y="-1"/>
+ <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"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="glyph00005">
+ <tuple>
+ <coord axis="0000" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-2"/>
+ <delta pt="2" x="-1" y="-2"/>
+ <delta pt="3" x="-4" y="-2"/>
+ <delta pt="4" x="-9" y="-2"/>
+ <delta pt="5" x="-13" y="-2"/>
+ <delta pt="6" x="-16" y="-2"/>
+ <delta pt="7" x="-17" y="-2"/>
+ <delta pt="8" x="-17" y="-2"/>
+ <delta pt="9" x="-20" y="-2"/>
+ <delta pt="10" x="-21" y="-3"/>
+ <delta pt="11" x="-22" y="-2"/>
+ <delta pt="12" x="-24" y="-1"/>
+ <delta pt="13" x="-25" y="-1"/>
+ <delta pt="14" x="-25" y="0"/>
+ <delta pt="15" x="-25" y="0"/>
+ <delta pt="16" x="-24" y="0"/>
+ <delta pt="17" x="-24" y="0"/>
+ <delta pt="18" x="-21" y="0"/>
+ <delta pt="19" x="-20" y="0"/>
+ <delta pt="20" x="-20" y="0"/>
+ <delta pt="21" x="-17" y="0"/>
+ <delta pt="22" x="-12" y="0"/>
+ <delta pt="23" x="-8" y="0"/>
+ <delta pt="24" x="-4" y="0"/>
+ <delta pt="25" x="-1" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="3" y="-2"/>
+ <delta pt="28" x="0" y="-2"/>
+ <delta pt="29" x="0" y="0"/>
+ <delta pt="30" x="1" y="1"/>
+ <delta pt="31" x="1" y="1"/>
+ <delta pt="32" x="2" y="1"/>
+ <delta pt="33" x="2" y="1"/>
+ <delta pt="34" x="3" y="0"/>
+ <delta pt="35" x="3" y="0"/>
+ <delta pt="36" x="3" y="0"/>
+ <delta pt="37" x="3" y="-1"/>
+ <delta pt="38" x="3" y="-2"/>
+ <delta pt="39" x="2" y="-15"/>
+ <delta pt="40" x="0" y="-15"/>
+ <delta pt="41" x="0" y="-14"/>
+ <delta pt="42" x="0" y="-12"/>
+ <delta pt="43" x="0" y="-10"/>
+ <delta pt="44" x="0" y="-9"/>
+ <delta pt="45" x="0" y="-2"/>
+ <delta pt="46" x="3" y="-2"/>
+ <delta pt="47" x="3" y="-8"/>
+ <delta pt="48" x="3" y="-9"/>
+ <delta pt="49" x="3" y="-12"/>
+ <delta pt="50" x="2" y="-14"/>
+ <delta pt="51" x="2" y="-15"/>
+ <delta pt="52" x="0" y="0"/>
+ <delta pt="53" x="0" y="0"/>
+ <delta pt="54" x="0" y="0"/>
+ <delta pt="55" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="0001" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="1" y="0"/>
+ <delta pt="3" x="4" y="0"/>
+ <delta pt="4" x="8" y="0"/>
+ <delta pt="5" x="11" y="0"/>
+ <delta pt="6" x="14" y="0"/>
+ <delta pt="7" x="14" y="0"/>
+ <delta pt="8" x="14" y="0"/>
+ <delta pt="9" x="13" y="0"/>
+ <delta pt="10" x="13" y="0"/>
+ <delta pt="11" x="13" y="0"/>
+ <delta pt="12" x="12" y="0"/>
+ <delta pt="13" x="12" y="0"/>
+ <delta pt="14" x="12" y="0"/>
+ <delta pt="15" x="12" y="1"/>
+ <delta pt="16" x="12" y="1"/>
+ <delta pt="17" x="13" y="1"/>
+ <delta pt="18" x="13" y="1"/>
+ <delta pt="19" x="13" y="1"/>
+ <delta pt="20" x="13" y="1"/>
+ <delta pt="21" x="12" y="1"/>
+ <delta pt="22" x="9" y="1"/>
+ <delta pt="23" x="6" y="0"/>
+ <delta pt="24" x="3" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="1" y="0"/>
+ <delta pt="28" x="1" y="0"/>
+ <delta pt="29" x="1" y="0"/>
+ <delta pt="30" x="1" y="0"/>
+ <delta pt="31" x="1" y="0"/>
+ <delta pt="32" x="1" y="0"/>
+ <delta pt="33" x="1" y="0"/>
+ <delta pt="34" x="1" y="0"/>
+ <delta pt="35" x="2" y="0"/>
+ <delta pt="36" x="2" y="0"/>
+ <delta pt="37" x="2" y="0"/>
+ <delta pt="38" x="1" y="0"/>
+ <delta pt="39" x="1" y="-50"/>
+ <delta pt="40" x="1" y="-50"/>
+ <delta pt="41" x="1" y="-50"/>
+ <delta pt="42" x="1" y="-49"/>
+ <delta pt="43" x="1" y="-48"/>
+ <delta pt="44" x="1" y="-48"/>
+ <delta pt="45" x="1" y="0"/>
+ <delta pt="46" x="1" y="0"/>
+ <delta pt="47" x="1" y="-48"/>
+ <delta pt="48" x="1" y="-48"/>
+ <delta pt="49" x="1" y="-49"/>
+ <delta pt="50" x="1" y="-50"/>
+ <delta pt="51" x="1" y="-50"/>
+ <delta pt="52" x="0" y="0"/>
+ <delta pt="53" x="0" y="0"/>
+ <delta pt="54" x="0" y="0"/>
+ <delta pt="55" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-21"/>
+ <delta pt="2" x="0" y="-21"/>
+ <delta pt="3" x="7" y="-21"/>
+ <delta pt="4" x="16" y="-21"/>
+ <delta pt="5" x="27" y="-20"/>
+ <delta pt="6" x="33" y="-20"/>
+ <delta pt="7" x="34" y="-20"/>
+ <delta pt="8" x="32" y="-20"/>
+ <delta pt="9" x="28" y="-21"/>
+ <delta pt="10" x="26" y="-21"/>
+ <delta pt="11" x="22" y="-18"/>
+ <delta pt="12" x="15" y="-11"/>
+ <delta pt="13" x="13" y="-7"/>
+ <delta pt="14" x="12" y="-6"/>
+ <delta pt="15" x="13" y="-4"/>
+ <delta pt="16" x="15" y="0"/>
+ <delta pt="17" x="19" y="0"/>
+ <delta pt="18" x="26" y="1"/>
+ <delta pt="19" x="30" y="1"/>
+ <delta pt="20" x="30" y="1"/>
+ <delta pt="21" x="26" y="0"/>
+ <delta pt="22" x="19" y="0"/>
+ <delta pt="23" x="11" y="0"/>
+ <delta pt="24" x="4" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="30" y="-18"/>
+ <delta pt="28" x="-9" y="-21"/>
+ <delta pt="29" x="2" y="1"/>
+ <delta pt="30" x="9" y="3"/>
+ <delta pt="31" x="11" y="4"/>
+ <delta pt="32" x="13" y="3"/>
+ <delta pt="33" x="18" y="1"/>
+ <delta pt="34" x="28" y="-4"/>
+ <delta pt="35" x="31" y="-6"/>
+ <delta pt="36" x="32" y="-7"/>
+ <delta pt="37" x="32" y="-8"/>
+ <delta pt="38" x="30" y="-18"/>
+ <delta pt="39" x="18" y="-7"/>
+ <delta pt="40" x="-4" y="-7"/>
+ <delta pt="41" x="-6" y="-10"/>
+ <delta pt="42" x="-8" y="-11"/>
+ <delta pt="43" x="-9" y="-12"/>
+ <delta pt="44" x="-9" y="-13"/>
+ <delta pt="45" x="-9" y="-20"/>
+ <delta pt="46" x="30" y="-20"/>
+ <delta pt="47" x="30" y="-6"/>
+ <delta pt="48" x="30" y="-8"/>
+ <delta pt="49" x="27" y="-9"/>
+ <delta pt="50" x="22" y="-9"/>
+ <delta pt="51" x="18" y="-7"/>
+ <delta pt="52" x="0" y="0"/>
+ <delta pt="53" x="0" y="0"/>
+ <delta pt="54" x="0" y="0"/>
+ <delta pt="55" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="0000" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="-2"/>
+ <delta pt="2" x="0" y="-2"/>
+ <delta pt="3" x="1" y="-2"/>
+ <delta pt="4" x="2" y="-2"/>
+ <delta pt="5" x="3" y="-2"/>
+ <delta pt="6" x="4" y="-2"/>
+ <delta pt="7" x="4" y="-2"/>
+ <delta pt="8" x="3" y="-2"/>
+ <delta pt="9" x="3" y="-2"/>
+ <delta pt="10" x="3" y="-2"/>
+ <delta pt="11" x="3" y="-2"/>
+ <delta pt="12" x="2" y="-1"/>
+ <delta pt="13" x="2" y="0"/>
+ <delta pt="14" x="2" y="-1"/>
+ <delta pt="15" x="2" y="0"/>
+ <delta pt="16" x="2" y="0"/>
+ <delta pt="17" x="3" y="0"/>
+ <delta pt="18" x="3" y="0"/>
+ <delta pt="19" x="4" y="0"/>
+ <delta pt="20" x="4" y="0"/>
+ <delta pt="21" x="3" y="1"/>
+ <delta pt="22" x="2" y="0"/>
+ <delta pt="23" x="1" y="0"/>
+ <delta pt="24" x="1" y="0"/>
+ <delta pt="25" x="0" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="3" y="-2"/>
+ <delta pt="28" x="-1" y="-2"/>
+ <delta pt="29" x="1" y="0"/>
+ <delta pt="30" x="1" y="1"/>
+ <delta pt="31" x="1" y="0"/>
+ <delta pt="32" x="2" y="1"/>
+ <delta pt="33" x="2" y="0"/>
+ <delta pt="34" x="4" y="0"/>
+ <delta pt="35" x="3" y="0"/>
+ <delta pt="36" x="4" y="0"/>
+ <delta pt="37" x="3" y="-1"/>
+ <delta pt="38" x="3" y="-2"/>
+ <delta pt="39" x="2" y="0"/>
+ <delta pt="40" x="0" y="0"/>
+ <delta pt="41" x="-1" y="0"/>
+ <delta pt="42" x="-1" y="-1"/>
+ <delta pt="43" x="-1" y="0"/>
+ <delta pt="44" x="-1" y="-1"/>
+ <delta pt="45" x="-1" y="-2"/>
+ <delta pt="46" x="3" y="-2"/>
+ <delta pt="47" x="3" y="0"/>
+ <delta pt="48" x="3" y="0"/>
+ <delta pt="49" x="3" y="0"/>
+ <delta pt="50" x="2" y="0"/>
+ <delta pt="51" x="2" y="0"/>
+ <delta pt="52" x="0" y="0"/>
+ <delta pt="53" x="0" y="0"/>
+ <delta pt="54" x="0" y="0"/>
+ <delta pt="55" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="0001" value="1.0"/>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="1" y="-1"/>
+ <delta pt="3" x="2" y="-1"/>
+ <delta pt="4" x="3" y="-1"/>
+ <delta pt="5" x="4" y="-1"/>
+ <delta pt="6" x="5" y="-1"/>
+ <delta pt="7" x="5" y="-1"/>
+ <delta pt="8" x="5" y="-1"/>
+ <delta pt="9" x="5" y="-1"/>
+ <delta pt="10" x="5" y="-1"/>
+ <delta pt="11" x="5" y="-1"/>
+ <delta pt="12" x="4" y="-1"/>
+ <delta pt="13" x="4" y="0"/>
+ <delta pt="14" x="5" y="0"/>
+ <delta pt="15" x="4" y="-1"/>
+ <delta pt="16" x="4" y="-1"/>
+ <delta pt="17" x="4" y="-1"/>
+ <delta pt="18" x="5" y="-1"/>
+ <delta pt="19" x="5" y="-1"/>
+ <delta pt="20" x="4" y="-1"/>
+ <delta pt="21" x="3" y="0"/>
+ <delta pt="22" x="3" y="0"/>
+ <delta pt="23" x="2" y="0"/>
+ <delta pt="24" x="1" y="0"/>
+ <delta pt="25" x="1" y="0"/>
+ <delta pt="26" x="0" y="0"/>
+ <delta pt="27" x="1" y="0"/>
+ <delta pt="28" x="0" y="0"/>
+ <delta pt="29" x="0" y="0"/>
+ <delta pt="30" x="0" y="0"/>
+ <delta pt="31" x="0" y="1"/>
+ <delta pt="32" x="1" y="0"/>
+ <delta pt="33" x="0" y="0"/>
+ <delta pt="34" x="1" y="0"/>
+ <delta pt="35" x="0" y="0"/>
+ <delta pt="36" x="0" y="-1"/>
+ <delta pt="37" x="0" y="0"/>
+ <delta pt="38" x="1" y="0"/>
+ <delta pt="39" x="0" y="2"/>
+ <delta pt="40" x="0" y="2"/>
+ <delta pt="41" x="0" y="3"/>
+ <delta pt="42" x="0" y="6"/>
+ <delta pt="43" x="0" y="9"/>
+ <delta pt="44" x="0" y="9"/>
+ <delta pt="45" x="0" y="0"/>
+ <delta pt="46" x="1" y="0"/>
+ <delta pt="47" x="1" y="10"/>
+ <delta pt="48" x="1" y="9"/>
+ <delta pt="49" x="0" y="6"/>
+ <delta pt="50" x="0" y="3"/>
+ <delta pt="51" x="0" y="2"/>
+ <delta pt="52" x="0" y="0"/>
+ <delta pt="53" x="0" y="0"/>
+ <delta pt="54" x="0" y="0"/>
+ <delta pt="55" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="uniAC00">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-27" y="2"/>
+ <delta pt="1" x="-6" y="-10"/>
+ <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="uniAC01">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-23" y="1"/>
+ <delta pt="1" x="-6" y="-7"/>
+ <delta pt="2" x="-21" y="3"/>
+ <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/ttLib/data/varc-ac00-ac01.ttf b/Tests/ttLib/data/varc-ac00-ac01.ttf
new file mode 100644
index 00000000..1e385bad
--- /dev/null
+++ b/Tests/ttLib/data/varc-ac00-ac01.ttf
Binary files differ
diff --git a/Tests/ttLib/main_test.py b/Tests/ttLib/main_test.py
new file mode 100644
index 00000000..d97f3c94
--- /dev/null
+++ b/Tests/ttLib/main_test.py
@@ -0,0 +1,105 @@
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+
+from fontTools.ttLib import __main__, TTFont, TTCollection
+
+import pytest
+
+
+TEST_DATA = Path(__file__).parent / "data"
+
+
+@pytest.fixture
+def ttfont_path():
+ font = TTFont()
+ font.importXML(TEST_DATA / "TestTTF-Regular.ttx")
+ with tempfile.NamedTemporaryFile(suffix=".ttf", delete=False) as fp:
+ font_path = Path(fp.name)
+ font.save(font_path)
+ yield font_path
+ font_path.unlink()
+
+
+@pytest.fixture
+def ttcollection_path():
+ font1 = TTFont()
+ font1.importXML(TEST_DATA / "TestTTF-Regular.ttx")
+ font2 = TTFont()
+ font2.importXML(TEST_DATA / "TestTTF-Regular.ttx")
+ coll = TTCollection()
+ coll.fonts = [font1, font2]
+ with tempfile.NamedTemporaryFile(suffix=".ttf", delete=False) as fp:
+ collection_path = Path(fp.name)
+ coll.save(collection_path)
+ yield collection_path
+ collection_path.unlink()
+
+
+@pytest.fixture(params=[None, "woff"])
+def flavor(request):
+ return request.param
+
+
+def test_ttLib_main_as_subprocess(ttfont_path):
+ subprocess.run(
+ [sys.executable, "-m", "fontTools.ttLib", str(ttfont_path)], check=True
+ )
+
+
+def test_ttLib_open_ttfont(ttfont_path):
+ __main__.main([str(ttfont_path)])
+
+
+def test_ttLib_open_save_ttfont(tmp_path, ttfont_path, flavor):
+ output_path = tmp_path / "TestTTF-Regular.ttf"
+ args = ["-o", str(output_path), str(ttfont_path)]
+ if flavor is not None:
+ args.extend(["--flavor", flavor])
+
+ __main__.main(args)
+
+ assert output_path.exists()
+ assert TTFont(output_path).getGlyphOrder() == TTFont(ttfont_path).getGlyphOrder()
+
+
+def test_ttLib_open_ttcollection(ttcollection_path):
+ __main__.main(["-y", "0", str(ttcollection_path)])
+
+
+def test_ttLib_open_ttcollection_save_single_font(tmp_path, ttcollection_path, flavor):
+ for i in range(2):
+ output_path = tmp_path / f"TestTTF-Regular#{i}.ttf"
+ args = ["-y", str(i), "-o", str(output_path), str(ttcollection_path)]
+ if flavor is not None:
+ args.extend(["--flavor", flavor])
+
+ __main__.main(args)
+
+ assert output_path.exists()
+ assert (
+ TTFont(output_path).getGlyphOrder()
+ == TTCollection(ttcollection_path)[i].getGlyphOrder()
+ )
+
+
+def test_ttLib_open_ttcollection_save_ttcollection(tmp_path, ttcollection_path):
+ output_path = tmp_path / "TestTTF.ttc"
+
+ __main__.main(["-o", str(output_path), str(ttcollection_path)])
+
+ assert output_path.exists()
+ assert len(TTCollection(output_path)) == len(TTCollection(ttcollection_path))
+
+
+def test_ttLib_open_multiple_fonts_save_ttcollection(tmp_path, ttfont_path):
+ output_path = tmp_path / "TestTTF.ttc"
+
+ __main__.main(["-o", str(output_path), str(ttfont_path), str(ttfont_path)])
+
+ assert output_path.exists()
+
+ coll = TTCollection(output_path)
+ assert len(coll) == 2
+ assert coll[0].getGlyphOrder() == coll[1].getGlyphOrder()
diff --git a/Tests/ttLib/scaleUpem_test.py b/Tests/ttLib/scaleUpem_test.py
index dc52bf94..6024758f 100644
--- a/Tests/ttLib/scaleUpem_test.py
+++ b/Tests/ttLib/scaleUpem_test.py
@@ -8,8 +8,8 @@ import tempfile
import unittest
import pytest
-class ScaleUpemTest(unittest.TestCase):
+class ScaleUpemTest(unittest.TestCase):
def setUp(self):
self.tempdir = None
self.num_tempfiles = 0
@@ -26,8 +26,7 @@ class ScaleUpemTest(unittest.TestCase):
def temp_path(self, suffix):
self.temp_dir()
self.num_tempfiles += 1
- return os.path.join(self.tempdir,
- "tmp%d%s" % (self.num_tempfiles, suffix))
+ return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
def temp_dir(self):
if not self.tempdir:
@@ -51,12 +50,12 @@ class ScaleUpemTest(unittest.TestCase):
expected = self.read_ttx(expected_ttx)
if actual != expected:
for line in difflib.unified_diff(
- expected, actual, fromfile=expected_ttx, tofile=path):
+ expected, actual, fromfile=expected_ttx, tofile=path
+ ):
sys.stdout.write(line)
self.fail("TTX output is different from expected")
def test_scale_upem_ttf(self):
-
font = TTFont(self.get_path("I.ttf"))
tables = [table_tag for table_tag in font.keys() if table_tag != "head"]
@@ -65,9 +64,20 @@ class ScaleUpemTest(unittest.TestCase):
expected_ttx_path = self.get_path("I-512upem.ttx")
self.expect_ttx(font, expected_ttx_path, tables)
+ def test_scale_upem_varComposite(self):
+ font = TTFont(self.get_path("varc-ac00-ac01.ttf"))
+ tables = [table_tag for table_tag in font.keys() if table_tag != "head"]
- def test_scale_upem_otf(self):
+ scale_upem(font, 500)
+ expected_ttx_path = self.get_path("varc-ac00-ac01-500upem.ttx")
+ self.expect_ttx(font, expected_ttx_path, tables)
+
+ # Scale our other varComposite font as well; without checking the expected
+ font = TTFont(self.get_path("varc-6868.ttf"))
+ scale_upem(font, 500)
+
+ def test_scale_upem_otf(self):
# Just test that it doesn't crash
font = TTFont(self.get_path("TestVGID-Regular.otf"))
diff --git a/Tests/ttLib/sfnt_test.py b/Tests/ttLib/sfnt_test.py
index 9f817444..7832a2ff 100644
--- a/Tests/ttLib/sfnt_test.py
+++ b/Tests/ttLib/sfnt_test.py
@@ -1,9 +1,25 @@
import io
import copy
import pickle
-from fontTools.ttLib.sfnt import calcChecksum, SFNTReader
+import tempfile
+from fontTools.ttLib import TTFont
+from fontTools.ttLib.sfnt import calcChecksum, SFNTReader, WOFFFlavorData
+from pathlib import Path
import pytest
+TEST_DATA = Path(__file__).parent / "data"
+
+
+@pytest.fixture
+def ttfont_path():
+ font = TTFont()
+ font.importXML(TEST_DATA / "TestTTF-Regular.ttx")
+ with tempfile.NamedTemporaryFile(suffix=".ttf", delete=False) as fp:
+ font_path = Path(fp.name)
+ font.save(font_path)
+ yield font_path
+ font_path.unlink()
+
def test_calcChecksum():
assert calcChecksum(b"abcd") == 1633837924
@@ -57,3 +73,24 @@ class SFNTReaderTest:
if k == "file":
continue
assert getattr(reader2, k) == v
+
+
+def test_ttLib_sfnt_write_privData(tmp_path, ttfont_path):
+ output_path = tmp_path / "TestTTF-Regular.woff"
+ font = TTFont(ttfont_path)
+
+ privData = "Private Eyes".encode()
+
+ data = WOFFFlavorData()
+ head = font["head"]
+ data.majorVersion, data.minorVersion = map(
+ int, format(head.fontRevision, ".3f").split(".")
+ )
+
+ data.privData = privData
+ font.flavor = "woff"
+ font.flavorData = data
+ font.save(output_path)
+
+ assert output_path.exists()
+ assert TTFont(output_path).flavorData.privData == privData
diff --git a/Tests/ttLib/tables/C_F_F__2_test.py b/Tests/ttLib/tables/C_F_F__2_test.py
index 10f9b2fb..2e4d19af 100644
--- a/Tests/ttLib/tables/C_F_F__2_test.py
+++ b/Tests/ttLib/tables/C_F_F__2_test.py
@@ -8,7 +8,7 @@ import unittest
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
CFF_TTX = os.path.join(DATA_DIR, "C_F_F__2.ttx")
CFF_BIN = os.path.join(DATA_DIR, "C_F_F__2.bin")
@@ -16,28 +16,28 @@ CFF_BIN = os.path.join(DATA_DIR, "C_F_F__2.bin")
def strip_VariableItems(string):
# ttlib changes with the fontTools version
- string = re.sub(' ttLibVersion=".*"', '', string)
+ string = re.sub(' ttLibVersion=".*"', "", string)
# head table checksum and mod date changes with each save.
- string = re.sub('<checkSumAdjustment value="[^"]+"/>', '', string)
- string = re.sub('<modified value="[^"]+"/>', '', string)
+ string = re.sub('<checkSumAdjustment value="[^"]+"/>', "", string)
+ string = re.sub('<modified value="[^"]+"/>', "", string)
return string
-class CFFTableTest(unittest.TestCase):
+class CFFTableTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
- with open(CFF_BIN, 'rb') as f:
+ with open(CFF_BIN, "rb") as f:
font = TTFont(file=CFF_BIN)
- cffTable = font['CFF2']
+ cffTable = font["CFF2"]
cls.cff2Data = cffTable.compile(font)
- with open(CFF_TTX, 'r') as f:
+ with open(CFF_TTX, "r") as f:
cff2XML = f.read()
cff2XML = strip_VariableItems(cff2XML)
cls.cff2XML = cff2XML.splitlines()
def test_toXML(self):
font = TTFont(file=CFF_BIN)
- cffTable = font['CFF2']
+ cffTable = font["CFF2"]
cffData = cffTable.compile(font)
out = StringIO()
font.saveXML(out)
@@ -47,9 +47,9 @@ class CFFTableTest(unittest.TestCase):
self.assertEqual(cff2XML, self.cff2XML)
def test_fromXML(self):
- font = TTFont(sfntVersion='OTTO')
+ font = TTFont(sfntVersion="OTTO")
font.importXML(CFF_TTX)
- cffTable = font['CFF2']
+ cffTable = font["CFF2"]
cff2Data = cffTable.compile(font)
self.assertEqual(cff2Data, self.cff2Data)
diff --git a/Tests/ttLib/tables/C_F_F_test.py b/Tests/ttLib/tables/C_F_F_test.py
index cb8d8c55..76bff437 100644
--- a/Tests/ttLib/tables/C_F_F_test.py
+++ b/Tests/ttLib/tables/C_F_F_test.py
@@ -8,28 +8,27 @@ import unittest
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
CFF_TTX = os.path.join(DATA_DIR, "C_F_F_.ttx")
CFF_BIN = os.path.join(DATA_DIR, "C_F_F_.bin")
def strip_ttLibVersion(string):
- return re.sub(' ttLibVersion=".*"', '', string)
+ return re.sub(' ttLibVersion=".*"', "", string)
class CFFTableTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
- with open(CFF_BIN, 'rb') as f:
+ with open(CFF_BIN, "rb") as f:
cls.cffData = f.read()
- with open(CFF_TTX, 'r') as f:
+ with open(CFF_TTX, "r") as f:
cls.cffXML = strip_ttLibVersion(f.read()).splitlines()
def test_toXML(self):
- font = TTFont(sfntVersion='OTTO')
- cffTable = font['CFF '] = newTable('CFF ')
+ font = TTFont(sfntVersion="OTTO")
+ cffTable = font["CFF "] = newTable("CFF ")
cffTable.decompile(self.cffData, font)
out = StringIO()
font.saveXML(out)
@@ -37,13 +36,14 @@ class CFFTableTest(unittest.TestCase):
self.assertEqual(cffXML, self.cffXML)
def test_fromXML(self):
- font = TTFont(sfntVersion='OTTO')
+ font = TTFont(sfntVersion="OTTO")
font.importXML(CFF_TTX)
- cffTable = font['CFF ']
+ cffTable = font["CFF "]
cffData = cffTable.compile(font)
self.assertEqual(cffData, self.cffData)
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/C_O_L_R_test.py b/Tests/ttLib/tables/C_O_L_R_test.py
index 132449ea..43ad7049 100644
--- a/Tests/ttLib/tables/C_O_L_R_test.py
+++ b/Tests/ttLib/tables/C_O_L_R_test.py
@@ -1,11 +1,16 @@
from fontTools import ttLib
from fontTools.misc.testTools import getXML, parseXML
+from fontTools.ttLib import TTFont
from fontTools.ttLib.tables.C_O_L_R_ import table_C_O_L_R_
+from pathlib import Path
import binascii
import pytest
+TEST_DATA_DIR = Path(__file__).parent / "data"
+
+
COLR_V0_SAMPLE = (
(b"\x00\x00", "Version (0)"),
(b"\x00\x01", "BaseGlyphRecordCount (1)"),
@@ -111,7 +116,7 @@ COLR_V1_SAMPLE = (
(b"\x00\x03", "LayerRecordCount (3)"),
(b"\x00\x00\x00\x34", "Offset to BaseGlyphList from beginning of table (52)"),
(b"\x00\x00\x00\x9f", "Offset to LayerList from beginning of table (159)"),
- (b"\x00\x00\x01\x62", "Offset to ClipList (354)"),
+ (b"\x00\x00\x01\x66", "Offset to ClipList (358)"),
(b"\x00\x00\x00\x00", "Offset to DeltaSetIndexMap (NULL)"),
(b"\x00\x00\x00\x00", "Offset to VarStore (NULL)"),
(b"\x00\x06", "BaseGlyphRecord[0].BaseGlyph (6)"),
@@ -151,7 +156,10 @@ COLR_V1_SAMPLE = (
(b"\x00\x00\x08", "Offset to BackdropPaint from beginning of PaintComposite (8)"),
(b"\x0d", "BaseGlyphPaintRecord[1].Paint.BackdropPaint.Format (13)"),
(b"\x00\x00\x07", "Offset to Paint from beginning of PaintVarTransform (7)"),
- (b"\x00\x00\x0a", "Offset to VarAffine2x3 from beginning of PaintVarTransform (10)"),
+ (
+ b"\x00\x00\x0a",
+ "Offset to VarAffine2x3 from beginning of PaintVarTransform (10)",
+ ),
(b"\x0b", "BaseGlyphPaintRecord[1].Paint.BackdropPaint.Format (11)"),
(b"\x00\x0a", "BaseGlyphPaintRecord[1].Paint.BackdropPaint.Glyph (10)"),
(b"\x00\x01\x00\x00", "VarAffine2x3.xx (1.0)"),
@@ -179,22 +187,26 @@ COLR_V1_SAMPLE = (
(b"\x00\x05", "ColorLine.ColorStop[1].PaletteIndex (5)"),
(b"@\x00", "ColorLine.ColorStop[1].Alpha (1.0)"),
# LayerList
- (b"\x00\x00\x00\x04", "LayerList.LayerCount (4)"),
+ (b"\x00\x00\x00\x05", "LayerList.LayerCount (5)"),
+ (
+ b"\x00\x00\x00\x18",
+ "First Offset to Paint table from beginning of LayerList (24)",
+ ),
(
- b"\x00\x00\x00\x14",
- "First Offset to Paint table from beginning of LayerList (20)",
+ b"\x00\x00\x00\x27",
+ "Second Offset to Paint table from beginning of LayerList (39)",
),
(
- b"\x00\x00\x00\x23",
- "Second Offset to Paint table from beginning of LayerList (35)",
+ b"\x00\x00\x00\x52",
+ "Third Offset to Paint table from beginning of LayerList (82)",
),
(
- b"\x00\x00\x00\x4e",
- "Third Offset to Paint table from beginning of LayerList (78)",
+ b"\x00\x00\x00\xa2",
+ "Fourth Offset to Paint table from beginning of LayerList (162)",
),
(
- b"\x00\x00\x00\x9e",
- "Fourth Offset to Paint table from beginning of LayerList (158)",
+ b"\x00\x00\x00\xbc",
+ "Fifth Offset to Paint table from beginning of LayerList (188)",
),
# BaseGlyphPaintRecord[2]
(b"\x0a", "BaseGlyphPaintRecord[2].Paint.Format (10)"),
@@ -234,9 +246,15 @@ COLR_V1_SAMPLE = (
(b"\x00\x0d", "LayerList.Paint[2].Glyph (13)"),
(b"\x0c", "LayerList.Paint[2].Paint.Format (12)"),
(b"\x00\x00\x07", "Offset to Paint subtable from beginning of PaintTransform (7)"),
- (b"\x00\x00\x32", "Offset to Affine2x3 subtable from beginning of PaintTransform (50)"),
+ (
+ b"\x00\x00\x32",
+ "Offset to Affine2x3 subtable from beginning of PaintTransform (50)",
+ ),
(b"\x07", "LayerList.Paint[2].Paint.Paint.Format (7)"),
- (b"\x00\x00\x14", "Offset to ColorLine from beginning of PaintVarRadialGradient (20)"),
+ (
+ b"\x00\x00\x14",
+ "Offset to ColorLine from beginning of PaintVarRadialGradient (20)",
+ ),
(b"\x00\x07", "Paint.x0.value (7)"),
(b"\x00\x08", "Paint.y0.value (8)"),
(b"\x00\t", "Paint.r0.value (9)"),
@@ -253,7 +271,6 @@ COLR_V1_SAMPLE = (
(b"@\x00", "ColorLine.ColorStop[1].StopOffset.value (1.0)"),
(b"\x00\x07", "ColorLine.ColorStop[1].PaletteIndex (7)"),
(b"\x19\x9a", "ColorLine.ColorStop[1].Alpha.value (0.4)"),
-
(b"\x00\x00\x00\x07", "VarIndexBase (7)"),
(b"\xff\xf3\x00\x00", "Affine2x3.xx (-13)"),
(b"\x00\x0e\x00\x00", "Affine2x3.xy (14)"),
@@ -261,13 +278,11 @@ COLR_V1_SAMPLE = (
(b"\xff\xef\x00\x00", "Affine2x3.yy (-17)"),
(b"\x00\x12\x00\x00", "Affine2x3.yy (18)"),
(b"\x00\x13\x00\x00", "Affine2x3.yy (19)"),
-
# PaintTranslate
(b"\x0e", "LayerList.Paint[3].Format (14)"),
(b"\x00\x00\x08", "Offset to Paint subtable from beginning of PaintTranslate (8)"),
(b"\x01\x01", "dx (257)"),
(b"\x01\x02", "dy (258)"),
-
# PaintRotateAroundCenter
(b"\x1a", "LayerList.Paint[3].Paint.Format (26)"),
(
@@ -277,7 +292,6 @@ COLR_V1_SAMPLE = (
(b"\x10\x00", "angle (0.25)"),
(b"\x00\xff", "centerX (255)"),
(b"\x01\x00", "centerY (256)"),
-
# PaintSkew
(b"\x1c", "LayerList.Paint[3].Paint.Paint.Format (28)"),
(
@@ -286,39 +300,34 @@ COLR_V1_SAMPLE = (
),
(b"\xfc\x17", "xSkewAngle (-0.0611)"),
(b"\x01\xc7", "ySkewAngle (0.0278)"),
-
- # PaintGlyph
+ # PaintGlyph glyph00011 (pointed to by both PaintSkew above and by LayerList[4] offset)
(b"\x0a", "LayerList.Paint[3].Paint.Paint.Paint.Format (10)"),
(b"\x00\x00\x06", "Offset to Paint subtable from beginning of PaintGlyph (6)"),
(b"\x00\x0b", "LayerList.Paint[2].Glyph (11)"),
-
# PaintSolid
(b"\x02", "LayerList.Paint[0].Paint.Paint.Paint.Paint.Format (2)"),
(b"\x00\x02", "Paint.PaletteIndex (2)"),
(b" \x00", "Paint.Alpha (0.5)"),
-
# ClipList
- (b'\x01', "ClipList.Format (1)"),
- (b'\x00\x00\x00\x02', "ClipList.ClipCount (2)"),
- (b'\x00\x0a', "ClipRecord[0].StartGlyphID (10)"),
- (b'\x00\x0a', "ClipRecord[0].EndGlyphID (10)"),
- (b'\x00\x00\x13', "Offset to ClipBox subtable from beginning of ClipList (19)"),
- (b'\x00\x0e', "ClipRecord[1].StartGlyphID (14)"),
- (b'\x00\x0f', "ClipRecord[1].EndGlyphID (15)"),
- (b'\x00\x00\x20', "Offset to ClipBox subtable from beginning of ClipList (32)"),
-
- (b'\x02', "ClipBox.Format (2)"),
- (b'\x00\x00', "ClipBox.xMin (0)"),
- (b'\x00\x00', "ClipBox.yMin (0)"),
- (b'\x01\xf4', "ClipBox.xMax (500)"),
- (b'\x01\xf4', "ClipBox.yMax (500)"),
- (b'\x00\x00\x00\t', "ClipBox.VarIndexBase (9)"),
-
- (b'\x01', "ClipBox.Format (1)"),
- (b'\x00\x00', "ClipBox.xMin (0)"),
- (b'\x00\x00', "ClipBox.yMin (0)"),
- (b'\x03\xe8', "ClipBox.xMax (1000)"),
- (b'\x03\xe8', "ClipBox.yMax (1000)"),
+ (b"\x01", "ClipList.Format (1)"),
+ (b"\x00\x00\x00\x02", "ClipList.ClipCount (2)"),
+ (b"\x00\x0a", "ClipRecord[0].StartGlyphID (10)"),
+ (b"\x00\x0a", "ClipRecord[0].EndGlyphID (10)"),
+ (b"\x00\x00\x13", "Offset to ClipBox subtable from beginning of ClipList (19)"),
+ (b"\x00\x0e", "ClipRecord[1].StartGlyphID (14)"),
+ (b"\x00\x0f", "ClipRecord[1].EndGlyphID (15)"),
+ (b"\x00\x00\x20", "Offset to ClipBox subtable from beginning of ClipList (32)"),
+ (b"\x02", "ClipBox.Format (2)"),
+ (b"\x00\x00", "ClipBox.xMin (0)"),
+ (b"\x00\x00", "ClipBox.yMin (0)"),
+ (b"\x01\xf4", "ClipBox.xMax (500)"),
+ (b"\x01\xf4", "ClipBox.yMax (500)"),
+ (b"\x00\x00\x00\t", "ClipBox.VarIndexBase (9)"),
+ (b"\x01", "ClipBox.Format (1)"),
+ (b"\x00\x00", "ClipBox.xMin (0)"),
+ (b"\x00\x00", "ClipBox.yMin (0)"),
+ (b"\x03\xe8", "ClipBox.xMax (1000)"),
+ (b"\x03\xe8", "ClipBox.yMax (1000)"),
)
COLR_V1_DATA = b"".join(t[0] for t in COLR_V1_SAMPLE)
@@ -408,7 +417,7 @@ COLR_V1_XML = [
" </BaseGlyphPaintRecord>",
"</BaseGlyphList>",
"<LayerList>",
- " <!-- LayerCount=4 -->",
+ " <!-- LayerCount=5 -->",
' <Paint index="0" Format="10"><!-- PaintGlyph -->',
' <Paint Format="3"><!-- PaintVarSolid -->',
' <PaletteIndex value="2"/>',
@@ -505,6 +514,13 @@ COLR_V1_XML = [
' <dx value="257"/>',
' <dy value="258"/>',
" </Paint>",
+ ' <Paint index="4" Format="10"><!-- PaintGlyph -->',
+ ' <Paint Format="2"><!-- PaintSolid -->',
+ ' <PaletteIndex value="2"/>',
+ ' <Alpha value="0.5"/>',
+ " </Paint>",
+ ' <Glyph value="glyph00011"/>',
+ " </Paint>",
"</LayerList>",
'<ClipList Format="1">',
" <Clip>",
@@ -532,7 +548,7 @@ COLR_V1_XML = [
COLR_V1_VAR_XML = [
'<VarIndexMap Format="0">',
- ' <!-- Omitted values default to 0xFFFF/0xFFFF (no variations) -->',
+ " <!-- Omitted values default to 0xFFFF/0xFFFF (no variations) -->",
' <Map index="0" outer="1" inner="0"/>',
' <Map index="1"/>',
' <Map index="2"/>',
@@ -611,6 +627,26 @@ class COLR_V1_Test(object):
colr.decompile(compiled, font)
assert getXML(colr.toXML, font) == COLR_V1_XML
+ @pytest.mark.parametrize("quantization", [1, 10, 100])
+ @pytest.mark.parametrize("flavor", ["glyf", "cff"])
+ def test_computeClipBoxes(self, flavor, quantization):
+ font = TTFont()
+ font.importXML(TEST_DATA_DIR / f"COLRv1-clip-boxes-{flavor}.ttx")
+ assert font["COLR"].table.ClipList is None
+
+ font["COLR"].table.computeClipBoxes(font.getGlyphSet(), quantization)
+
+ clipList = font["COLR"].table.ClipList
+ assert len(clipList.clips) > 0
+
+ expected = TTFont()
+ expected.importXML(
+ TEST_DATA_DIR / f"COLRv1-clip-boxes-q{quantization}-expected.ttx"
+ )
+ expectedClipList = expected["COLR"].table.ClipList
+
+ assert getXML(clipList.toXML) == getXML(expectedClipList.toXML)
+
class COLR_V1_Variable_Test(object):
def test_round_trip_xml(self, font):
diff --git a/Tests/ttLib/tables/C_P_A_L_test.py b/Tests/ttLib/tables/C_P_A_L_test.py
index 10c8ea0e..0a197099 100644
--- a/Tests/ttLib/tables/C_P_A_L_test.py
+++ b/Tests/ttLib/tables/C_P_A_L_test.py
@@ -5,48 +5,52 @@ import unittest
CPAL_DATA_V0 = deHexStr(
- '0000 0002 ' # version=0, numPaletteEntries=2
- '0002 0004 ' # numPalettes=2, numColorRecords=4
- '00000010 ' # offsetToFirstColorRecord=16
- '0000 0002 ' # colorRecordIndex=[0, 2]
- '000000FF FFCC66FF ' # colorRecord #0, #1 (blue/green/red/alpha)
- '000000FF 000080FF') # colorRecord #2, #3
+ "0000 0002 " # version=0, numPaletteEntries=2
+ "0002 0004 " # numPalettes=2, numColorRecords=4
+ "00000010 " # offsetToFirstColorRecord=16
+ "0000 0002 " # colorRecordIndex=[0, 2]
+ "000000FF FFCC66FF " # colorRecord #0, #1 (blue/green/red/alpha)
+ "000000FF 000080FF"
+) # colorRecord #2, #3
CPAL_DATA_V0_SHARING_COLORS = deHexStr(
- '0000 0003 ' # version=0, numPaletteEntries=3
- '0004 0006 ' # numPalettes=4, numColorRecords=6
- '00000014 ' # offsetToFirstColorRecord=20
- '0000 0000 0003 0000 ' # colorRecordIndex=[0, 0, 3, 0]
- '443322FF 77889911 55555555 ' # colorRecord #0, #1, #2 (BGRA)
- '443322FF 77889911 FFFFFFFF') # colorRecord #3, #4, #5
+ "0000 0003 " # version=0, numPaletteEntries=3
+ "0004 0006 " # numPalettes=4, numColorRecords=6
+ "00000014 " # offsetToFirstColorRecord=20
+ "0000 0000 0003 0000 " # colorRecordIndex=[0, 0, 3, 0]
+ "443322FF 77889911 55555555 " # colorRecord #0, #1, #2 (BGRA)
+ "443322FF 77889911 FFFFFFFF"
+) # colorRecord #3, #4, #5
CPAL_DATA_V1_NOLABELS_NOTYPES = deHexStr(
- '0001 0003 ' # version=1, numPaletteEntries=3
- '0002 0006 ' # numPalettes=2, numColorRecords=6
- '0000001C ' # offsetToFirstColorRecord=28
- '0000 0003 ' # colorRecordIndex=[0, 3]
- '00000000 ' # offsetToPaletteTypeArray=0
- '00000000 ' # offsetToPaletteLabelArray=0
- '00000000 ' # offsetToPaletteEntryLabelArray=0
- 'CAFECAFE 00112233 44556677 ' # colorRecord #0, #1, #2 (BGRA)
- '31415927 42424242 00331337') # colorRecord #3, #4, #5
+ "0001 0003 " # version=1, numPaletteEntries=3
+ "0002 0006 " # numPalettes=2, numColorRecords=6
+ "0000001C " # offsetToFirstColorRecord=28
+ "0000 0003 " # colorRecordIndex=[0, 3]
+ "00000000 " # offsetToPaletteTypeArray=0
+ "00000000 " # offsetToPaletteLabelArray=0
+ "00000000 " # offsetToPaletteEntryLabelArray=0
+ "CAFECAFE 00112233 44556677 " # colorRecord #0, #1, #2 (BGRA)
+ "31415927 42424242 00331337"
+) # colorRecord #3, #4, #5
CPAL_DATA_V1 = deHexStr(
- '0001 0003 ' # version=1, numPaletteEntries=3
- '0002 0006 ' # numPalettes=2, numColorRecords=6
- '0000001C ' # offsetToFirstColorRecord=28
- '0000 0003 ' # colorRecordIndex=[0, 3]
- '00000034 ' # offsetToPaletteTypeArray=52
- '0000003C ' # offsetToPaletteLabelArray=60
- '00000040 ' # offsetToPaletteEntryLabelArray=64
- 'CAFECAFE 00112233 44556677 ' # colorRecord #0, #1, #2 (BGRA)
- '31415927 42424242 00331337 ' # colorRecord #3, #4, #5
- '00000001 00000002 ' # paletteType=[1, 2]
- '0102 0103 ' # paletteLabel=[258, 259]
- '0201 0202 0203') # paletteEntryLabel=[513, 514, 515]
+ "0001 0003 " # version=1, numPaletteEntries=3
+ "0002 0006 " # numPalettes=2, numColorRecords=6
+ "0000001C " # offsetToFirstColorRecord=28
+ "0000 0003 " # colorRecordIndex=[0, 3]
+ "00000034 " # offsetToPaletteTypeArray=52
+ "0000003C " # offsetToPaletteLabelArray=60
+ "00000040 " # offsetToPaletteEntryLabelArray=64
+ "CAFECAFE 00112233 44556677 " # colorRecord #0, #1, #2 (BGRA)
+ "31415927 42424242 00331337 " # colorRecord #3, #4, #5
+ "00000001 00000002 " # paletteType=[1, 2]
+ "0102 0103 " # paletteLabel=[258, 259]
+ "0201 0202 0203"
+) # paletteEntryLabel=[513, 514, 515]
class FakeNameTable(object):
@@ -59,160 +63,188 @@ class FakeNameTable(object):
class CPALTest(unittest.TestCase):
def test_decompile_v0(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V0, ttFont=None)
self.assertEqual(cpal.version, 0)
self.assertEqual(cpal.numPaletteEntries, 2)
- self.assertEqual(repr(cpal.palettes),
- '[[#000000FF, #66CCFFFF], [#000000FF, #800000FF]]')
+ self.assertEqual(
+ repr(cpal.palettes), "[[#000000FF, #66CCFFFF], [#000000FF, #800000FF]]"
+ )
def test_decompile_v0_sharingColors(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V0_SHARING_COLORS, ttFont=None)
self.assertEqual(cpal.version, 0)
self.assertEqual(cpal.numPaletteEntries, 3)
- self.assertEqual([repr(p) for p in cpal.palettes], [
- '[#223344FF, #99887711, #55555555]',
- '[#223344FF, #99887711, #55555555]',
- '[#223344FF, #99887711, #FFFFFFFF]',
- '[#223344FF, #99887711, #55555555]'])
+ self.assertEqual(
+ [repr(p) for p in cpal.palettes],
+ [
+ "[#223344FF, #99887711, #55555555]",
+ "[#223344FF, #99887711, #55555555]",
+ "[#223344FF, #99887711, #FFFFFFFF]",
+ "[#223344FF, #99887711, #55555555]",
+ ],
+ )
def test_decompile_v1_noLabelsNoTypes(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V1_NOLABELS_NOTYPES, ttFont=None)
self.assertEqual(cpal.version, 1)
self.assertEqual(cpal.numPaletteEntries, 3)
- self.assertEqual([repr(p) for p in cpal.palettes], [
- '[#CAFECAFE, #22110033, #66554477]', # RGBA
- '[#59413127, #42424242, #13330037]'])
+ self.assertEqual(
+ [repr(p) for p in cpal.palettes],
+ [
+ "[#CAFECAFE, #22110033, #66554477]", # RGBA
+ "[#59413127, #42424242, #13330037]",
+ ],
+ )
self.assertEqual(cpal.paletteLabels, [cpal.NO_NAME_ID] * len(cpal.palettes))
self.assertEqual(cpal.paletteTypes, [0, 0])
- self.assertEqual(cpal.paletteEntryLabels,
- [cpal.NO_NAME_ID] * cpal.numPaletteEntries)
+ self.assertEqual(
+ cpal.paletteEntryLabels, [cpal.NO_NAME_ID] * cpal.numPaletteEntries
+ )
def test_decompile_v1(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V1, ttFont=None)
self.assertEqual(cpal.version, 1)
self.assertEqual(cpal.numPaletteEntries, 3)
- self.assertEqual([repr(p) for p in cpal.palettes], [
- '[#CAFECAFE, #22110033, #66554477]', # RGBA
- '[#59413127, #42424242, #13330037]'])
+ self.assertEqual(
+ [repr(p) for p in cpal.palettes],
+ [
+ "[#CAFECAFE, #22110033, #66554477]", # RGBA
+ "[#59413127, #42424242, #13330037]",
+ ],
+ )
self.assertEqual(cpal.paletteTypes, [1, 2])
self.assertEqual(cpal.paletteLabels, [258, 259])
self.assertEqual(cpal.paletteEntryLabels, [513, 514, 515])
def test_compile_v0(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V0, ttFont=None)
self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V0)
def test_compile_v0_sharingColors(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.version = 0
- Color = getTableModule('CPAL').Color
- palette1 = [Color(red=0x22, green=0x33, blue=0x44, alpha=0xff),
- Color(red=0x99, green=0x88, blue=0x77, alpha=0x11),
- Color(red=0x55, green=0x55, blue=0x55, alpha=0x55)]
- palette2 = [Color(red=0x22, green=0x33, blue=0x44, alpha=0xff),
- Color(red=0x99, green=0x88, blue=0x77, alpha=0x11),
- Color(red=0xFF, green=0xFF, blue=0xFF, alpha=0xFF)]
+ Color = getTableModule("CPAL").Color
+ palette1 = [
+ Color(red=0x22, green=0x33, blue=0x44, alpha=0xFF),
+ Color(red=0x99, green=0x88, blue=0x77, alpha=0x11),
+ Color(red=0x55, green=0x55, blue=0x55, alpha=0x55),
+ ]
+ palette2 = [
+ Color(red=0x22, green=0x33, blue=0x44, alpha=0xFF),
+ Color(red=0x99, green=0x88, blue=0x77, alpha=0x11),
+ Color(red=0xFF, green=0xFF, blue=0xFF, alpha=0xFF),
+ ]
cpal.numPaletteEntries = len(palette1)
cpal.palettes = [palette1, palette1, palette2, palette1]
- self.assertEqual(cpal.compile(ttFont=None),
- CPAL_DATA_V0_SHARING_COLORS)
+ self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V0_SHARING_COLORS)
def test_compile_v1(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V1, ttFont=None)
self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V1)
def test_compile_v1_noLabelsNoTypes(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V1_NOLABELS_NOTYPES, ttFont=None)
- self.assertEqual(cpal.compile(ttFont=None),
- CPAL_DATA_V1_NOLABELS_NOTYPES)
+ self.assertEqual(cpal.compile(ttFont=None), CPAL_DATA_V1_NOLABELS_NOTYPES)
def test_toXML_v0(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
cpal.decompile(CPAL_DATA_V0, ttFont=None)
- self.assertEqual(getXML(cpal.toXML),
- ['<version value="0"/>',
- '<numPaletteEntries value="2"/>',
- '<palette index="0">',
- ' <color index="0" value="#000000FF"/>',
- ' <color index="1" value="#66CCFFFF"/>',
- '</palette>',
- '<palette index="1">',
- ' <color index="0" value="#000000FF"/>',
- ' <color index="1" value="#800000FF"/>',
- '</palette>'])
+ self.assertEqual(
+ getXML(cpal.toXML),
+ [
+ '<version value="0"/>',
+ '<numPaletteEntries value="2"/>',
+ '<palette index="0">',
+ ' <color index="0" value="#000000FF"/>',
+ ' <color index="1" value="#66CCFFFF"/>',
+ "</palette>",
+ '<palette index="1">',
+ ' <color index="0" value="#000000FF"/>',
+ ' <color index="1" value="#800000FF"/>',
+ "</palette>",
+ ],
+ )
def test_toXML_v1(self):
- name = FakeNameTable({258: "Spring theme", 259: "Winter theme",
- 513: "darks", 515: "lights"})
- cpal = newTable('CPAL')
+ name = FakeNameTable(
+ {258: "Spring theme", 259: "Winter theme", 513: "darks", 515: "lights"}
+ )
+ cpal = newTable("CPAL")
ttFont = {"name": name, "CPAL": cpal}
cpal.decompile(CPAL_DATA_V1, ttFont)
- self.assertEqual(getXML(cpal.toXML, ttFont),
- ['<version value="1"/>',
- '<numPaletteEntries value="3"/>',
- '<palette index="0" label="258" type="1">',
- ' <!-- Spring theme -->',
- ' <color index="0" value="#CAFECAFE"/>',
- ' <color index="1" value="#22110033"/>',
- ' <color index="2" value="#66554477"/>',
- '</palette>',
- '<palette index="1" label="259" type="2">',
- ' <!-- Winter theme -->',
- ' <color index="0" value="#59413127"/>',
- ' <color index="1" value="#42424242"/>',
- ' <color index="2" value="#13330037"/>',
- '</palette>',
- '<paletteEntryLabels>',
- ' <label index="0" value="513"/><!-- darks -->',
- ' <label index="1" value="514"/>',
- ' <label index="2" value="515"/><!-- lights -->',
- '</paletteEntryLabels>'])
+ self.assertEqual(
+ getXML(cpal.toXML, ttFont),
+ [
+ '<version value="1"/>',
+ '<numPaletteEntries value="3"/>',
+ '<palette index="0" label="258" type="1">',
+ " <!-- Spring theme -->",
+ ' <color index="0" value="#CAFECAFE"/>',
+ ' <color index="1" value="#22110033"/>',
+ ' <color index="2" value="#66554477"/>',
+ "</palette>",
+ '<palette index="1" label="259" type="2">',
+ " <!-- Winter theme -->",
+ ' <color index="0" value="#59413127"/>',
+ ' <color index="1" value="#42424242"/>',
+ ' <color index="2" value="#13330037"/>',
+ "</palette>",
+ "<paletteEntryLabels>",
+ ' <label index="0" value="513"/><!-- darks -->',
+ ' <label index="1" value="514"/>',
+ ' <label index="2" value="515"/><!-- lights -->',
+ "</paletteEntryLabels>",
+ ],
+ )
def test_fromXML_v0(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
for name, attrs, content in parseXML(
- '<version value="0"/>'
- '<numPaletteEntries value="2"/>'
- '<palette index="0">'
- ' <color index="0" value="#12345678"/>'
- ' <color index="1" value="#FEDCBA98"/>'
- '</palette>'):
+ '<version value="0"/>'
+ '<numPaletteEntries value="2"/>'
+ '<palette index="0">'
+ ' <color index="0" value="#12345678"/>'
+ ' <color index="1" value="#FEDCBA98"/>'
+ "</palette>"
+ ):
cpal.fromXML(name, attrs, content, ttFont=None)
self.assertEqual(cpal.version, 0)
self.assertEqual(cpal.numPaletteEntries, 2)
- self.assertEqual(repr(cpal.palettes), '[[#12345678, #FEDCBA98]]')
+ self.assertEqual(repr(cpal.palettes), "[[#12345678, #FEDCBA98]]")
def test_fromXML_v1(self):
- cpal = newTable('CPAL')
+ cpal = newTable("CPAL")
for name, attrs, content in parseXML(
- '<version value="1"/>'
- '<numPaletteEntries value="3"/>'
- '<palette index="0" label="259" type="2">'
- ' <color index="0" value="#12345678"/>'
- ' <color index="1" value="#FEDCBA98"/>'
- ' <color index="2" value="#CAFECAFE"/>'
- '</palette>'
- '<paletteEntryLabels>'
- ' <label index="1" value="262"/>'
- '</paletteEntryLabels>'):
+ '<version value="1"/>'
+ '<numPaletteEntries value="3"/>'
+ '<palette index="0" label="259" type="2">'
+ ' <color index="0" value="#12345678"/>'
+ ' <color index="1" value="#FEDCBA98"/>'
+ ' <color index="2" value="#CAFECAFE"/>'
+ "</palette>"
+ "<paletteEntryLabels>"
+ ' <label index="1" value="262"/>'
+ "</paletteEntryLabels>"
+ ):
cpal.fromXML(name, attrs, content, ttFont=None)
self.assertEqual(cpal.version, 1)
self.assertEqual(cpal.numPaletteEntries, 3)
- self.assertEqual(repr(cpal.palettes),
- '[[#12345678, #FEDCBA98, #CAFECAFE]]')
+ self.assertEqual(repr(cpal.palettes), "[[#12345678, #FEDCBA98, #CAFECAFE]]")
self.assertEqual(cpal.paletteLabels, [259])
self.assertEqual(cpal.paletteTypes, [2])
- self.assertEqual(cpal.paletteEntryLabels,
- [cpal.NO_NAME_ID, 262, cpal.NO_NAME_ID])
+ self.assertEqual(
+ cpal.paletteEntryLabels, [cpal.NO_NAME_ID, 262, cpal.NO_NAME_ID]
+ )
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/M_V_A_R_test.py b/Tests/ttLib/tables/M_V_A_R_test.py
index a8b092e0..17d365fe 100644
--- a/Tests/ttLib/tables/M_V_A_R_test.py
+++ b/Tests/ttLib/tables/M_V_A_R_test.py
@@ -6,146 +6,144 @@ import unittest
MVAR_DATA = deHexStr(
- '0001 0000 ' # 0: version=1.0
- '0000 0008 ' # 4: reserved=0, valueRecordSize=8
- '0009 ' # 8: valueRecordCount=9
- '0054 ' # 10: offsetToItemVariationStore=84
- '6861 7363 ' # 12: ValueRecord.valueTag="hasc"
- '0000 ' # 16: ValueRecord.deltaSetOuterIndex
- '0003 ' # 18: ValueRecord.deltaSetInnerIndex
- '6863 6C61 ' # 20: ValueRecord.valueTag="hcla"
- '0000 ' # 24: ValueRecord.deltaSetOuterIndex
- '0003 ' # 26: ValueRecord.deltaSetInnerIndex
- '6863 6C64 ' # 28: ValueRecord.valueTag="hcld"
- '0000 ' # 32: ValueRecord.deltaSetOuterIndex
- '0003 ' # 34: ValueRecord.deltaSetInnerIndex
- '6864 7363 ' # 36: ValueRecord.valueTag="hdsc"
- '0000 ' # 40: ValueRecord.deltaSetOuterIndex
- '0000 ' # 42: ValueRecord.deltaSetInnerIndex
- '686C 6770 ' # 44: ValueRecord.valueTag="hlgp"
- '0000 ' # 48: ValueRecord.deltaSetOuterIndex
- '0002 ' # 50: ValueRecord.deltaSetInnerIndex
- '7362 796F ' # 52: ValueRecord.valueTag="sbyo"
- '0000 ' # 56: ValueRecord.deltaSetOuterIndex
- '0001 ' # 58: ValueRecord.deltaSetInnerIndex
- '7370 796F ' # 60: ValueRecord.valueTag="spyo"
- '0000 ' # 64: ValueRecord.deltaSetOuterIndex
- '0002 ' # 66: ValueRecord.deltaSetInnerIndex
- '7465 7374 ' # 68: ValueRecord.valueTag="test"
- '0000 ' # 72: ValueRecord.deltaSetOuterIndex
- '0002 ' # 74: ValueRecord.deltaSetInnerIndex
- '7465 7332 ' # 76: ValueRecord.valueTag="tes2"
- '0000 ' # 78: ValueRecord.deltaSetOuterIndex
- '0002 ' # 82: ValueRecord.deltaSetInnerIndex
- '0001 ' # 84: VarStore.format=1
- '0000 000C ' # 86: VarStore.offsetToVariationRegionList=12
- '0001 ' # 90: VarStore.itemVariationDataCount=1
- '0000 0016 ' # 92: VarStore.itemVariationDataOffsets[0]=22
- '0001 ' # 96: VarRegionList.axisCount=1
- '0001 ' # 98: VarRegionList.regionCount=1
- '0000 ' # 100: variationRegions[0].regionAxes[0].startCoord=0.0
- '4000 ' # 102: variationRegions[0].regionAxes[0].peakCoord=1.0
- '4000 ' # 104: variationRegions[0].regionAxes[0].endCoord=1.0
- '0004 ' # 106: VarData.ItemCount=4
- '0001 ' # 108: VarData.NumShorts=1
- '0001 ' # 110: VarData.VarRegionCount=1
- '0000 ' # 112: VarData.VarRegionIndex[0]=0
- 'FF38 ' # 114: VarData.deltaSets[0]=-200
- 'FFCE ' # 116: VarData.deltaSets[0]=-50
- '0064 ' # 118: VarData.deltaSets[0]=100
- '00C8 ' # 120: VarData.deltaSets[0]=200
+ "0001 0000 " # 0: version=1.0
+ "0000 0008 " # 4: reserved=0, valueRecordSize=8
+ "0009 " # 8: valueRecordCount=9
+ "0054 " # 10: offsetToItemVariationStore=84
+ "6861 7363 " # 12: ValueRecord.valueTag="hasc"
+ "0000 " # 16: ValueRecord.deltaSetOuterIndex
+ "0003 " # 18: ValueRecord.deltaSetInnerIndex
+ "6863 6C61 " # 20: ValueRecord.valueTag="hcla"
+ "0000 " # 24: ValueRecord.deltaSetOuterIndex
+ "0003 " # 26: ValueRecord.deltaSetInnerIndex
+ "6863 6C64 " # 28: ValueRecord.valueTag="hcld"
+ "0000 " # 32: ValueRecord.deltaSetOuterIndex
+ "0003 " # 34: ValueRecord.deltaSetInnerIndex
+ "6864 7363 " # 36: ValueRecord.valueTag="hdsc"
+ "0000 " # 40: ValueRecord.deltaSetOuterIndex
+ "0000 " # 42: ValueRecord.deltaSetInnerIndex
+ "686C 6770 " # 44: ValueRecord.valueTag="hlgp"
+ "0000 " # 48: ValueRecord.deltaSetOuterIndex
+ "0002 " # 50: ValueRecord.deltaSetInnerIndex
+ "7362 796F " # 52: ValueRecord.valueTag="sbyo"
+ "0000 " # 56: ValueRecord.deltaSetOuterIndex
+ "0001 " # 58: ValueRecord.deltaSetInnerIndex
+ "7370 796F " # 60: ValueRecord.valueTag="spyo"
+ "0000 " # 64: ValueRecord.deltaSetOuterIndex
+ "0002 " # 66: ValueRecord.deltaSetInnerIndex
+ "7465 7374 " # 68: ValueRecord.valueTag="test"
+ "0000 " # 72: ValueRecord.deltaSetOuterIndex
+ "0002 " # 74: ValueRecord.deltaSetInnerIndex
+ "7465 7332 " # 76: ValueRecord.valueTag="tes2"
+ "0000 " # 78: ValueRecord.deltaSetOuterIndex
+ "0002 " # 82: ValueRecord.deltaSetInnerIndex
+ "0001 " # 84: VarStore.format=1
+ "0000 000C " # 86: VarStore.offsetToVariationRegionList=12
+ "0001 " # 90: VarStore.itemVariationDataCount=1
+ "0000 0016 " # 92: VarStore.itemVariationDataOffsets[0]=22
+ "0001 " # 96: VarRegionList.axisCount=1
+ "0001 " # 98: VarRegionList.regionCount=1
+ "0000 " # 100: variationRegions[0].regionAxes[0].startCoord=0.0
+ "4000 " # 102: variationRegions[0].regionAxes[0].peakCoord=1.0
+ "4000 " # 104: variationRegions[0].regionAxes[0].endCoord=1.0
+ "0004 " # 106: VarData.ItemCount=4
+ "0001 " # 108: VarData.NumShorts=1
+ "0001 " # 110: VarData.VarRegionCount=1
+ "0000 " # 112: VarData.VarRegionIndex[0]=0
+ "FF38 " # 114: VarData.deltaSets[0]=-200
+ "FFCE " # 116: VarData.deltaSets[0]=-50
+ "0064 " # 118: VarData.deltaSets[0]=100
+ "00C8 " # 120: VarData.deltaSets[0]=200
)
MVAR_XML = [
'<Version value="0x00010000"/>',
'<Reserved value="0"/>',
'<ValueRecordSize value="8"/>',
- '<!-- ValueRecordCount=9 -->',
+ "<!-- ValueRecordCount=9 -->",
'<VarStore Format="1">',
' <Format value="1"/>',
- ' <VarRegionList>',
- ' <!-- RegionAxisCount=1 -->',
- ' <!-- RegionCount=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 -->',
+ " </VarRegionAxis>",
+ " </Region>",
+ " </VarRegionList>",
+ " <!-- VarDataCount=1 -->",
' <VarData index="0">',
- ' <!-- ItemCount=4 -->',
+ " <!-- ItemCount=4 -->",
' <NumShorts value="1"/>',
- ' <!-- VarRegionCount=1 -->',
+ " <!-- VarRegionCount=1 -->",
' <VarRegionIndex index="0" value="0"/>',
' <Item index="0" value="[-200]"/>',
' <Item index="1" value="[-50]"/>',
' <Item index="2" value="[100]"/>',
' <Item index="3" value="[200]"/>',
- ' </VarData>',
- '</VarStore>',
+ " </VarData>",
+ "</VarStore>",
'<ValueRecord index="0">',
' <ValueTag value="hasc"/>',
' <VarIdx value="3"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="1">',
' <ValueTag value="hcla"/>',
' <VarIdx value="3"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="2">',
' <ValueTag value="hcld"/>',
' <VarIdx value="3"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="3">',
' <ValueTag value="hdsc"/>',
' <VarIdx value="0"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="4">',
' <ValueTag value="hlgp"/>',
' <VarIdx value="2"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="5">',
' <ValueTag value="sbyo"/>',
' <VarIdx value="1"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="6">',
' <ValueTag value="spyo"/>',
' <VarIdx value="2"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="7">',
' <ValueTag value="test"/>',
' <VarIdx value="2"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
'<ValueRecord index="8">',
' <ValueTag value="tes2"/>',
' <VarIdx value="2"/>',
- '</ValueRecord>',
+ "</ValueRecord>",
]
class MVARTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
def test_decompile_toXML(self):
- mvar = newTable('MVAR')
+ mvar = newTable("MVAR")
font = TTFont()
mvar.decompile(MVAR_DATA, font)
self.assertEqual(getXML(mvar.toXML), MVAR_XML)
-
def test_decompile_toXML_lazy(self):
- mvar = newTable('MVAR')
+ mvar = newTable("MVAR")
font = TTFont(lazy=True)
mvar.decompile(MVAR_DATA, font)
self.assertEqual(getXML(mvar.toXML), MVAR_XML)
def test_compile_fromXML(self):
- mvar = newTable('MVAR')
+ mvar = newTable("MVAR")
font = TTFont()
for name, attrs, content in parseXML(MVAR_XML):
mvar.fromXML(name, attrs, content, font=font)
@@ -153,6 +151,7 @@ class MVARTest(unittest.TestCase):
self.assertEqual(hexStr(mvar.compile(font)), hexStr(data))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/O_S_2f_2_test.py b/Tests/ttLib/tables/O_S_2f_2_test.py
index 1f123090..9567b9ec 100644
--- a/Tests/ttLib/tables/O_S_2f_2_test.py
+++ b/Tests/ttLib/tables/O_S_2f_2_test.py
@@ -4,58 +4,59 @@ import unittest
class OS2TableTest(unittest.TestCase):
-
- def test_getUnicodeRanges(self):
- table = table_O_S_2f_2()
- table.ulUnicodeRange1 = 0xFFFFFFFF
- table.ulUnicodeRange2 = 0xFFFFFFFF
- table.ulUnicodeRange3 = 0xFFFFFFFF
- table.ulUnicodeRange4 = 0xFFFFFFFF
- bits = table.getUnicodeRanges()
- for i in range(127):
- self.assertIn(i, bits)
-
- def test_setUnicodeRanges(self):
- table = table_O_S_2f_2()
- table.ulUnicodeRange1 = 0
- table.ulUnicodeRange2 = 0
- table.ulUnicodeRange3 = 0
- table.ulUnicodeRange4 = 0
- bits = set(range(123))
- table.setUnicodeRanges(bits)
- self.assertEqual(table.getUnicodeRanges(), bits)
- with self.assertRaises(ValueError):
- table.setUnicodeRanges([-1, 127, 255])
-
- def test_recalcUnicodeRanges(self):
- font = TTFont()
- font['OS/2'] = os2 = newTable('OS/2')
- font['cmap'] = cmap = newTable('cmap')
- st = getTableModule('cmap').CmapSubtable.newSubtable(4)
- st.platformID, st.platEncID, st.language = 3, 1, 0
- st.cmap = {0x0041:'A', 0x03B1: 'alpha', 0x0410: 'Acyr'}
- cmap.tables = []
- cmap.tables.append(st)
- os2.setUnicodeRanges({0, 1, 9})
- # 'pruneOnly' will clear any bits for which there's no intersection:
- # bit 1 ('Latin 1 Supplement'), in this case. However, it won't set
- # bit 7 ('Greek and Coptic') despite the "alpha" character is present.
- self.assertEqual(os2.recalcUnicodeRanges(font, pruneOnly=True), {0, 9})
- # try again with pruneOnly=False: bit 7 is now set.
- self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9})
- # add a non-BMP char from 'Mahjong Tiles' block (bit 122)
- st.cmap[0x1F000] = 'eastwindtile'
- # the bit 122 and the special bit 57 ('Non Plane 0') are also enabled
- self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9, 57, 122})
-
- def test_intersectUnicodeRanges(self):
- self.assertEqual(intersectUnicodeRanges([0x0410]), {9})
- self.assertEqual(intersectUnicodeRanges([0x0410, 0x1F000]), {9, 57, 122})
- self.assertEqual(
- intersectUnicodeRanges([0x0410, 0x1F000], inverse=True),
- (set(range(123)) - {9, 57, 122}))
+ def test_getUnicodeRanges(self):
+ table = table_O_S_2f_2()
+ table.ulUnicodeRange1 = 0xFFFFFFFF
+ table.ulUnicodeRange2 = 0xFFFFFFFF
+ table.ulUnicodeRange3 = 0xFFFFFFFF
+ table.ulUnicodeRange4 = 0xFFFFFFFF
+ bits = table.getUnicodeRanges()
+ for i in range(127):
+ self.assertIn(i, bits)
+
+ def test_setUnicodeRanges(self):
+ table = table_O_S_2f_2()
+ table.ulUnicodeRange1 = 0
+ table.ulUnicodeRange2 = 0
+ table.ulUnicodeRange3 = 0
+ table.ulUnicodeRange4 = 0
+ bits = set(range(123))
+ table.setUnicodeRanges(bits)
+ self.assertEqual(table.getUnicodeRanges(), bits)
+ with self.assertRaises(ValueError):
+ table.setUnicodeRanges([-1, 127, 255])
+
+ def test_recalcUnicodeRanges(self):
+ font = TTFont()
+ font["OS/2"] = os2 = newTable("OS/2")
+ font["cmap"] = cmap = newTable("cmap")
+ st = getTableModule("cmap").CmapSubtable.newSubtable(4)
+ st.platformID, st.platEncID, st.language = 3, 1, 0
+ st.cmap = {0x0041: "A", 0x03B1: "alpha", 0x0410: "Acyr"}
+ cmap.tables = []
+ cmap.tables.append(st)
+ os2.setUnicodeRanges({0, 1, 9})
+ # 'pruneOnly' will clear any bits for which there's no intersection:
+ # bit 1 ('Latin 1 Supplement'), in this case. However, it won't set
+ # bit 7 ('Greek and Coptic') despite the "alpha" character is present.
+ self.assertEqual(os2.recalcUnicodeRanges(font, pruneOnly=True), {0, 9})
+ # try again with pruneOnly=False: bit 7 is now set.
+ self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9})
+ # add a non-BMP char from 'Mahjong Tiles' block (bit 122)
+ st.cmap[0x1F000] = "eastwindtile"
+ # the bit 122 and the special bit 57 ('Non Plane 0') are also enabled
+ self.assertEqual(os2.recalcUnicodeRanges(font), {0, 7, 9, 57, 122})
+
+ def test_intersectUnicodeRanges(self):
+ self.assertEqual(intersectUnicodeRanges([0x0410]), {9})
+ self.assertEqual(intersectUnicodeRanges([0x0410, 0x1F000]), {9, 57, 122})
+ self.assertEqual(
+ intersectUnicodeRanges([0x0410, 0x1F000], inverse=True),
+ (set(range(123)) - {9, 57, 122}),
+ )
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/S_T_A_T_test.py b/Tests/ttLib/tables/S_T_A_T_test.py
index c5c12341..947ff195 100644
--- a/Tests/ttLib/tables/S_T_A_T_test.py
+++ b/Tests/ttLib/tables/S_T_A_T_test.py
@@ -5,53 +5,53 @@ import unittest
STAT_DATA = deHexStr(
- '0001 0000 ' # 0: Version=1.0
- '0008 0002 ' # 4: DesignAxisSize=8, DesignAxisCount=2
- '0000 0012 ' # 8: OffsetToDesignAxes=18
- '0003 0000 0022 ' # 12: AxisValueCount=3, OffsetToAxisValueOffsets=34
- '7767 6874 ' # 18: DesignAxis[0].AxisTag='wght'
- '012D 0002 ' # 22: DesignAxis[0].NameID=301, .AxisOrdering=2
- '5445 5354 ' # 26: DesignAxis[1].AxisTag='TEST'
- '012E 0001 ' # 30: DesignAxis[1].NameID=302, .AxisOrdering=1
- '0006 0012 0026 ' # 34: AxisValueOffsets = [6, 18, 38] (+34)
- '0001 0000 0000 ' # 40: AxisValue[0].Format=1, .AxisIndex=0, .Flags=0
- '0191 0190 0000 ' # 46: AxisValue[0].ValueNameID=401, .Value=400.0
- '0002 0001 0000 ' # 52: AxisValue[1].Format=2, .AxisIndex=1, .Flags=0
- '0192 ' # 58: AxisValue[1].ValueNameID=402
- '0002 0000 ' # 60: AxisValue[1].NominalValue=2.0
- '0001 0000 ' # 64: AxisValue[1].RangeMinValue=1.0
- '0003 0000 ' # 68: AxisValue[1].RangeMaxValue=3.0
- '0003 0000 0000 ' # 72: AxisValue[2].Format=3, .AxisIndex=0, .Flags=0
- '0002 ' # 78: AxisValue[2].ValueNameID=2 'Regular'
- '0190 0000 02BC 0000 ' # 80: AxisValue[2].Value=400.0, .LinkedValue=700.0
-) # 88: <end>
-assert(len(STAT_DATA) == 88)
+ "0001 0000 " # 0: Version=1.0
+ "0008 0002 " # 4: DesignAxisSize=8, DesignAxisCount=2
+ "0000 0012 " # 8: OffsetToDesignAxes=18
+ "0003 0000 0022 " # 12: AxisValueCount=3, OffsetToAxisValueOffsets=34
+ "7767 6874 " # 18: DesignAxis[0].AxisTag='wght'
+ "012D 0002 " # 22: DesignAxis[0].NameID=301, .AxisOrdering=2
+ "5445 5354 " # 26: DesignAxis[1].AxisTag='TEST'
+ "012E 0001 " # 30: DesignAxis[1].NameID=302, .AxisOrdering=1
+ "0006 0012 0026 " # 34: AxisValueOffsets = [6, 18, 38] (+34)
+ "0001 0000 0000 " # 40: AxisValue[0].Format=1, .AxisIndex=0, .Flags=0
+ "0191 0190 0000 " # 46: AxisValue[0].ValueNameID=401, .Value=400.0
+ "0002 0001 0000 " # 52: AxisValue[1].Format=2, .AxisIndex=1, .Flags=0
+ "0192 " # 58: AxisValue[1].ValueNameID=402
+ "0002 0000 " # 60: AxisValue[1].NominalValue=2.0
+ "0001 0000 " # 64: AxisValue[1].RangeMinValue=1.0
+ "0003 0000 " # 68: AxisValue[1].RangeMaxValue=3.0
+ "0003 0000 0000 " # 72: AxisValue[2].Format=3, .AxisIndex=0, .Flags=0
+ "0002 " # 78: AxisValue[2].ValueNameID=2 'Regular'
+ "0190 0000 02BC 0000 " # 80: AxisValue[2].Value=400.0, .LinkedValue=700.0
+) # 88: <end>
+assert len(STAT_DATA) == 88
STAT_XML = [
'<Version value="0x00010000"/>',
'<DesignAxisRecordSize value="8"/>',
- '<!-- DesignAxisCount=2 -->',
- '<DesignAxisRecord>',
+ "<!-- DesignAxisCount=2 -->",
+ "<DesignAxisRecord>",
' <Axis index="0">',
' <AxisTag value="wght"/>',
' <AxisNameID value="301"/>',
' <AxisOrdering value="2"/>',
- ' </Axis>',
+ " </Axis>",
' <Axis index="1">',
' <AxisTag value="TEST"/>',
' <AxisNameID value="302"/>',
' <AxisOrdering value="1"/>',
- ' </Axis>',
- '</DesignAxisRecord>',
- '<!-- AxisValueCount=3 -->',
- '<AxisValueArray>',
+ " </Axis>",
+ "</DesignAxisRecord>",
+ "<!-- AxisValueCount=3 -->",
+ "<AxisValueArray>",
' <AxisValue index="0" Format="1">',
' <AxisIndex value="0"/>',
' <Flags value="0"/>',
' <ValueNameID value="401"/>',
' <Value value="400.0"/>',
- ' </AxisValue>',
+ " </AxisValue>",
' <AxisValue index="1" Format="2">',
' <AxisIndex value="1"/>',
' <Flags value="0"/>',
@@ -59,204 +59,202 @@ STAT_XML = [
' <NominalValue value="2.0"/>',
' <RangeMinValue value="1.0"/>',
' <RangeMaxValue value="3.0"/>',
- ' </AxisValue>',
+ " </AxisValue>",
' <AxisValue index="2" Format="3">',
' <AxisIndex value="0"/>',
' <Flags value="0"/>',
' <ValueNameID value="2"/>',
' <Value value="400.0"/>',
' <LinkedValue value="700.0"/>',
- ' </AxisValue>',
- '</AxisValueArray>',
+ " </AxisValue>",
+ "</AxisValueArray>",
]
# Contains junk data for making sure we get our offset decoding right.
STAT_DATA_WITH_AXIS_JUNK = deHexStr(
- '0001 0000 ' # 0: Version=1.0
- '000A 0002 ' # 4: DesignAxisSize=10, DesignAxisCount=2
- '0000 0012 ' # 8: OffsetToDesignAxes=18
- '0000 0000 0000 ' # 12: AxisValueCount=3, OffsetToAxisValueOffsets=34
- '7767 6874 ' # 18: DesignAxis[0].AxisTag='wght'
- '012D 0002 ' # 22: DesignAxis[0].NameID=301, .AxisOrdering=2
- 'DEAD ' # 26: <junk>
- '5445 5354 ' # 28: DesignAxis[1].AxisTag='TEST'
- '012E 0001 ' # 32: DesignAxis[1].NameID=302, .AxisOrdering=1
- 'BEEF ' # 36: <junk>
-) # 38: <end>
+ "0001 0000 " # 0: Version=1.0
+ "000A 0002 " # 4: DesignAxisSize=10, DesignAxisCount=2
+ "0000 0012 " # 8: OffsetToDesignAxes=18
+ "0000 0000 0000 " # 12: AxisValueCount=3, OffsetToAxisValueOffsets=34
+ "7767 6874 " # 18: DesignAxis[0].AxisTag='wght'
+ "012D 0002 " # 22: DesignAxis[0].NameID=301, .AxisOrdering=2
+ "DEAD " # 26: <junk>
+ "5445 5354 " # 28: DesignAxis[1].AxisTag='TEST'
+ "012E 0001 " # 32: DesignAxis[1].NameID=302, .AxisOrdering=1
+ "BEEF " # 36: <junk>
+) # 38: <end>
-assert(len(STAT_DATA_WITH_AXIS_JUNK) == 38)
+assert len(STAT_DATA_WITH_AXIS_JUNK) == 38
STAT_XML_WITH_AXIS_JUNK = [
'<Version value="0x00010000"/>',
'<DesignAxisRecordSize value="10"/>',
- '<!-- DesignAxisCount=2 -->',
- '<DesignAxisRecord>',
+ "<!-- DesignAxisCount=2 -->",
+ "<DesignAxisRecord>",
' <Axis index="0">',
' <AxisTag value="wght"/>',
' <AxisNameID value="301"/>',
' <AxisOrdering value="2"/>',
' <MoreBytes index="0" value="222"/>', # 0xDE
' <MoreBytes index="1" value="173"/>', # 0xAD
- ' </Axis>',
+ " </Axis>",
' <Axis index="1">',
' <AxisTag value="TEST"/>',
' <AxisNameID value="302"/>',
' <AxisOrdering value="1"/>',
' <MoreBytes index="0" value="190"/>', # 0xBE
' <MoreBytes index="1" value="239"/>', # 0xEF
- ' </Axis>',
- '</DesignAxisRecord>',
- '<!-- AxisValueCount=0 -->',
+ " </Axis>",
+ "</DesignAxisRecord>",
+ "<!-- AxisValueCount=0 -->",
]
STAT_DATA_AXIS_VALUE_FORMAT3 = deHexStr(
- '0001 0000 ' # 0: Version=1.0
- '0008 0001 ' # 4: DesignAxisSize=8, DesignAxisCount=1
- '0000 0012 ' # 8: OffsetToDesignAxes=18
- '0001 ' # 12: AxisValueCount=1
- '0000 001A ' # 14: OffsetToAxisValueOffsets=26
- '7767 6874 ' # 18: DesignAxis[0].AxisTag='wght'
- '0102 ' # 22: DesignAxis[0].AxisNameID=258 'Weight'
- '0000 ' # 24: DesignAxis[0].AxisOrdering=0
- '0002 ' # 26: AxisValueOffsets=[2] (+26)
- '0003 ' # 28: AxisValue[0].Format=3
- '0000 0002 ' # 30: AxisValue[0].AxisIndex=0, .Flags=0x2
- '0002 ' # 34: AxisValue[0].ValueNameID=2 'Regular'
- '0190 0000 ' # 36: AxisValue[0].Value=400.0
- '02BC 0000 ' # 40: AxisValue[0].LinkedValue=700.0
-) # 44: <end>
-assert(len(STAT_DATA_AXIS_VALUE_FORMAT3) == 44)
+ "0001 0000 " # 0: Version=1.0
+ "0008 0001 " # 4: DesignAxisSize=8, DesignAxisCount=1
+ "0000 0012 " # 8: OffsetToDesignAxes=18
+ "0001 " # 12: AxisValueCount=1
+ "0000 001A " # 14: OffsetToAxisValueOffsets=26
+ "7767 6874 " # 18: DesignAxis[0].AxisTag='wght'
+ "0102 " # 22: DesignAxis[0].AxisNameID=258 'Weight'
+ "0000 " # 24: DesignAxis[0].AxisOrdering=0
+ "0002 " # 26: AxisValueOffsets=[2] (+26)
+ "0003 " # 28: AxisValue[0].Format=3
+ "0000 0002 " # 30: AxisValue[0].AxisIndex=0, .Flags=0x2
+ "0002 " # 34: AxisValue[0].ValueNameID=2 'Regular'
+ "0190 0000 " # 36: AxisValue[0].Value=400.0
+ "02BC 0000 " # 40: AxisValue[0].LinkedValue=700.0
+) # 44: <end>
+assert len(STAT_DATA_AXIS_VALUE_FORMAT3) == 44
STAT_XML_AXIS_VALUE_FORMAT3 = [
'<Version value="0x00010000"/>',
'<DesignAxisRecordSize value="8"/>',
- '<!-- DesignAxisCount=1 -->',
- '<DesignAxisRecord>',
+ "<!-- DesignAxisCount=1 -->",
+ "<DesignAxisRecord>",
' <Axis index="0">',
' <AxisTag value="wght"/>',
' <AxisNameID value="258"/>',
' <AxisOrdering value="0"/>',
- ' </Axis>',
- '</DesignAxisRecord>',
- '<!-- AxisValueCount=1 -->',
- '<AxisValueArray>',
+ " </Axis>",
+ "</DesignAxisRecord>",
+ "<!-- AxisValueCount=1 -->",
+ "<AxisValueArray>",
' <AxisValue index="0" Format="3">',
' <AxisIndex value="0"/>',
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
' <ValueNameID value="2"/>',
' <Value value="400.0"/>',
' <LinkedValue value="700.0"/>',
- ' </AxisValue>',
- '</AxisValueArray>',
+ " </AxisValue>",
+ "</AxisValueArray>",
]
STAT_DATA_VERSION_1_1 = deHexStr(
- '0001 0001 ' # 0: Version=1.1
- '0008 0001 ' # 4: DesignAxisSize=8, DesignAxisCount=1
- '0000 0014 ' # 8: OffsetToDesignAxes=20
- '0001 ' # 12: AxisValueCount=1
- '0000 001C ' # 14: OffsetToAxisValueOffsets=28
- '0101 ' # 18: ElidedFallbackNameID: 257
- '7767 6874 ' # 20: DesignAxis[0].AxisTag='wght'
- '0102 ' # 24: DesignAxis[0].AxisNameID=258 'Weight'
- '0000 ' # 26: DesignAxis[0].AxisOrdering=0
- '0002 ' # 28: AxisValueOffsets=[2] (+28)
- '0003 ' # 30: AxisValue[0].Format=3
- '0000 0002 ' # 32: AxisValue[0].AxisIndex=0, .Flags=0x2
- '0002 ' # 36: AxisValue[0].ValueNameID=2 'Regular'
- '0190 0000 ' # 38: AxisValue[0].Value=400.0
- '02BC 0000 ' # 42: AxisValue[0].LinkedValue=700.0
-) # 46: <end>
-assert(len(STAT_DATA_VERSION_1_1) == 46)
+ "0001 0001 " # 0: Version=1.1
+ "0008 0001 " # 4: DesignAxisSize=8, DesignAxisCount=1
+ "0000 0014 " # 8: OffsetToDesignAxes=20
+ "0001 " # 12: AxisValueCount=1
+ "0000 001C " # 14: OffsetToAxisValueOffsets=28
+ "0101 " # 18: ElidedFallbackNameID: 257
+ "7767 6874 " # 20: DesignAxis[0].AxisTag='wght'
+ "0102 " # 24: DesignAxis[0].AxisNameID=258 'Weight'
+ "0000 " # 26: DesignAxis[0].AxisOrdering=0
+ "0002 " # 28: AxisValueOffsets=[2] (+28)
+ "0003 " # 30: AxisValue[0].Format=3
+ "0000 0002 " # 32: AxisValue[0].AxisIndex=0, .Flags=0x2
+ "0002 " # 36: AxisValue[0].ValueNameID=2 'Regular'
+ "0190 0000 " # 38: AxisValue[0].Value=400.0
+ "02BC 0000 " # 42: AxisValue[0].LinkedValue=700.0
+) # 46: <end>
+assert len(STAT_DATA_VERSION_1_1) == 46
STAT_XML_VERSION_1_1 = [
'<Version value="0x00010001"/>',
'<DesignAxisRecordSize value="8"/>',
- '<!-- DesignAxisCount=1 -->',
- '<DesignAxisRecord>',
+ "<!-- DesignAxisCount=1 -->",
+ "<DesignAxisRecord>",
' <Axis index="0">',
' <AxisTag value="wght"/>',
' <AxisNameID value="258"/>',
' <AxisOrdering value="0"/>',
- ' </Axis>',
- '</DesignAxisRecord>',
- '<!-- AxisValueCount=1 -->',
- '<AxisValueArray>',
+ " </Axis>",
+ "</DesignAxisRecord>",
+ "<!-- AxisValueCount=1 -->",
+ "<AxisValueArray>",
' <AxisValue index="0" Format="3">',
' <AxisIndex value="0"/>',
' <Flags value="2"/> <!-- ElidableAxisValueName -->',
' <ValueNameID value="2"/>',
' <Value value="400.0"/>',
' <LinkedValue value="700.0"/>',
- ' </AxisValue>',
- '</AxisValueArray>',
+ " </AxisValue>",
+ "</AxisValueArray>",
'<ElidedFallbackNameID value="257"/>',
]
class STATTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
def test_decompile_toXML(self):
- table = newTable('STAT')
- table.decompile(STAT_DATA, font=FakeFont(['.notdef']))
+ table = newTable("STAT")
+ table.decompile(STAT_DATA, font=FakeFont([".notdef"]))
self.assertEqual(getXML(table.toXML), STAT_XML)
def test_decompile_toXML_withAxisJunk(self):
- table = newTable('STAT')
- table.decompile(STAT_DATA_WITH_AXIS_JUNK, font=FakeFont(['.notdef']))
+ table = newTable("STAT")
+ table.decompile(STAT_DATA_WITH_AXIS_JUNK, font=FakeFont([".notdef"]))
self.assertEqual(getXML(table.toXML), STAT_XML_WITH_AXIS_JUNK)
def test_decompile_toXML_format3(self):
- table = newTable('STAT')
- table.decompile(STAT_DATA_AXIS_VALUE_FORMAT3,
- font=FakeFont(['.notdef']))
+ table = newTable("STAT")
+ table.decompile(STAT_DATA_AXIS_VALUE_FORMAT3, font=FakeFont([".notdef"]))
self.assertEqual(getXML(table.toXML), STAT_XML_AXIS_VALUE_FORMAT3)
def test_decompile_toXML_version_1_1(self):
- table = newTable('STAT')
- table.decompile(STAT_DATA_VERSION_1_1,
- font=FakeFont(['.notdef']))
+ table = newTable("STAT")
+ table.decompile(STAT_DATA_VERSION_1_1, font=FakeFont([".notdef"]))
self.assertEqual(getXML(table.toXML), STAT_XML_VERSION_1_1)
def test_compile_fromXML(self):
- table = newTable('STAT')
- font = FakeFont(['.notdef'])
+ table = newTable("STAT")
+ font = FakeFont([".notdef"])
for name, attrs, content in parseXML(STAT_XML):
table.fromXML(name, attrs, content, font=font)
self.assertEqual(table.compile(font), STAT_DATA)
def test_compile_fromXML_withAxisJunk(self):
- table = newTable('STAT')
- font = FakeFont(['.notdef'])
+ table = newTable("STAT")
+ font = FakeFont([".notdef"])
for name, attrs, content in parseXML(STAT_XML_WITH_AXIS_JUNK):
table.fromXML(name, attrs, content, font=font)
self.assertEqual(table.compile(font), STAT_DATA_WITH_AXIS_JUNK)
def test_compile_fromXML_format3(self):
- table = newTable('STAT')
- font = FakeFont(['.notdef'])
+ table = newTable("STAT")
+ font = FakeFont([".notdef"])
for name, attrs, content in parseXML(STAT_XML_AXIS_VALUE_FORMAT3):
table.fromXML(name, attrs, content, font=font)
self.assertEqual(table.compile(font), STAT_DATA_AXIS_VALUE_FORMAT3)
def test_compile_fromXML_version_1_1(self):
- table = newTable('STAT')
- font = FakeFont(['.notdef'])
+ table = newTable("STAT")
+ font = FakeFont([".notdef"])
for name, attrs, content in parseXML(STAT_XML_VERSION_1_1):
table.fromXML(name, attrs, content, font=font)
self.assertEqual(table.compile(font), STAT_DATA_VERSION_1_1)
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/T_S_I__0_test.py b/Tests/ttLib/tables/T_S_I__0_test.py
index 44ca44ed..871ece3d 100644
--- a/Tests/ttLib/tables/T_S_I__0_test.py
+++ b/Tests/ttLib/tables/T_S_I__0_test.py
@@ -6,19 +6,15 @@ import pytest
# (gid, length, offset) for glyph programs
-TSI0_INDICES = [
- (0, 1, 0),
- (1, 5, 1),
- (2, 0, 1),
- (3, 0, 1),
- (4, 8, 6)]
+TSI0_INDICES = [(0, 1, 0), (1, 5, 1), (2, 0, 1), (3, 0, 1), (4, 8, 6)]
# (type, length, offset) for 'extra' programs
TSI0_EXTRA_INDICES = [
- (0xFFFA, 2, 14), # ppgm
- (0xFFFB, 4, 16), # cvt
- (0xFFFC, 6, 20), # reserved
- (0xFFFD, 10, 26)] # fpgm
+ (0xFFFA, 2, 14), # ppgm
+ (0xFFFB, 4, 16), # cvt
+ (0xFFFC, 6, 20), # reserved
+ (0xFFFD, 10, 26),
+] # fpgm
# compiled TSI0 table from data above
TSI0_DATA = deHexStr(
@@ -27,25 +23,28 @@ TSI0_DATA = deHexStr(
"0002 0000 00000001"
"0003 0000 00000001"
"0004 0008 00000006"
- "FFFE 0000 ABFC1F34" # 'magic' separates glyph from extra programs
+ "FFFE 0000 ABFC1F34" # 'magic' separates glyph from extra programs
"FFFA 0002 0000000E"
"FFFB 0004 00000010"
"FFFC 0006 00000014"
- "FFFD 000A 0000001A")
+ "FFFD 000A 0000001A"
+)
# empty font has no glyph programs but 4 extra programs are always present
EMPTY_TSI0_EXTRA_INDICES = [
(0xFFFA, 0, 0),
(0xFFFB, 0, 0),
(0xFFFC, 0, 0),
- (0xFFFD, 0, 0)]
+ (0xFFFD, 0, 0),
+]
EMPTY_TSI0_DATA = deHexStr(
"FFFE 0000 ABFC1F34"
"FFFA 0000 00000000"
"FFFB 0000 00000000"
"FFFC 0000 00000000"
- "FFFD 0000 00000000")
+ "FFFD 0000 00000000"
+)
@pytest.fixture
@@ -57,13 +56,12 @@ def table():
"numGlyphs, data, expected_indices, expected_extra_indices",
[
(5, TSI0_DATA, TSI0_INDICES, TSI0_EXTRA_INDICES),
- (0, EMPTY_TSI0_DATA, [], EMPTY_TSI0_EXTRA_INDICES)
+ (0, EMPTY_TSI0_DATA, [], EMPTY_TSI0_EXTRA_INDICES),
],
- ids=["simple", "empty"]
+ ids=["simple", "empty"],
)
-def test_decompile(table, numGlyphs, data, expected_indices,
- expected_extra_indices):
- font = {'maxp': SimpleNamespace(numGlyphs=numGlyphs)}
+def test_decompile(table, numGlyphs, data, expected_indices, expected_extra_indices):
+ font = {"maxp": SimpleNamespace(numGlyphs=numGlyphs)}
table.decompile(data, font)
@@ -77,9 +75,9 @@ def test_decompile(table, numGlyphs, data, expected_indices,
"numGlyphs, indices, extra_indices, expected_data",
[
(5, TSI0_INDICES, TSI0_EXTRA_INDICES, TSI0_DATA),
- (0, [], EMPTY_TSI0_EXTRA_INDICES, EMPTY_TSI0_DATA)
+ (0, [], EMPTY_TSI0_EXTRA_INDICES, EMPTY_TSI0_DATA),
],
- ids=["simple", "empty"]
+ ids=["simple", "empty"],
)
def test_compile(table, numGlyphs, indices, extra_indices, expected_data):
assert table.compile(ttFont=None) == b""
@@ -97,9 +95,11 @@ def test_set(table):
def test_toXML(table):
assert getXML(table.toXML, ttFont=None) == [
- '<!-- This table will be calculated by the compiler -->']
+ "<!-- This table will be calculated by the compiler -->"
+ ]
if __name__ == "__main__":
import sys
+
sys.exit(pytest.main(sys.argv))
diff --git a/Tests/ttLib/tables/T_S_I__1_test.py b/Tests/ttLib/tables/T_S_I__1_test.py
index b792221e..e14f41d2 100644
--- a/Tests/ttLib/tables/T_S_I__1_test.py
+++ b/Tests/ttLib/tables/T_S_I__1_test.py
@@ -14,15 +14,19 @@ TSI1_UTF8_DATA = b"""abcd\xc3\xa9ghijklmnopqrstuvxywz0123456789"""
def indextable():
table = table_T_S_I__0()
table.set(
- [(0, 1, 0), # gid 0, length=1, offset=0, text='a'
- (1, 5, 1), # gid 1, length=5, offset=1, text='bcdef'
- (2, 0, 1), # gid 2, length=0, offset=1, text=''
- (3, 0, 1), # gid 3, length=0, offset=1, text=''
- (4, 8, 6)], # gid 4, length=8, offset=6, text='ghijklmn'
- [(0xFFFA, 2, 14), # 'ppgm', length=2, offset=14, text='op'
- (0xFFFB, 4, 16), # 'cvt', length=4, offset=16, text='qrst'
- (0xFFFC, 6, 20), # 'reserved', length=6, offset=20, text='uvxywz'
- (0xFFFD, 10, 26)] # 'fpgm', length=10, offset=26, text='0123456789'
+ [
+ (0, 1, 0), # gid 0, length=1, offset=0, text='a'
+ (1, 5, 1), # gid 1, length=5, offset=1, text='bcdef'
+ (2, 0, 1), # gid 2, length=0, offset=1, text=''
+ (3, 0, 1), # gid 3, length=0, offset=1, text=''
+ (4, 8, 6),
+ ], # gid 4, length=8, offset=6, text='ghijklmn'
+ [
+ (0xFFFA, 2, 14), # 'ppgm', length=2, offset=14, text='op'
+ (0xFFFB, 4, 16), # 'cvt', length=4, offset=16, text='qrst'
+ (0xFFFC, 6, 20), # 'reserved', length=6, offset=20, text='uvxywz'
+ (0xFFFD, 10, 26),
+ ], # 'fpgm', length=10, offset=26, text='0123456789'
)
return table
@@ -33,8 +37,8 @@ def font(indextable):
# ['a', 'b', 'c', ...]
ch = 0x61
n = len(indextable.indices)
- font.glyphOrder = [chr(i) for i in range(ch, ch+n)]
- font['TSI0'] = indextable
+ font.glyphOrder = [chr(i) for i in range(ch, ch + n)]
+ font["TSI0"] = indextable
return font
@@ -43,11 +47,8 @@ def empty_font():
font = TTFont()
font.glyphOrder = []
indextable = table_T_S_I__0()
- indextable.set([], [(0xFFFA, 0, 0),
- (0xFFFB, 0, 0),
- (0xFFFC, 0, 0),
- (0xFFFD, 0, 0)])
- font['TSI0'] = indextable
+ indextable.set([], [(0xFFFA, 0, 0), (0xFFFB, 0, 0), (0xFFFC, 0, 0), (0xFFFD, 0, 0)])
+ font["TSI0"] = indextable
return font
@@ -56,16 +57,18 @@ def test_decompile(font):
table.decompile(TSI1_DATA, font)
assert table.glyphPrograms == {
- 'a': 'a',
- 'b': 'bcdef',
+ "a": "a",
+ "b": "bcdef",
# 'c': '', # zero-length entries are skipped
# 'd': '',
- 'e': 'ghijklmn'}
+ "e": "ghijklmn",
+ }
assert table.extraPrograms == {
- 'ppgm': 'op',
- 'cvt': 'qrst',
- 'reserved': 'uvxywz',
- 'fpgm': '0123456789'}
+ "ppgm": "op",
+ "cvt": "qrst",
+ "reserved": "uvxywz",
+ "fpgm": "0123456789",
+ }
def test_decompile_utf8(font):
@@ -73,16 +76,18 @@ def test_decompile_utf8(font):
table.decompile(TSI1_UTF8_DATA, font)
assert table.glyphPrograms == {
- 'a': 'a',
- 'b': 'bcd\u00e9',
+ "a": "a",
+ "b": "bcd\u00e9",
# 'c': '', # zero-length entries are skipped
# 'd': '',
- 'e': 'ghijklmn'}
+ "e": "ghijklmn",
+ }
assert table.extraPrograms == {
- 'ppgm': 'op',
- 'cvt': 'qrst',
- 'reserved': 'uvxywz',
- 'fpgm': '0123456789'}
+ "ppgm": "op",
+ "cvt": "qrst",
+ "reserved": "uvxywz",
+ "fpgm": "0123456789",
+ }
def test_decompile_empty(empty_font):
@@ -94,32 +99,32 @@ def test_decompile_empty(empty_font):
def test_decompile_invalid_length(empty_font):
- empty_font.glyphOrder = ['a']
- empty_font['TSI0'].indices = [(0, 0x8000+1, 0)]
+ empty_font.glyphOrder = ["a"]
+ empty_font["TSI0"].indices = [(0, 0x8000 + 1, 0)]
table = table_T_S_I__1()
with pytest.raises(TTLibError) as excinfo:
- table.decompile(b'', empty_font)
+ table.decompile(b"", empty_font)
assert excinfo.match("textLength .* must not be > 32768")
def test_decompile_offset_past_end(empty_font):
- empty_font.glyphOrder = ['foo', 'bar']
- content = 'baz'
+ empty_font.glyphOrder = ["foo", "bar"]
+ content = "baz"
data = tobytes(content)
- empty_font['TSI0'].indices = [(0, len(data), 0), (1, 1, len(data)+1)]
+ empty_font["TSI0"].indices = [(0, len(data), 0), (1, 1, len(data) + 1)]
table = table_T_S_I__1()
with CapturingLogHandler(table.log, "WARNING") as captor:
table.decompile(data, empty_font)
# the 'bar' program is skipped because its offset > len(data)
- assert table.glyphPrograms == {'foo': 'baz'}
+ assert table.glyphPrograms == {"foo": "baz"}
assert any("textOffset > totalLength" in r.msg for r in captor.records)
def test_decompile_magic_length_last_extra(empty_font):
- indextable = empty_font['TSI0']
+ indextable = empty_font["TSI0"]
indextable.extra_indices[-1] = (0xFFFD, 0x8000, 0)
content = "0" * (0x8000 + 1)
data = tobytes(content)
@@ -127,20 +132,22 @@ def test_decompile_magic_length_last_extra(empty_font):
table = table_T_S_I__1()
table.decompile(data, empty_font)
- assert table.extraPrograms['fpgm'] == content
+ assert table.extraPrograms["fpgm"] == content
def test_decompile_magic_length_last_glyph(empty_font):
- empty_font.glyphOrder = ['foo', 'bar']
- indextable = empty_font['TSI0']
+ empty_font.glyphOrder = ["foo", "bar"]
+ indextable = empty_font["TSI0"]
indextable.indices = [
(0, 3, 0),
- (1, 0x8000, 3)] # the actual length of 'bar' program is
+ (1, 0x8000, 3),
+ ] # the actual length of 'bar' program is
indextable.extra_indices = [ # the difference between the first extra's
- (0xFFFA, 0, 0x8004), # offset and 'bar' offset: 0x8004 - 3
+ (0xFFFA, 0, 0x8004), # offset and 'bar' offset: 0x8004 - 3
(0xFFFB, 0, 0x8004),
(0xFFFC, 0, 0x8004),
- (0xFFFD, 0, 0x8004)]
+ (0xFFFD, 0, 0x8004),
+ ]
foo_content = "0" * 3
bar_content = "1" * (0x8000 + 1)
data = tobytes(foo_content + bar_content)
@@ -148,17 +155,18 @@ def test_decompile_magic_length_last_glyph(empty_font):
table = table_T_S_I__1()
table.decompile(data, empty_font)
- assert table.glyphPrograms['foo'] == foo_content
- assert table.glyphPrograms['bar'] == bar_content
+ assert table.glyphPrograms["foo"] == foo_content
+ assert table.glyphPrograms["bar"] == bar_content
def test_decompile_magic_length_non_last(empty_font):
- indextable = empty_font['TSI0']
+ indextable = empty_font["TSI0"]
indextable.extra_indices = [
(0xFFFA, 3, 0),
(0xFFFB, 0x8000, 3), # the actual length of 'cvt' program is:
(0xFFFC, 0, 0x8004), # nextTextOffset - textOffset: 0x8004 - 3
- (0xFFFD, 0, 0x8004)]
+ (0xFFFD, 0, 0x8004),
+ ]
ppgm_content = "0" * 3
cvt_content = "1" * (0x8000 + 1)
data = tobytes(ppgm_content + cvt_content)
@@ -166,16 +174,17 @@ def test_decompile_magic_length_non_last(empty_font):
table = table_T_S_I__1()
table.decompile(data, empty_font)
- assert table.extraPrograms['ppgm'] == ppgm_content
- assert table.extraPrograms['cvt'] == cvt_content
+ assert table.extraPrograms["ppgm"] == ppgm_content
+ assert table.extraPrograms["cvt"] == cvt_content
table = table_T_S_I__1()
with CapturingLogHandler(table.log, "WARNING") as captor:
table.decompile(data[:-1], empty_font) # last entry is truncated
captor.assertRegex("nextTextOffset > totalLength")
- assert table.extraPrograms['cvt'] == cvt_content[:-1]
+ assert table.extraPrograms["cvt"] == cvt_content[:-1]
if __name__ == "__main__":
import sys
+
sys.exit(pytest.main(sys.argv))
diff --git a/Tests/ttLib/tables/TupleVariation_test.py b/Tests/ttLib/tables/TupleVariation_test.py
index 99b94918..bfb0e453 100644
--- a/Tests/ttLib/tables/TupleVariation_test.py
+++ b/Tests/ttLib/tables/TupleVariation_test.py
@@ -2,23 +2,29 @@ from fontTools.misc.loggingTools import CapturingLogHandler
from fontTools.misc.testTools import parseXML
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.misc.xmlWriter import XMLWriter
-from fontTools.ttLib.tables.TupleVariation import \
- log, TupleVariation, compileSharedTuples, decompileSharedTuples, \
- compileTupleVariationStore, decompileTupleVariationStore, inferRegion_
+from fontTools.ttLib.tables.TupleVariation import (
+ log,
+ TupleVariation,
+ compileSharedTuples,
+ decompileSharedTuples,
+ compileTupleVariationStore,
+ decompileTupleVariationStore,
+ inferRegion_,
+)
from io import BytesIO
import random
import unittest
def hexencode(s):
- h = hexStr(s).upper()
- return ' '.join([h[i:i+2] for i in range(0, len(h), 2)])
+ h = hexStr(s).upper()
+ return " ".join([h[i : i + 2] for i in range(0, len(h), 2)])
AXES = {
- "wdth": (0.25, 0.375, 0.5),
- "wght": (0.0, 1.0, 1.0),
- "opsz": (-0.75, -0.75, 0.0)
+ "wdth": (0.25, 0.375, 0.5),
+ "wght": (0.0, 1.0, 1.0),
+ "opsz": (-0.75, -0.75, 0.0),
}
@@ -26,18 +32,19 @@ AXES = {
# in Apple's TrueType specification.
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
SKIA_GVAR_SHARED_TUPLES_DATA = deHexStr(
- "40 00 00 00 C0 00 00 00 00 00 40 00 00 00 C0 00 "
- "C0 00 C0 00 40 00 C0 00 40 00 40 00 C0 00 40 00")
+ "40 00 00 00 C0 00 00 00 00 00 40 00 00 00 C0 00 "
+ "C0 00 C0 00 40 00 C0 00 40 00 40 00 C0 00 40 00"
+)
SKIA_GVAR_SHARED_TUPLES = [
- {"wght": 1.0, "wdth": 0.0},
- {"wght": -1.0, "wdth": 0.0},
- {"wght": 0.0, "wdth": 1.0},
- {"wght": 0.0, "wdth": -1.0},
- {"wght": -1.0, "wdth": -1.0},
- {"wght": 1.0, "wdth": -1.0},
- {"wght": 1.0, "wdth": 1.0},
- {"wght": -1.0, "wdth": 1.0}
+ {"wght": 1.0, "wdth": 0.0},
+ {"wght": -1.0, "wdth": 0.0},
+ {"wght": 0.0, "wdth": 1.0},
+ {"wght": 0.0, "wdth": -1.0},
+ {"wght": -1.0, "wdth": -1.0},
+ {"wght": 1.0, "wdth": -1.0},
+ {"wght": 1.0, "wdth": 1.0},
+ {"wght": -1.0, "wdth": 1.0},
]
@@ -47,831 +54,987 @@ SKIA_GVAR_SHARED_TUPLES = [
# we can parse the data as it appears in the specification.
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
SKIA_GVAR_I_DATA = deHexStr(
- "00 08 00 24 00 33 20 00 00 15 20 01 00 1B 20 02 "
- "00 24 20 03 00 15 20 04 00 26 20 07 00 0D 20 06 "
- "00 1A 20 05 00 40 01 01 01 81 80 43 FF 7E FF 7E "
- "FF 7E FF 7E 00 81 45 01 01 01 03 01 04 01 04 01 "
- "04 01 02 80 40 00 82 81 81 04 3A 5A 3E 43 20 81 "
- "04 0E 40 15 45 7C 83 00 0D 9E F3 F2 F0 F0 F0 F0 "
- "F3 9E A0 A1 A1 A1 9F 80 00 91 81 91 00 0D 0A 0A "
- "09 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0B 80 00 15 81 "
- "81 00 C4 89 00 C4 83 00 0D 80 99 98 96 96 96 96 "
- "99 80 82 83 83 83 81 80 40 FF 18 81 81 04 E6 F9 "
- "10 21 02 81 04 E8 E5 EB 4D DA 83 00 0D CE D3 D4 "
- "D3 D3 D3 D5 D2 CE CC CD CD CD CD 80 00 A1 81 91 "
- "00 0D 07 03 04 02 02 02 03 03 07 07 08 08 08 07 "
- "80 00 09 81 81 00 28 40 00 A4 02 24 24 66 81 04 "
- "08 FA FA FA 28 83 00 82 02 FF FF FF 83 02 01 01 "
- "01 84 91 00 80 06 07 08 08 08 08 0A 07 80 03 FE "
- "FF FF FF 81 00 08 81 82 02 EE EE EE 8B 6D 00")
+ "00 08 00 24 00 33 20 00 00 15 20 01 00 1B 20 02 "
+ "00 24 20 03 00 15 20 04 00 26 20 07 00 0D 20 06 "
+ "00 1A 20 05 00 40 01 01 01 81 80 43 FF 7E FF 7E "
+ "FF 7E FF 7E 00 81 45 01 01 01 03 01 04 01 04 01 "
+ "04 01 02 80 40 00 82 81 81 04 3A 5A 3E 43 20 81 "
+ "04 0E 40 15 45 7C 83 00 0D 9E F3 F2 F0 F0 F0 F0 "
+ "F3 9E A0 A1 A1 A1 9F 80 00 91 81 91 00 0D 0A 0A "
+ "09 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0B 80 00 15 81 "
+ "81 00 C4 89 00 C4 83 00 0D 80 99 98 96 96 96 96 "
+ "99 80 82 83 83 83 81 80 40 FF 18 81 81 04 E6 F9 "
+ "10 21 02 81 04 E8 E5 EB 4D DA 83 00 0D CE D3 D4 "
+ "D3 D3 D3 D5 D2 CE CC CD CD CD CD 80 00 A1 81 91 "
+ "00 0D 07 03 04 02 02 02 03 03 07 07 08 08 08 07 "
+ "80 00 09 81 81 00 28 40 00 A4 02 24 24 66 81 04 "
+ "08 FA FA FA 28 83 00 82 02 FF FF FF 83 02 01 01 "
+ "01 84 91 00 80 06 07 08 08 08 08 0A 07 80 03 FE "
+ "FF FF FF 81 00 08 81 82 02 EE EE EE 8B 6D 00"
+)
class TupleVariationTest(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 test_equal(self):
- var1 = TupleVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
- var2 = TupleVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
- self.assertEqual(var1, var2)
-
- def test_equal_differentAxes(self):
- var1 = TupleVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
- var2 = TupleVariation({"wght":(0.7, 0.8, 0.9)}, [(0,0), (9,8), (7,6)])
- self.assertNotEqual(var1, var2)
-
- def test_equal_differentCoordinates(self):
- var1 = TupleVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8), (7,6)])
- var2 = TupleVariation({"wght":(0.0, 1.0, 1.0)}, [(0,0), (9,8)])
- self.assertNotEqual(var1, var2)
-
- def test_hasImpact_someDeltasNotZero(self):
- axes = {"wght":(0.0, 1.0, 1.0)}
- var = TupleVariation(axes, [(0,0), (9,8), (7,6)])
- self.assertTrue(var.hasImpact())
-
- def test_hasImpact_allDeltasZero(self):
- axes = {"wght":(0.0, 1.0, 1.0)}
- var = TupleVariation(axes, [(0,0), (0,0), (0,0)])
- self.assertTrue(var.hasImpact())
-
- def test_hasImpact_allDeltasNone(self):
- axes = {"wght":(0.0, 1.0, 1.0)}
- var = TupleVariation(axes, [None, None, None])
- self.assertFalse(var.hasImpact())
-
- def test_toXML_badDeltaFormat(self):
- writer = XMLWriter(BytesIO())
- g = TupleVariation(AXES, ["String"])
- with CapturingLogHandler(log, "ERROR") as captor:
- g.toXML(writer, ["wdth"])
- self.assertIn("bad delta format", [r.msg for r in captor.records])
- self.assertEqual([
- '<tuple>',
- '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
- '<!-- bad delta #0 -->',
- '</tuple>',
- ], TupleVariationTest.xml_lines(writer))
-
- def test_toXML_constants(self):
- writer = XMLWriter(BytesIO())
- g = TupleVariation(AXES, [42, None, 23, 0, -17, None])
- g.toXML(writer, ["wdth", "wght", "opsz"])
- self.assertEqual([
- '<tuple>',
- '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
- '<coord axis="wght" value="1.0"/>',
- '<coord axis="opsz" value="-0.75"/>',
- '<delta cvt="0" value="42"/>',
- '<delta cvt="2" value="23"/>',
- '<delta cvt="3" value="0"/>',
- '<delta cvt="4" value="-17"/>',
- '</tuple>'
- ], TupleVariationTest.xml_lines(writer))
-
- def test_toXML_points(self):
- writer = XMLWriter(BytesIO())
- g = TupleVariation(AXES, [(9,8), None, (7,6), (0,0), (-1,-2), None])
- g.toXML(writer, ["wdth", "wght", "opsz"])
- self.assertEqual([
- '<tuple>',
- '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
- '<coord axis="wght" value="1.0"/>',
- '<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"/>',
- '<delta pt="4" x="-1" y="-2"/>',
- '</tuple>'
- ], TupleVariationTest.xml_lines(writer))
-
- def test_toXML_allDeltasNone(self):
- writer = XMLWriter(BytesIO())
- axes = {"wght":(0.0, 1.0, 1.0)}
- g = TupleVariation(axes, [None] * 5)
- g.toXML(writer, ["wght", "wdth"])
- self.assertEqual([
- '<tuple>',
- '<coord axis="wght" value="1.0"/>',
- '<!-- no deltas -->',
- '</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:
- for name, attrs, content in parseXML('<delta a="1" b="2"/>'):
- g.fromXML(name, attrs, content)
- self.assertIn("bad delta format: a, b",
- [r.msg for r in captor.records])
-
- def test_fromXML_constants(self):
- g = TupleVariation({}, [None] * 4)
- for name, attrs, content in parseXML(
- '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
- '<coord axis="wght" value="1.0"/>'
- '<coord axis="opsz" value="-0.75"/>'
- '<delta cvt="1" value="42"/>'
- '<delta cvt="2" value="-23"/>'):
- g.fromXML(name, attrs, content)
- self.assertEqual(AXES, g.axes)
- self.assertEqual([None, 42, -23, None], g.coordinates)
-
- def test_fromXML_points(self):
- g = TupleVariation({}, [None] * 4)
- for name, attrs, content in parseXML(
- '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
- '<coord axis="wght" value="1.0"/>'
- '<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)},
- [(7,4), (8,5), (9,6)])
- axisTags = ["wght", "wdth"]
- sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
- tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b'')
- # len(deltas)=8; flags=None; tupleIndex=0x77
- # embeddedPeaks=[]; intermediateCoord=[]
- self.assertEqual("00 08 00 77", hexencode(tup))
- self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_sharedPeaks_intermediate_sharedPoints(self):
- var = TupleVariation(
- {"wght": (0.3, 0.5, 0.7), "wdth": (0.1, 0.8, 0.9)},
- [(7,4), (8,5), (9,6)])
- axisTags = ["wght", "wdth"]
- sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
- tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b'')
- # len(deltas)=8; flags=INTERMEDIATE_REGION; tupleIndex=0x77
- # embeddedPeak=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)]
- self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tup))
- self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_sharedPeaks_nonIntermediate_privatePoints(self):
- var = TupleVariation(
- {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
- [(7,4), (8,5), (9,6)])
- axisTags = ["wght", "wdth"]
- sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
- tup, deltas = var.compile(axisTags, sharedPeakIndices)
- # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
- # embeddedPeak=[]; intermediateCoord=[]
- self.assertEqual("00 09 20 77", hexencode(tup))
- self.assertEqual("00 " # all points in glyph
- "02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_sharedPeaks_intermediate_privatePoints(self):
- var = TupleVariation(
- {"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 1.0)},
- [(7,4), (8,5), (9,6)])
- axisTags = ["wght", "wdth"]
- sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
- tuple, deltas = var.compile(axisTags, sharedPeakIndices)
- # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
- # embeddedPeak=[]; intermediateCoord=[(0.0, 0.0), (1.0, 1.0)]
- self.assertEqual("00 09 60 77 00 00 00 00 40 00 40 00",
- hexencode(tuple))
- self.assertEqual("00 " # all points in glyph
- "02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_embeddedPeak_nonIntermediate_sharedPoints(self):
- var = TupleVariation(
- {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
- [(7,4), (8,5), (9,6)])
- tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'')
- # len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
- # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
- self.assertEqual("00 08 80 00 20 00 33 33", hexencode(tup))
- self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_embeddedPeak_nonIntermediate_sharedConstants(self):
- var = TupleVariation(
- {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
- [3, 1, 4])
- tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'')
- # len(deltas)=4; flags=EMBEDDED_PEAK_TUPLE
- # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
- self.assertEqual("00 04 80 00 20 00 33 33", hexencode(tup))
- self.assertEqual("02 03 01 04", # delta: [3, 1, 4]
- hexencode(deltas))
-
- def test_compile_embeddedPeak_intermediate_sharedPoints(self):
- var = TupleVariation(
- {"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 0.8)},
- [(7,4), (8,5), (9,6)])
- tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'')
- # len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
- # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[(0.0, 0.0), (1.0, 0.8)]
- self.assertEqual("00 08 C0 00 20 00 33 33 00 00 00 00 40 00 33 33",
- hexencode(tup))
- self.assertEqual("02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_embeddedPeak_nonIntermediate_privatePoints(self):
- var = TupleVariation(
- {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
- [(7,4), (8,5), (9,6)])
- tup, deltas = var.compile(axisTags=["wght", "wdth"])
- # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
- # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
- self.assertEqual("00 09 A0 00 20 00 33 33", hexencode(tup))
- self.assertEqual("00 " # all points in glyph
- "02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_embeddedPeak_nonIntermediate_privateConstants(self):
- var = TupleVariation(
- {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
- [7, 8, 9])
- tup, deltas = var.compile(axisTags=["wght", "wdth"])
- # len(deltas)=5; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
- # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
- self.assertEqual("00 05 A0 00 20 00 33 33", hexencode(tup))
- self.assertEqual("00 " # all points in glyph
- "02 07 08 09", # delta: [7, 8, 9]
- hexencode(deltas))
-
- def test_compile_embeddedPeak_intermediate_privatePoints(self):
- var = TupleVariation(
- {"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)},
- [(7,4), (8,5), (9,6)])
- tup, deltas = var.compile(axisTags = ["wght", "wdth"])
- # len(deltas)=9;
- # flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
- # embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
- self.assertEqual("00 09 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A",
- hexencode(tup))
- self.assertEqual("00 " # all points in glyph
- "02 07 08 09 " # deltaX: [7, 8, 9]
- "02 04 05 06", # deltaY: [4, 5, 6]
- hexencode(deltas))
-
- def test_compile_embeddedPeak_intermediate_privateConstants(self):
- var = TupleVariation(
- {"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)},
- [7, 8, 9])
- tup, deltas = var.compile(axisTags = ["wght", "wdth"])
- # len(deltas)=5;
- # flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
- # embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
- self.assertEqual("00 05 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A",
- hexencode(tup))
- self.assertEqual("00 " # all points in glyph
- "02 07 08 09", # delta: [7, 8, 9]
- hexencode(deltas))
-
- def test_compileCoord(self):
- var = TupleVariation({"wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4)
- self.assertEqual("C0 00 20 00", hexencode(var.compileCoord(["wght", "wdth"])))
- self.assertEqual("20 00 C0 00", hexencode(var.compileCoord(["wdth", "wght"])))
- self.assertEqual("C0 00", hexencode(var.compileCoord(["wght"])))
-
- def test_compileIntermediateCoord(self):
- var = TupleVariation({"wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4)
- self.assertEqual("C0 00 19 9A 00 00 26 66", hexencode(var.compileIntermediateCoord(["wght", "wdth"])))
- self.assertEqual("19 9A C0 00 26 66 00 00", hexencode(var.compileIntermediateCoord(["wdth", "wght"])))
- self.assertEqual(None, var.compileIntermediateCoord(["wght"]))
- self.assertEqual("19 9A 26 66", hexencode(var.compileIntermediateCoord(["wdth"])))
-
- def test_decompileCoord(self):
- decompileCoord = TupleVariation.decompileCoord_
- data = deHexStr("DE AD C0 00 20 00 DE AD")
- self.assertEqual(({"wght": -1.0, "wdth": 0.5}, 6), decompileCoord(["wght", "wdth"], data, 2))
-
- def test_decompileCoord_roundTrip(self):
- # Make sure we are not affected by https://github.com/fonttools/fonttools/issues/286
- data = deHexStr("7F B9 80 35")
- values, _ = TupleVariation.decompileCoord_(["wght", "wdth"], data, 0)
- axisValues = {axis:(val, val, val) for axis, val in values.items()}
- var = TupleVariation(axisValues, [None] * 4)
- self.assertEqual("7F B9 80 35", hexencode(var.compileCoord(["wght", "wdth"])))
-
- def test_compilePoints(self):
- compilePoints = lambda p: TupleVariation.compilePoints(set(p))
- self.assertEqual("00", hexencode(compilePoints(set()))) # all points in glyph
- self.assertEqual("01 00 07", hexencode(compilePoints([7])))
- self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535])))
- self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15])))
- self.assertEqual("06 05 07 01 F7 02 01 F2", hexencode(compilePoints([7, 8, 255, 257, 258, 500])))
- self.assertEqual("03 01 07 01 80 01 EC", hexencode(compilePoints([7, 8, 500])))
- self.assertEqual("04 01 07 01 81 BE E7 0C 0F", hexencode(compilePoints([7, 8, 0xBEEF, 0xCAFE])))
- self.maxDiff = None
- self.assertEqual("81 2C" + # 300 points (0x12c) in total
- " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127]
- " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255]
- " 2B" + (44 * " 01"), # third run, contains 44 points: [256 .. 299]
- hexencode(compilePoints(range(300))))
- self.assertEqual("81 8F" + # 399 points (0x18f) in total
- " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127]
- " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255]
- " 7F" + (128 * " 01") + # third run, contains 128 points: [256 .. 383]
- " 0E" + (15 * " 01"), # fourth run, contains 15 points: [384 .. 398]
- hexencode(compilePoints(range(399))))
-
- def test_decompilePoints(self):
- numPointsInGlyph = 65536
- allPoints = list(range(numPointsInGlyph))
- def decompilePoints(data, offset):
- points, offset = TupleVariation.decompilePoints_(numPointsInGlyph, deHexStr(data), offset, "gvar")
- # Conversion to list needed for Python 3.
- return (list(points), offset)
- # all points in glyph
- self.assertEqual((allPoints, 1), decompilePoints("00", 0))
- # all points in glyph (in overly verbose encoding, not explicitly prohibited by spec)
- self.assertEqual((allPoints, 2), decompilePoints("80 00", 0))
- # 2 points; first run: [9, 9+6]
- self.assertEqual(([9, 15], 4), decompilePoints("02 01 09 06", 0))
- # 2 points; first run: [0xBEEF, 0xCAFE]. (0x0C0F = 0xCAFE - 0xBEEF)
- self.assertEqual(([0xBEEF, 0xCAFE], 6), decompilePoints("02 81 BE EF 0C 0F", 0))
- # 1 point; first run: [7]
- self.assertEqual(([7], 3), decompilePoints("01 00 07", 0))
- # 1 point; first run: [7] in overly verbose encoding
- self.assertEqual(([7], 4), decompilePoints("01 80 00 07", 0))
- # 1 point; first run: [65535]; requires words to be treated as unsigned numbers
- self.assertEqual(([65535], 4), decompilePoints("01 80 FF FF", 0))
- # 4 points; first run: [7, 8]; second run: [255, 257]. 257 is stored in delta-encoded bytes (0xFF + 2).
- self.assertEqual(([7, 8, 263, 265], 7), decompilePoints("04 01 07 01 01 FF 02", 0))
- # combination of all encodings, preceded and followed by 4 bytes of unused data
- data = "DE AD DE AD 04 01 07 01 81 BE E7 0C 0F DE AD DE AD"
- self.assertEqual(([7, 8, 0xBEEF, 0xCAFE], 13), decompilePoints(data, 4))
- self.assertSetEqual(set(range(300)), set(decompilePoints(
- "81 2C" + # 300 points (0x12c) in total
- " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127]
- " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255]
- " AB" + (44 * " 00 01"), # third run, contains 44 points: [256 .. 299]
- 0)[0]))
- self.assertSetEqual(set(range(399)), set(decompilePoints(
- "81 8F" + # 399 points (0x18f) in total
- " 7F 00" + (127 * " 01") + # first run, contains 128 points: [0 .. 127]
- " 7F" + (128 * " 01") + # second run, contains 128 points: [128 .. 255]
- " FF" + (128 * " 00 01") + # third run, contains 128 points: [256 .. 383]
- " 8E" + (15 * " 00 01"), # fourth run, contains 15 points: [384 .. 398]
- 0)[0]))
-
- def test_decompilePoints_shouldAcceptBadPointNumbers(self):
- decompilePoints = TupleVariation.decompilePoints_
- # 2 points; first run: [3, 9].
- numPointsInGlyph = 8
- with CapturingLogHandler(log, "WARNING") as captor:
- decompilePoints(numPointsInGlyph,
- deHexStr("02 01 03 06"), 0, "cvar")
- self.assertIn("point 9 out of range in 'cvar' table",
- [r.msg for r in captor.records])
-
- def test_decompilePoints_roundTrip(self):
- numPointsInGlyph = 500 # greater than 255, so we also exercise code path for 16-bit encoding
- compile = lambda points: TupleVariation.compilePoints(points)
- decompile = lambda data: set(TupleVariation.decompilePoints_(numPointsInGlyph, data, 0, "gvar")[0])
- for i in range(50):
- points = set(random.sample(range(numPointsInGlyph), 30))
- self.assertSetEqual(points, decompile(compile(points)),
- "failed round-trip decompile/compilePoints; points=%s" % points)
- allPoints = set(range(numPointsInGlyph))
- self.assertSetEqual(allPoints, decompile(compile(allPoints)))
- self.assertSetEqual(allPoints, decompile(compile(set())))
-
- def test_compileDeltas_points(self):
- var = TupleVariation({}, [None, (1, 0), (2, 0), None, (4, 0), None])
- # deltaX for points: [1, 2, 4]; deltaY for points: [0, 0, 0]
- self.assertEqual("02 01 02 04 82", hexencode(var.compileDeltas()))
-
- def test_compileDeltas_constants(self):
- var = TupleVariation({}, [None, 1, 2, None, 4, None])
- # delta for cvts: [1, 2, 4]
- self.assertEqual("02 01 02 04", hexencode(var.compileDeltas()))
-
- def test_compileDeltaValues(self):
- compileDeltaValues = lambda values: hexencode(TupleVariation.compileDeltaValues_(values))
- # zeroes
- self.assertEqual("80", compileDeltaValues([0]))
- self.assertEqual("BF", compileDeltaValues([0] * 64))
- self.assertEqual("BF 80", compileDeltaValues([0] * 65))
- self.assertEqual("BF A3", compileDeltaValues([0] * 100))
- self.assertEqual("BF BF BF BF", compileDeltaValues([0] * 256))
- # bytes
- self.assertEqual("00 01", compileDeltaValues([1]))
- self.assertEqual("06 01 02 03 7F 80 FF FE", compileDeltaValues([1, 2, 3, 127, -128, -1, -2]))
- self.assertEqual("3F" + (64 * " 7F"), compileDeltaValues([127] * 64))
- self.assertEqual("3F" + (64 * " 7F") + " 00 7F", compileDeltaValues([127] * 65))
- # words
- self.assertEqual("40 66 66", compileDeltaValues([0x6666]))
- self.assertEqual("43 66 66 7F FF FF FF 80 00", compileDeltaValues([0x6666, 32767, -1, -32768]))
- self.assertEqual("7F" + (64 * " 11 22"), compileDeltaValues([0x1122] * 64))
- self.assertEqual("7F" + (64 * " 11 22") + " 40 11 22", compileDeltaValues([0x1122] * 65))
- # bytes, zeroes, bytes: a single zero is more compact when encoded as part of the bytes run
- self.assertEqual("04 7F 7F 00 7F 7F", compileDeltaValues([127, 127, 0, 127, 127]))
- self.assertEqual("01 7F 7F 81 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 127, 127]))
- self.assertEqual("01 7F 7F 82 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 127, 127]))
- self.assertEqual("01 7F 7F 83 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 0, 127, 127]))
- # bytes, zeroes
- self.assertEqual("01 01 00", compileDeltaValues([1, 0]))
- self.assertEqual("00 01 81", compileDeltaValues([1, 0, 0]))
- # words, bytes, words: a single byte is more compact when encoded as part of the words run
- self.assertEqual("42 66 66 00 02 77 77", compileDeltaValues([0x6666, 2, 0x7777]))
- self.assertEqual("40 66 66 01 02 02 40 77 77", compileDeltaValues([0x6666, 2, 2, 0x7777]))
- # words, zeroes, words
- self.assertEqual("40 66 66 80 40 77 77", compileDeltaValues([0x6666, 0, 0x7777]))
- self.assertEqual("40 66 66 81 40 77 77", compileDeltaValues([0x6666, 0, 0, 0x7777]))
- self.assertEqual("40 66 66 82 40 77 77", compileDeltaValues([0x6666, 0, 0, 0, 0x7777]))
- # words, zeroes, bytes
- self.assertEqual("40 66 66 80 02 01 02 03", compileDeltaValues([0x6666, 0, 1, 2, 3]))
- self.assertEqual("40 66 66 81 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 1, 2, 3]))
- self.assertEqual("40 66 66 82 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 0, 1, 2, 3]))
- # words, zeroes
- self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0]))
- self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0]))
-
- def test_decompileDeltas(self):
- decompileDeltas = TupleVariation.decompileDeltas_
- # 83 = zero values (0x80), count = 4 (1 + 0x83 & 0x3F)
- self.assertEqual(([0, 0, 0, 0], 1), decompileDeltas(4, deHexStr("83"), 0))
- # 41 01 02 FF FF = signed 16-bit values (0x40), count = 2 (1 + 0x41 & 0x3F)
- self.assertEqual(([258, -1], 5), decompileDeltas(2, deHexStr("41 01 02 FF FF"), 0))
- # 01 81 07 = signed 8-bit values, count = 2 (1 + 0x01 & 0x3F)
- self.assertEqual(([-127, 7], 3), decompileDeltas(2, deHexStr("01 81 07"), 0))
- # combination of all three encodings, preceded and followed by 4 bytes of unused data
- data = deHexStr("DE AD BE EF 83 40 01 02 01 81 80 DE AD BE EF")
- self.assertEqual(([0, 0, 0, 0, 258, -127, -128], 11), decompileDeltas(7, data, 4))
-
- def test_decompileDeltas_roundTrip(self):
- numDeltas = 30
- compile = TupleVariation.compileDeltaValues_
- decompile = lambda data: TupleVariation.decompileDeltas_(numDeltas, data, 0)[0]
- for i in range(50):
- deltas = random.sample(range(-128, 127), 10)
- deltas.extend(random.sample(range(-32768, 32767), 10))
- deltas.extend([0] * 10)
- random.shuffle(deltas)
- self.assertListEqual(deltas, decompile(compile(deltas)))
-
- def test_compileSharedTuples(self):
- # Below, the peak coordinate {"wght": 1.0, "wdth": 0.8} appears
- # three times (most frequent sorted first); {"wght": 1.0, "wdth": 0.5}
- # and {"wght": 1.0, "wdth": 0.7} both appears two times (tie) and
- # are sorted alphanumerically to ensure determinism.
- # The peak coordinate {"wght": 1.0, "wdth": 0.9} appears only once
- # and is thus ignored.
- # Because the start and end of variation ranges is not encoded
- # into the shared pool, they should get ignored.
- deltas = [None] * 4
- variations = [
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.5, 0.7, 1.0)
- }, deltas),
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.2, 0.7, 1.0)
- }, deltas),
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.2, 0.8, 1.0)
- }, deltas),
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.3, 0.5, 1.0)
- }, deltas),
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.3, 0.8, 1.0)
- }, deltas),
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.3, 0.9, 1.0)
- }, deltas),
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.4, 0.8, 1.0)
- }, deltas),
- TupleVariation({
- "wght": (1.0, 1.0, 1.0),
- "wdth": (0.5, 0.5, 1.0)
- }, deltas),
- ]
- result = compileSharedTuples(["wght", "wdth"], variations)
- self.assertEqual([hexencode(c) for c in result],
- ["40 00 33 33", "40 00 20 00", "40 00 2C CD"])
-
- def test_decompileSharedTuples_Skia(self):
- sharedTuples = decompileSharedTuples(
- axisTags=["wght", "wdth"], sharedTupleCount=8,
- data=SKIA_GVAR_SHARED_TUPLES_DATA, offset=0)
- self.assertEqual(sharedTuples, SKIA_GVAR_SHARED_TUPLES)
-
- def test_decompileSharedTuples_empty(self):
- self.assertEqual(decompileSharedTuples(["wght"], 0, b"", 0), [])
-
- def test_compileTupleVariationStore_allVariationsRedundant(self):
- axes = {"wght": (0.3, 0.4, 0.5), "opsz": (0.7, 0.8, 0.9)}
- variations = [
- TupleVariation(axes, [None] * 4),
- TupleVariation(axes, [None] * 4),
- TupleVariation(axes, [None] * 4)
- ]
- self.assertEqual(
- compileTupleVariationStore(variations, pointCount=8,
- axisTags=["wght", "opsz"],
- sharedTupleIndices={}),
- (0, b"", b""))
-
- def test_compileTupleVariationStore_noVariations(self):
- self.assertEqual(
- compileTupleVariationStore(variations=[], pointCount=8,
- axisTags=["wght", "opsz"],
- sharedTupleIndices={}),
- (0, b"", b""))
-
- def test_compileTupleVariationStore_roundTrip_cvar(self):
- deltas = [1, 2, 3, 4]
- variations = [
- TupleVariation({"wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)},
- deltas),
- TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)},
- deltas)
- ]
- tupleVariationCount, tuples, data = compileTupleVariationStore(
- variations, pointCount=4, axisTags=["wght", "wdth"],
- sharedTupleIndices={})
- self.assertEqual(
- decompileTupleVariationStore("cvar", ["wght", "wdth"],
- tupleVariationCount, pointCount=4,
- sharedTuples={}, data=(tuples + data),
- pos=0, dataPos=len(tuples)),
- variations)
-
- def test_compileTupleVariationStore_roundTrip_gvar(self):
- deltas = [(1,1), (2,2), (3,3), (4,4)]
- variations = [
- TupleVariation({"wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)},
- deltas),
- TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)},
- deltas)
- ]
- tupleVariationCount, tuples, data = compileTupleVariationStore(
- variations, pointCount=4, axisTags=["wght", "wdth"],
- sharedTupleIndices={})
- self.assertEqual(
- decompileTupleVariationStore("gvar", ["wght", "wdth"],
- tupleVariationCount, pointCount=4,
- sharedTuples={}, data=(tuples + data),
- pos=0, dataPos=len(tuples)),
- variations)
-
- def test_decompileTupleVariationStore_Skia_I(self):
- tvar = decompileTupleVariationStore(
- tableTag="gvar", axisTags=["wght", "wdth"],
- tupleVariationCount=8, pointCount=18,
- sharedTuples=SKIA_GVAR_SHARED_TUPLES,
- data=SKIA_GVAR_I_DATA, pos=4, dataPos=36)
- self.assertEqual(len(tvar), 8)
- self.assertEqual(tvar[0].axes, {"wght": (0.0, 1.0, 1.0)})
- self.assertEqual(
- " ".join(["%d,%d" % c for c in tvar[0].coordinates]),
- "257,0 -127,0 -128,58 -130,90 -130,62 -130,67 -130,32 -127,0 "
- "257,0 259,14 260,64 260,21 260,69 258,124 0,0 130,0 0,0 0,0")
-
- def test_decompileTupleVariationStore_empty(self):
- self.assertEqual(
- decompileTupleVariationStore(tableTag="gvar", axisTags=[],
- tupleVariationCount=0, pointCount=5,
- sharedTuples=[],
- data=b"", pos=4, dataPos=4),
- [])
-
- def test_getTupleSize(self):
- getTupleSize = TupleVariation.getTupleSize_
- numAxes = 3
- self.assertEqual(4 + numAxes * 2, getTupleSize(0x8042, numAxes))
- self.assertEqual(4 + numAxes * 4, getTupleSize(0x4077, numAxes))
- self.assertEqual(4, getTupleSize(0x2077, numAxes))
- self.assertEqual(4, getTupleSize(11, numAxes))
-
- def test_inferRegion(self):
- start, end = inferRegion_({"wght": -0.3, "wdth": 0.7})
- self.assertEqual(start, {"wght": -0.3, "wdth": 0.0})
- self.assertEqual(end, {"wght": 0.0, "wdth": 0.7})
-
- @staticmethod
- def xml_lines(writer):
- content = writer.file.getvalue().decode("utf-8")
- return [line.strip() for line in content.splitlines()][1:]
-
- def test_getCoordWidth(self):
- empty = TupleVariation({}, [])
- self.assertEqual(empty.getCoordWidth(), 0)
-
- empty = TupleVariation({}, [None])
- self.assertEqual(empty.getCoordWidth(), 0)
-
- gvarTuple = TupleVariation({}, [None, (0, 0)])
- self.assertEqual(gvarTuple.getCoordWidth(), 2)
-
- cvarTuple = TupleVariation({}, [None, 0])
- self.assertEqual(cvarTuple.getCoordWidth(), 1)
-
- cvarTuple.coordinates[1] *= 1.0
- self.assertEqual(cvarTuple.getCoordWidth(), 1)
-
- with self.assertRaises(TypeError):
- TupleVariation({}, [None, "a"]).getCoordWidth()
-
- def test_scaleDeltas_cvar(self):
- var = TupleVariation({}, [100, None])
-
- var.scaleDeltas(1.0)
- self.assertEqual(var.coordinates, [100, None])
-
- var.scaleDeltas(0.333)
- self.assertAlmostEqual(var.coordinates[0], 33.3)
- self.assertIsNone(var.coordinates[1])
-
- var.scaleDeltas(0.0)
- self.assertEqual(var.coordinates, [0, None])
-
- def test_scaleDeltas_gvar(self):
- var = TupleVariation({}, [(100, 200), None])
-
- var.scaleDeltas(1.0)
- self.assertEqual(var.coordinates, [(100, 200), None])
-
- var.scaleDeltas(0.333)
- self.assertAlmostEqual(var.coordinates[0][0], 33.3)
- self.assertAlmostEqual(var.coordinates[0][1], 66.6)
- self.assertIsNone(var.coordinates[1])
-
- var.scaleDeltas(0.0)
- self.assertEqual(var.coordinates, [(0, 0), None])
-
- def test_roundDeltas_cvar(self):
- var = TupleVariation({}, [55.5, None, 99.9])
- var.roundDeltas()
- self.assertEqual(var.coordinates, [56, None, 100])
-
- def test_roundDeltas_gvar(self):
- var = TupleVariation({}, [(55.5, 100.0), None, (99.9, 100.0)])
- var.roundDeltas()
- self.assertEqual(var.coordinates, [(56, 100), None, (100, 100)])
-
- def test_calcInferredDeltas(self):
- var = TupleVariation({}, [(0, 0), None, None, None])
- coords = [(1, 1), (1, 1), (1, 1), (1, 1)]
-
- var.calcInferredDeltas(coords, [])
-
- self.assertEqual(
- var.coordinates,
- [(0, 0), (0, 0), (0, 0), (0, 0)]
- )
-
- def test_calcInferredDeltas_invalid(self):
- # cvar tuples can't have inferred deltas
- with self.assertRaises(TypeError):
- TupleVariation({}, [0]).calcInferredDeltas([], [])
-
- # origCoords must have same length as self.coordinates
- with self.assertRaises(ValueError):
- TupleVariation({}, [(0, 0), None]).calcInferredDeltas([], [])
-
- # at least 4 phantom points required
- with self.assertRaises(AssertionError):
- TupleVariation({}, [(0, 0), None]).calcInferredDeltas([(0, 0), (0, 0)], [])
-
- with self.assertRaises(AssertionError):
- TupleVariation({}, [(0, 0)] + [None]*5).calcInferredDeltas(
- [(0, 0)]*6,
- [1, 0] # endPts not in increasing order
- )
-
- def test_optimize(self):
- var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)]*5)
-
- var.optimize([(0, 0)]*5, [0])
-
- self.assertEqual(var.coordinates, [None, None, None, None, None])
-
- def test_optimize_isComposite(self):
- # when a composite glyph's deltas are all (0, 0), we still want
- # to write out an entry in gvar, else macOS doesn't apply any
- # variations to the composite glyph (even if its individual components
- # do vary).
- # https://github.com/fonttools/fonttools/issues/1381
- var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)]*5)
- var.optimize([(0, 0)]*5, [0], isComposite=True)
- self.assertEqual(var.coordinates, [(0, 0)]*5)
-
- # it takes more than 128 (0, 0) deltas before the optimized tuple with
- # (None) inferred deltas (except for the first) becomes smaller than
- # the un-optimized one that has all deltas explicitly set to (0, 0).
- var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)]*129)
- var.optimize([(0, 0)]*129, list(range(129-4)), isComposite=True)
- self.assertEqual(var.coordinates, [(0, 0)] + [None]*128)
-
- def test_sum_deltas_gvar(self):
- var1 = TupleVariation(
- {},
- [
- (-20, 0), (-20, 0), (20, 0), (20, 0),
- (0, 0), (0, 0), (0, 0), (0, 0),
- ]
- )
- var2 = TupleVariation(
- {},
- [
- (-10, 0), (-10, 0), (10, 0), (10, 0),
- (0, 0), (20, 0), (0, 0), (0, 0),
- ]
- )
-
- var1 += var2
-
- self.assertEqual(
- var1.coordinates,
- [
- (-30, 0), (-30, 0), (30, 0), (30, 0),
- (0, 0), (20, 0), (0, 0), (0, 0),
- ]
- )
-
- def test_sum_deltas_gvar_invalid_length(self):
- var1 = TupleVariation({}, [(1, 2)])
- var2 = TupleVariation({}, [(1, 2), (3, 4)])
-
- with self.assertRaisesRegex(ValueError, "deltas with different lengths"):
- var1 += var2
-
- def test_sum_deltas_gvar_with_inferred_points(self):
- var1 = TupleVariation({}, [(1, 2), None])
- var2 = TupleVariation({}, [(2, 3), None])
-
- with self.assertRaisesRegex(ValueError, "deltas with inferred points"):
- var1 += var2
-
- def test_sum_deltas_cvar(self):
- axes = {"wght": (0.0, 1.0, 1.0)}
- var1 = TupleVariation(axes, [0, 1, None, None])
- var2 = TupleVariation(axes, [None, 2, None, 3])
- var3 = TupleVariation(axes, [None, None, None, 4])
-
- var1 += var2
- var1 += var3
-
- self.assertEqual(var1.coordinates, [0, 3, None, 7])
+ 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 test_equal(self):
+ var1 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
+ var2 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
+ self.assertEqual(var1, var2)
+
+ def test_equal_differentAxes(self):
+ var1 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
+ var2 = TupleVariation({"wght": (0.7, 0.8, 0.9)}, [(0, 0), (9, 8), (7, 6)])
+ self.assertNotEqual(var1, var2)
+
+ def test_equal_differentCoordinates(self):
+ var1 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8), (7, 6)])
+ var2 = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0), (9, 8)])
+ self.assertNotEqual(var1, var2)
+
+ def test_hasImpact_someDeltasNotZero(self):
+ axes = {"wght": (0.0, 1.0, 1.0)}
+ var = TupleVariation(axes, [(0, 0), (9, 8), (7, 6)])
+ self.assertTrue(var.hasImpact())
+
+ def test_hasImpact_allDeltasZero(self):
+ axes = {"wght": (0.0, 1.0, 1.0)}
+ var = TupleVariation(axes, [(0, 0), (0, 0), (0, 0)])
+ self.assertTrue(var.hasImpact())
+
+ def test_hasImpact_allDeltasNone(self):
+ axes = {"wght": (0.0, 1.0, 1.0)}
+ var = TupleVariation(axes, [None, None, None])
+ self.assertFalse(var.hasImpact())
+
+ def test_toXML_badDeltaFormat(self):
+ writer = XMLWriter(BytesIO())
+ g = TupleVariation(AXES, ["String"])
+ with CapturingLogHandler(log, "ERROR") as captor:
+ g.toXML(writer, ["wdth"])
+ self.assertIn("bad delta format", [r.msg for r in captor.records])
+ self.assertEqual(
+ [
+ "<tuple>",
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
+ "<!-- bad delta #0 -->",
+ "</tuple>",
+ ],
+ TupleVariationTest.xml_lines(writer),
+ )
+
+ def test_toXML_constants(self):
+ writer = XMLWriter(BytesIO())
+ g = TupleVariation(AXES, [42, None, 23, 0, -17, None])
+ g.toXML(writer, ["wdth", "wght", "opsz"])
+ self.assertEqual(
+ [
+ "<tuple>",
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
+ '<coord axis="wght" value="1.0"/>',
+ '<coord axis="opsz" value="-0.75"/>',
+ '<delta cvt="0" value="42"/>',
+ '<delta cvt="2" value="23"/>',
+ '<delta cvt="3" value="0"/>',
+ '<delta cvt="4" value="-17"/>',
+ "</tuple>",
+ ],
+ TupleVariationTest.xml_lines(writer),
+ )
+
+ def test_toXML_points(self):
+ writer = XMLWriter(BytesIO())
+ g = TupleVariation(AXES, [(9, 8), None, (7, 6), (0, 0), (-1, -2), None])
+ g.toXML(writer, ["wdth", "wght", "opsz"])
+ self.assertEqual(
+ [
+ "<tuple>",
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>',
+ '<coord axis="wght" value="1.0"/>',
+ '<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"/>',
+ '<delta pt="4" x="-1" y="-2"/>',
+ "</tuple>",
+ ],
+ TupleVariationTest.xml_lines(writer),
+ )
+
+ def test_toXML_allDeltasNone(self):
+ writer = XMLWriter(BytesIO())
+ axes = {"wght": (0.0, 1.0, 1.0)}
+ g = TupleVariation(axes, [None] * 5)
+ g.toXML(writer, ["wght", "wdth"])
+ self.assertEqual(
+ [
+ "<tuple>",
+ '<coord axis="wght" value="1.0"/>',
+ "<!-- no deltas -->",
+ "</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:
+ for name, attrs, content in parseXML('<delta a="1" b="2"/>'):
+ g.fromXML(name, attrs, content)
+ self.assertIn("bad delta format: a, b", [r.msg for r in captor.records])
+
+ def test_fromXML_constants(self):
+ g = TupleVariation({}, [None] * 4)
+ for name, attrs, content in parseXML(
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
+ '<coord axis="wght" value="1.0"/>'
+ '<coord axis="opsz" value="-0.75"/>'
+ '<delta cvt="1" value="42"/>'
+ '<delta cvt="2" value="-23"/>'
+ ):
+ g.fromXML(name, attrs, content)
+ self.assertEqual(AXES, g.axes)
+ self.assertEqual([None, 42, -23, None], g.coordinates)
+
+ def test_fromXML_points(self):
+ g = TupleVariation({}, [None] * 4)
+ for name, attrs, content in parseXML(
+ '<coord axis="wdth" min="0.25" value="0.375" max="0.5"/>'
+ '<coord axis="wght" value="1.0"/>'
+ '<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)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ axisTags = ["wght", "wdth"]
+ sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
+ tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b"")
+ # len(deltas)=8; flags=None; tupleIndex=0x77
+ # embeddedPeaks=[]; intermediateCoord=[]
+ self.assertEqual("00 08 00 77", hexencode(tup))
+ self.assertEqual(
+ "02 07 08 09 " "02 04 05 06", # deltaX: [7, 8, 9] # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_sharedPeaks_intermediate_sharedPoints(self):
+ var = TupleVariation(
+ {"wght": (0.3, 0.5, 0.7), "wdth": (0.1, 0.8, 0.9)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ axisTags = ["wght", "wdth"]
+ sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
+ tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b"")
+ # len(deltas)=8; flags=INTERMEDIATE_REGION; tupleIndex=0x77
+ # embeddedPeak=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)]
+ self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tup))
+ self.assertEqual(
+ "02 07 08 09 " "02 04 05 06", # deltaX: [7, 8, 9] # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_sharedPeaks_nonIntermediate_privatePoints(self):
+ var = TupleVariation(
+ {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ axisTags = ["wght", "wdth"]
+ sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
+ tup, deltas = var.compile(axisTags, sharedPeakIndices)
+ # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
+ # embeddedPeak=[]; intermediateCoord=[]
+ self.assertEqual("00 09 20 77", hexencode(tup))
+ self.assertEqual(
+ "00 " # all points in glyph
+ "02 07 08 09 " # deltaX: [7, 8, 9]
+ "02 04 05 06", # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_sharedPeaks_intermediate_privatePoints(self):
+ var = TupleVariation(
+ {"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 1.0)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ axisTags = ["wght", "wdth"]
+ sharedPeakIndices = {var.compileCoord(axisTags): 0x77}
+ tuple, deltas = var.compile(axisTags, sharedPeakIndices)
+ # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
+ # embeddedPeak=[]; intermediateCoord=[(0.0, 0.0), (1.0, 1.0)]
+ self.assertEqual("00 09 60 77 00 00 00 00 40 00 40 00", hexencode(tuple))
+ self.assertEqual(
+ "00 " # all points in glyph
+ "02 07 08 09 " # deltaX: [7, 8, 9]
+ "02 04 05 06", # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_embeddedPeak_nonIntermediate_sharedPoints(self):
+ var = TupleVariation(
+ {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b"")
+ # len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
+ # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
+ self.assertEqual("00 08 80 00 20 00 33 33", hexencode(tup))
+ self.assertEqual(
+ "02 07 08 09 " "02 04 05 06", # deltaX: [7, 8, 9] # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_embeddedPeak_nonIntermediate_sharedConstants(self):
+ var = TupleVariation(
+ {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [3, 1, 4]
+ )
+ tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b"")
+ # len(deltas)=4; flags=EMBEDDED_PEAK_TUPLE
+ # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
+ self.assertEqual("00 04 80 00 20 00 33 33", hexencode(tup))
+ self.assertEqual("02 03 01 04", hexencode(deltas)) # delta: [3, 1, 4]
+
+ def test_compile_embeddedPeak_intermediate_sharedPoints(self):
+ var = TupleVariation(
+ {"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b"")
+ # len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
+ # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[(0.0, 0.0), (1.0, 0.8)]
+ self.assertEqual(
+ "00 08 C0 00 20 00 33 33 00 00 00 00 40 00 33 33", hexencode(tup)
+ )
+ self.assertEqual(
+ "02 07 08 09 " "02 04 05 06", # deltaX: [7, 8, 9] # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_embeddedPeak_nonIntermediate_privatePoints(self):
+ var = TupleVariation(
+ {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ tup, deltas = var.compile(axisTags=["wght", "wdth"])
+ # len(deltas)=9; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
+ # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
+ self.assertEqual("00 09 A0 00 20 00 33 33", hexencode(tup))
+ self.assertEqual(
+ "00 " # all points in glyph
+ "02 07 08 09 " # deltaX: [7, 8, 9]
+ "02 04 05 06", # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_embeddedPeak_nonIntermediate_privateConstants(self):
+ var = TupleVariation(
+ {"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)}, [7, 8, 9]
+ )
+ tup, deltas = var.compile(axisTags=["wght", "wdth"])
+ # len(deltas)=5; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
+ # embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
+ self.assertEqual("00 05 A0 00 20 00 33 33", hexencode(tup))
+ self.assertEqual(
+ "00 " "02 07 08 09", # all points in glyph # delta: [7, 8, 9]
+ hexencode(deltas),
+ )
+
+ def test_compile_embeddedPeak_intermediate_privatePoints(self):
+ var = TupleVariation(
+ {"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)}, [(7, 4), (8, 5), (9, 6)]
+ )
+ tup, deltas = var.compile(axisTags=["wght", "wdth"])
+ # len(deltas)=9;
+ # flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
+ # embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
+ self.assertEqual(
+ "00 09 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A", hexencode(tup)
+ )
+ self.assertEqual(
+ "00 " # all points in glyph
+ "02 07 08 09 " # deltaX: [7, 8, 9]
+ "02 04 05 06", # deltaY: [4, 5, 6]
+ hexencode(deltas),
+ )
+
+ def test_compile_embeddedPeak_intermediate_privateConstants(self):
+ var = TupleVariation(
+ {"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)}, [7, 8, 9]
+ )
+ tup, deltas = var.compile(axisTags=["wght", "wdth"])
+ # len(deltas)=5;
+ # flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
+ # embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
+ self.assertEqual(
+ "00 05 E0 00 20 00 33 33 19 9A 2C CD 26 66 39 9A", hexencode(tup)
+ )
+ self.assertEqual(
+ "00 " "02 07 08 09", # all points in glyph # delta: [7, 8, 9]
+ hexencode(deltas),
+ )
+
+ def test_compileCoord(self):
+ var = TupleVariation(
+ {"wght": (-1.0, -1.0, -1.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4
+ )
+ self.assertEqual("C0 00 20 00", hexencode(var.compileCoord(["wght", "wdth"])))
+ self.assertEqual("20 00 C0 00", hexencode(var.compileCoord(["wdth", "wght"])))
+ self.assertEqual("C0 00", hexencode(var.compileCoord(["wght"])))
+
+ def test_compileIntermediateCoord(self):
+ var = TupleVariation(
+ {"wght": (-1.0, -1.0, 0.0), "wdth": (0.4, 0.5, 0.6)}, [None] * 4
+ )
+ self.assertEqual(
+ "C0 00 19 9A 00 00 26 66",
+ hexencode(var.compileIntermediateCoord(["wght", "wdth"])),
+ )
+ self.assertEqual(
+ "19 9A C0 00 26 66 00 00",
+ hexencode(var.compileIntermediateCoord(["wdth", "wght"])),
+ )
+ self.assertEqual(None, var.compileIntermediateCoord(["wght"]))
+ self.assertEqual(
+ "19 9A 26 66", hexencode(var.compileIntermediateCoord(["wdth"]))
+ )
+
+ def test_decompileCoord(self):
+ decompileCoord = TupleVariation.decompileCoord_
+ data = deHexStr("DE AD C0 00 20 00 DE AD")
+ self.assertEqual(
+ ({"wght": -1.0, "wdth": 0.5}, 6), decompileCoord(["wght", "wdth"], data, 2)
+ )
+
+ def test_decompileCoord_roundTrip(self):
+ # Make sure we are not affected by https://github.com/fonttools/fonttools/issues/286
+ data = deHexStr("7F B9 80 35")
+ values, _ = TupleVariation.decompileCoord_(["wght", "wdth"], data, 0)
+ axisValues = {axis: (val, val, val) for axis, val in values.items()}
+ var = TupleVariation(axisValues, [None] * 4)
+ self.assertEqual("7F B9 80 35", hexencode(var.compileCoord(["wght", "wdth"])))
+
+ def test_compilePoints(self):
+ compilePoints = lambda p: TupleVariation.compilePoints(set(p))
+ self.assertEqual("00", hexencode(compilePoints(set()))) # all points in glyph
+ self.assertEqual("01 00 07", hexencode(compilePoints([7])))
+ self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535])))
+ self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15])))
+ self.assertEqual(
+ "06 05 07 01 F7 02 01 F2",
+ hexencode(compilePoints([7, 8, 255, 257, 258, 500])),
+ )
+ self.assertEqual("03 01 07 01 80 01 EC", hexencode(compilePoints([7, 8, 500])))
+ self.assertEqual(
+ "04 01 07 01 81 BE E7 0C 0F",
+ hexencode(compilePoints([7, 8, 0xBEEF, 0xCAFE])),
+ )
+ self.maxDiff = None
+ self.assertEqual(
+ "81 2C"
+ + " 7F 00" # 300 points (0x12c) in total
+ + (127 * " 01")
+ + " 7F" # first run, contains 128 points: [0 .. 127]
+ + (128 * " 01")
+ + " 2B" # second run, contains 128 points: [128 .. 255]
+ + (44 * " 01"), # third run, contains 44 points: [256 .. 299]
+ hexencode(compilePoints(range(300))),
+ )
+ self.assertEqual(
+ "81 8F"
+ + " 7F 00" # 399 points (0x18f) in total
+ + (127 * " 01")
+ + " 7F" # first run, contains 128 points: [0 .. 127]
+ + (128 * " 01")
+ + " 7F" # second run, contains 128 points: [128 .. 255]
+ + (128 * " 01")
+ + " 0E" # third run, contains 128 points: [256 .. 383]
+ + (15 * " 01"), # fourth run, contains 15 points: [384 .. 398]
+ hexencode(compilePoints(range(399))),
+ )
+
+ def test_decompilePoints(self):
+ numPointsInGlyph = 65536
+ allPoints = list(range(numPointsInGlyph))
+
+ def decompilePoints(data, offset):
+ points, offset = TupleVariation.decompilePoints_(
+ numPointsInGlyph, deHexStr(data), offset, "gvar"
+ )
+ # Conversion to list needed for Python 3.
+ return (list(points), offset)
+
+ # all points in glyph
+ self.assertEqual((allPoints, 1), decompilePoints("00", 0))
+ # all points in glyph (in overly verbose encoding, not explicitly prohibited by spec)
+ self.assertEqual((allPoints, 2), decompilePoints("80 00", 0))
+ # 2 points; first run: [9, 9+6]
+ self.assertEqual(([9, 15], 4), decompilePoints("02 01 09 06", 0))
+ # 2 points; first run: [0xBEEF, 0xCAFE]. (0x0C0F = 0xCAFE - 0xBEEF)
+ self.assertEqual(([0xBEEF, 0xCAFE], 6), decompilePoints("02 81 BE EF 0C 0F", 0))
+ # 1 point; first run: [7]
+ self.assertEqual(([7], 3), decompilePoints("01 00 07", 0))
+ # 1 point; first run: [7] in overly verbose encoding
+ self.assertEqual(([7], 4), decompilePoints("01 80 00 07", 0))
+ # 1 point; first run: [65535]; requires words to be treated as unsigned numbers
+ self.assertEqual(([65535], 4), decompilePoints("01 80 FF FF", 0))
+ # 4 points; first run: [7, 8]; second run: [255, 257]. 257 is stored in delta-encoded bytes (0xFF + 2).
+ self.assertEqual(
+ ([7, 8, 263, 265], 7), decompilePoints("04 01 07 01 01 FF 02", 0)
+ )
+ # combination of all encodings, preceded and followed by 4 bytes of unused data
+ data = "DE AD DE AD 04 01 07 01 81 BE E7 0C 0F DE AD DE AD"
+ self.assertEqual(([7, 8, 0xBEEF, 0xCAFE], 13), decompilePoints(data, 4))
+ self.assertSetEqual(
+ set(range(300)),
+ set(
+ decompilePoints(
+ "81 2C"
+ + " 7F 00" # 300 points (0x12c) in total
+ + (127 * " 01")
+ + " 7F" # first run, contains 128 points: [0 .. 127]
+ + (128 * " 01")
+ + " AB" # second run, contains 128 points: [128 .. 255]
+ + (44 * " 00 01"), # third run, contains 44 points: [256 .. 299]
+ 0,
+ )[0]
+ ),
+ )
+ self.assertSetEqual(
+ set(range(399)),
+ set(
+ decompilePoints(
+ "81 8F"
+ + " 7F 00" # 399 points (0x18f) in total
+ + (127 * " 01")
+ + " 7F" # first run, contains 128 points: [0 .. 127]
+ + (128 * " 01")
+ + " FF" # second run, contains 128 points: [128 .. 255]
+ + (128 * " 00 01")
+ + " 8E" # third run, contains 128 points: [256 .. 383]
+ + (15 * " 00 01"), # fourth run, contains 15 points: [384 .. 398]
+ 0,
+ )[0]
+ ),
+ )
+
+ def test_decompilePoints_shouldAcceptBadPointNumbers(self):
+ decompilePoints = TupleVariation.decompilePoints_
+ # 2 points; first run: [3, 9].
+ numPointsInGlyph = 8
+ with CapturingLogHandler(log, "WARNING") as captor:
+ decompilePoints(numPointsInGlyph, deHexStr("02 01 03 06"), 0, "cvar")
+ self.assertIn(
+ "point 9 out of range in 'cvar' table", [r.msg for r in captor.records]
+ )
+
+ def test_decompilePoints_roundTrip(self):
+ numPointsInGlyph = (
+ 500 # greater than 255, so we also exercise code path for 16-bit encoding
+ )
+ compile = lambda points: TupleVariation.compilePoints(points)
+ decompile = lambda data: set(
+ TupleVariation.decompilePoints_(numPointsInGlyph, data, 0, "gvar")[0]
+ )
+ for i in range(50):
+ points = set(random.sample(range(numPointsInGlyph), 30))
+ self.assertSetEqual(
+ points,
+ decompile(compile(points)),
+ "failed round-trip decompile/compilePoints; points=%s" % points,
+ )
+ allPoints = set(range(numPointsInGlyph))
+ self.assertSetEqual(allPoints, decompile(compile(allPoints)))
+ self.assertSetEqual(allPoints, decompile(compile(set())))
+
+ def test_compileDeltas_points(self):
+ var = TupleVariation({}, [None, (1, 0), (2, 0), None, (4, 0), None])
+ # deltaX for points: [1, 2, 4]; deltaY for points: [0, 0, 0]
+ self.assertEqual("02 01 02 04 82", hexencode(var.compileDeltas()))
+
+ def test_compileDeltas_constants(self):
+ var = TupleVariation({}, [None, 1, 2, None, 4, None])
+ # delta for cvts: [1, 2, 4]
+ self.assertEqual("02 01 02 04", hexencode(var.compileDeltas()))
+
+ def test_compileDeltaValues(self):
+ compileDeltaValues = lambda values: hexencode(
+ TupleVariation.compileDeltaValues_(values)
+ )
+ # zeroes
+ self.assertEqual("80", compileDeltaValues([0]))
+ self.assertEqual("BF", compileDeltaValues([0] * 64))
+ self.assertEqual("BF 80", compileDeltaValues([0] * 65))
+ self.assertEqual("BF A3", compileDeltaValues([0] * 100))
+ self.assertEqual("BF BF BF BF", compileDeltaValues([0] * 256))
+ # bytes
+ self.assertEqual("00 01", compileDeltaValues([1]))
+ self.assertEqual(
+ "06 01 02 03 7F 80 FF FE", compileDeltaValues([1, 2, 3, 127, -128, -1, -2])
+ )
+ self.assertEqual("3F" + (64 * " 7F"), compileDeltaValues([127] * 64))
+ self.assertEqual("3F" + (64 * " 7F") + " 00 7F", compileDeltaValues([127] * 65))
+ # words
+ self.assertEqual("40 66 66", compileDeltaValues([0x6666]))
+ self.assertEqual(
+ "43 66 66 7F FF FF FF 80 00",
+ compileDeltaValues([0x6666, 32767, -1, -32768]),
+ )
+ self.assertEqual("7F" + (64 * " 11 22"), compileDeltaValues([0x1122] * 64))
+ self.assertEqual(
+ "7F" + (64 * " 11 22") + " 40 11 22", compileDeltaValues([0x1122] * 65)
+ )
+ # bytes, zeroes, bytes: a single zero is more compact when encoded as part of the bytes run
+ self.assertEqual(
+ "04 7F 7F 00 7F 7F", compileDeltaValues([127, 127, 0, 127, 127])
+ )
+ self.assertEqual(
+ "01 7F 7F 81 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 127, 127])
+ )
+ self.assertEqual(
+ "01 7F 7F 82 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 127, 127])
+ )
+ self.assertEqual(
+ "01 7F 7F 83 01 7F 7F", compileDeltaValues([127, 127, 0, 0, 0, 0, 127, 127])
+ )
+ # bytes, zeroes
+ self.assertEqual("01 01 00", compileDeltaValues([1, 0]))
+ self.assertEqual("00 01 81", compileDeltaValues([1, 0, 0]))
+ # words, bytes, words: a single byte is more compact when encoded as part of the words run
+ self.assertEqual(
+ "42 66 66 00 02 77 77", compileDeltaValues([0x6666, 2, 0x7777])
+ )
+ self.assertEqual(
+ "40 66 66 01 02 02 40 77 77", compileDeltaValues([0x6666, 2, 2, 0x7777])
+ )
+ # words, zeroes, words
+ self.assertEqual(
+ "40 66 66 80 40 77 77", compileDeltaValues([0x6666, 0, 0x7777])
+ )
+ self.assertEqual(
+ "40 66 66 81 40 77 77", compileDeltaValues([0x6666, 0, 0, 0x7777])
+ )
+ self.assertEqual(
+ "40 66 66 82 40 77 77", compileDeltaValues([0x6666, 0, 0, 0, 0x7777])
+ )
+ # words, zeroes, bytes
+ self.assertEqual(
+ "40 66 66 80 02 01 02 03", compileDeltaValues([0x6666, 0, 1, 2, 3])
+ )
+ self.assertEqual(
+ "40 66 66 81 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 1, 2, 3])
+ )
+ self.assertEqual(
+ "40 66 66 82 02 01 02 03", compileDeltaValues([0x6666, 0, 0, 0, 1, 2, 3])
+ )
+ # words, zeroes
+ self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0]))
+ self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0]))
+
+ def test_decompileDeltas(self):
+ decompileDeltas = TupleVariation.decompileDeltas_
+ # 83 = zero values (0x80), count = 4 (1 + 0x83 & 0x3F)
+ self.assertEqual(([0, 0, 0, 0], 1), decompileDeltas(4, deHexStr("83"), 0))
+ # 41 01 02 FF FF = signed 16-bit values (0x40), count = 2 (1 + 0x41 & 0x3F)
+ self.assertEqual(
+ ([258, -1], 5), decompileDeltas(2, deHexStr("41 01 02 FF FF"), 0)
+ )
+ # 01 81 07 = signed 8-bit values, count = 2 (1 + 0x01 & 0x3F)
+ self.assertEqual(([-127, 7], 3), decompileDeltas(2, deHexStr("01 81 07"), 0))
+ # combination of all three encodings, preceded and followed by 4 bytes of unused data
+ data = deHexStr("DE AD BE EF 83 40 01 02 01 81 80 DE AD BE EF")
+ self.assertEqual(
+ ([0, 0, 0, 0, 258, -127, -128], 11), decompileDeltas(7, data, 4)
+ )
+
+ def test_decompileDeltas_roundTrip(self):
+ numDeltas = 30
+ compile = TupleVariation.compileDeltaValues_
+ decompile = lambda data: TupleVariation.decompileDeltas_(numDeltas, data, 0)[0]
+ for i in range(50):
+ deltas = random.sample(range(-128, 127), 10)
+ deltas.extend(random.sample(range(-32768, 32767), 10))
+ deltas.extend([0] * 10)
+ random.shuffle(deltas)
+ self.assertListEqual(deltas, decompile(compile(deltas)))
+
+ def test_compileSharedTuples(self):
+ # Below, the peak coordinate {"wght": 1.0, "wdth": 0.8} appears
+ # three times (most frequent sorted first); {"wght": 1.0, "wdth": 0.5}
+ # and {"wght": 1.0, "wdth": 0.7} both appears two times (tie) and
+ # are sorted alphanumerically to ensure determinism.
+ # The peak coordinate {"wght": 1.0, "wdth": 0.9} appears only once
+ # and is thus ignored.
+ # Because the start and end of variation ranges is not encoded
+ # into the shared pool, they should get ignored.
+ deltas = [None] * 4
+ variations = [
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.7, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.7, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.2, 0.8, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.5, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.8, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.3, 0.9, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.4, 0.8, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (0.5, 0.5, 1.0)}, deltas),
+ ]
+ result = compileSharedTuples(["wght", "wdth"], variations)
+ self.assertEqual(
+ [hexencode(c) for c in result],
+ ["40 00 33 33", "40 00 20 00", "40 00 2C CD"],
+ )
+
+ def test_decompileSharedTuples_Skia(self):
+ sharedTuples = decompileSharedTuples(
+ axisTags=["wght", "wdth"],
+ sharedTupleCount=8,
+ data=SKIA_GVAR_SHARED_TUPLES_DATA,
+ offset=0,
+ )
+ self.assertEqual(sharedTuples, SKIA_GVAR_SHARED_TUPLES)
+
+ def test_decompileSharedTuples_empty(self):
+ self.assertEqual(decompileSharedTuples(["wght"], 0, b"", 0), [])
+
+ def test_compileTupleVariationStore_allVariationsRedundant(self):
+ axes = {"wght": (0.3, 0.4, 0.5), "opsz": (0.7, 0.8, 0.9)}
+ variations = [
+ TupleVariation(axes, [None] * 4),
+ TupleVariation(axes, [None] * 4),
+ TupleVariation(axes, [None] * 4),
+ ]
+ self.assertEqual(
+ compileTupleVariationStore(
+ variations,
+ pointCount=8,
+ axisTags=["wght", "opsz"],
+ sharedTupleIndices={},
+ ),
+ (0, b"", b""),
+ )
+
+ def test_compileTupleVariationStore_noVariations(self):
+ self.assertEqual(
+ compileTupleVariationStore(
+ variations=[],
+ pointCount=8,
+ axisTags=["wght", "opsz"],
+ sharedTupleIndices={},
+ ),
+ (0, b"", b""),
+ )
+
+ def test_compileTupleVariationStore_roundTrip_cvar(self):
+ deltas = [1, 2, 3, 4]
+ variations = [
+ TupleVariation({"wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
+ ]
+ tupleVariationCount, tuples, data = compileTupleVariationStore(
+ variations, pointCount=4, axisTags=["wght", "wdth"], sharedTupleIndices={}
+ )
+ self.assertEqual(
+ decompileTupleVariationStore(
+ "cvar",
+ ["wght", "wdth"],
+ tupleVariationCount,
+ pointCount=4,
+ sharedTuples={},
+ data=(tuples + data),
+ pos=0,
+ dataPos=len(tuples),
+ ),
+ variations,
+ )
+
+ def test_compileTupleVariationStore_roundTrip_gvar(self):
+ deltas = [(1, 1), (2, 2), (3, 3), (4, 4)]
+ variations = [
+ TupleVariation({"wght": (0.5, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
+ TupleVariation({"wght": (1.0, 1.0, 1.0), "wdth": (1.0, 1.0, 1.0)}, deltas),
+ ]
+ tupleVariationCount, tuples, data = compileTupleVariationStore(
+ variations, pointCount=4, axisTags=["wght", "wdth"], sharedTupleIndices={}
+ )
+ self.assertEqual(
+ decompileTupleVariationStore(
+ "gvar",
+ ["wght", "wdth"],
+ tupleVariationCount,
+ pointCount=4,
+ sharedTuples={},
+ data=(tuples + data),
+ pos=0,
+ dataPos=len(tuples),
+ ),
+ variations,
+ )
+
+ def test_decompileTupleVariationStore_Skia_I(self):
+ tvar = decompileTupleVariationStore(
+ tableTag="gvar",
+ axisTags=["wght", "wdth"],
+ tupleVariationCount=8,
+ pointCount=18,
+ sharedTuples=SKIA_GVAR_SHARED_TUPLES,
+ data=SKIA_GVAR_I_DATA,
+ pos=4,
+ dataPos=36,
+ )
+ self.assertEqual(len(tvar), 8)
+ self.assertEqual(tvar[0].axes, {"wght": (0.0, 1.0, 1.0)})
+ self.assertEqual(
+ " ".join(["%d,%d" % c for c in tvar[0].coordinates]),
+ "257,0 -127,0 -128,58 -130,90 -130,62 -130,67 -130,32 -127,0 "
+ "257,0 259,14 260,64 260,21 260,69 258,124 0,0 130,0 0,0 0,0",
+ )
+
+ def test_decompileTupleVariationStore_empty(self):
+ self.assertEqual(
+ decompileTupleVariationStore(
+ tableTag="gvar",
+ axisTags=[],
+ tupleVariationCount=0,
+ pointCount=5,
+ sharedTuples=[],
+ data=b"",
+ pos=4,
+ dataPos=4,
+ ),
+ [],
+ )
+
+ def test_getTupleSize(self):
+ getTupleSize = TupleVariation.getTupleSize_
+ numAxes = 3
+ self.assertEqual(4 + numAxes * 2, getTupleSize(0x8042, numAxes))
+ self.assertEqual(4 + numAxes * 4, getTupleSize(0x4077, numAxes))
+ self.assertEqual(4, getTupleSize(0x2077, numAxes))
+ self.assertEqual(4, getTupleSize(11, numAxes))
+
+ def test_inferRegion(self):
+ start, end = inferRegion_({"wght": -0.3, "wdth": 0.7})
+ self.assertEqual(start, {"wght": -0.3, "wdth": 0.0})
+ self.assertEqual(end, {"wght": 0.0, "wdth": 0.7})
+
+ @staticmethod
+ def xml_lines(writer):
+ content = writer.file.getvalue().decode("utf-8")
+ return [line.strip() for line in content.splitlines()][1:]
+
+ def test_getCoordWidth(self):
+ empty = TupleVariation({}, [])
+ self.assertEqual(empty.getCoordWidth(), 0)
+
+ empty = TupleVariation({}, [None])
+ self.assertEqual(empty.getCoordWidth(), 0)
+
+ gvarTuple = TupleVariation({}, [None, (0, 0)])
+ self.assertEqual(gvarTuple.getCoordWidth(), 2)
+
+ cvarTuple = TupleVariation({}, [None, 0])
+ self.assertEqual(cvarTuple.getCoordWidth(), 1)
+
+ cvarTuple.coordinates[1] *= 1.0
+ self.assertEqual(cvarTuple.getCoordWidth(), 1)
+
+ with self.assertRaises(TypeError):
+ TupleVariation({}, [None, "a"]).getCoordWidth()
+
+ def test_scaleDeltas_cvar(self):
+ var = TupleVariation({}, [100, None])
+
+ var.scaleDeltas(1.0)
+ self.assertEqual(var.coordinates, [100, None])
+
+ var.scaleDeltas(0.333)
+ self.assertAlmostEqual(var.coordinates[0], 33.3)
+ self.assertIsNone(var.coordinates[1])
+
+ var.scaleDeltas(0.0)
+ self.assertEqual(var.coordinates, [0, None])
+
+ def test_scaleDeltas_gvar(self):
+ var = TupleVariation({}, [(100, 200), None])
+
+ var.scaleDeltas(1.0)
+ self.assertEqual(var.coordinates, [(100, 200), None])
+
+ var.scaleDeltas(0.333)
+ self.assertAlmostEqual(var.coordinates[0][0], 33.3)
+ self.assertAlmostEqual(var.coordinates[0][1], 66.6)
+ self.assertIsNone(var.coordinates[1])
+
+ var.scaleDeltas(0.0)
+ self.assertEqual(var.coordinates, [(0, 0), None])
+
+ def test_roundDeltas_cvar(self):
+ var = TupleVariation({}, [55.5, None, 99.9])
+ var.roundDeltas()
+ self.assertEqual(var.coordinates, [56, None, 100])
+
+ def test_roundDeltas_gvar(self):
+ var = TupleVariation({}, [(55.5, 100.0), None, (99.9, 100.0)])
+ var.roundDeltas()
+ self.assertEqual(var.coordinates, [(56, 100), None, (100, 100)])
+
+ def test_calcInferredDeltas(self):
+ var = TupleVariation({}, [(0, 0), None, None, None])
+ coords = [(1, 1), (1, 1), (1, 1), (1, 1)]
+
+ var.calcInferredDeltas(coords, [])
+
+ self.assertEqual(var.coordinates, [(0, 0), (0, 0), (0, 0), (0, 0)])
+
+ def test_calcInferredDeltas_invalid(self):
+ # cvar tuples can't have inferred deltas
+ with self.assertRaises(TypeError):
+ TupleVariation({}, [0]).calcInferredDeltas([], [])
+
+ # origCoords must have same length as self.coordinates
+ with self.assertRaises(ValueError):
+ TupleVariation({}, [(0, 0), None]).calcInferredDeltas([], [])
+
+ # at least 4 phantom points required
+ with self.assertRaises(AssertionError):
+ TupleVariation({}, [(0, 0), None]).calcInferredDeltas([(0, 0), (0, 0)], [])
+
+ with self.assertRaises(AssertionError):
+ TupleVariation({}, [(0, 0)] + [None] * 5).calcInferredDeltas(
+ [(0, 0)] * 6, [1, 0] # endPts not in increasing order
+ )
+
+ def test_optimize(self):
+ var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 5)
+
+ var.optimize([(0, 0)] * 5, [0])
+
+ self.assertEqual(var.coordinates, [None, None, None, None, None])
+
+ def test_optimize_isComposite(self):
+ # when a composite glyph's deltas are all (0, 0), we still want
+ # to write out an entry in gvar, else macOS doesn't apply any
+ # variations to the composite glyph (even if its individual components
+ # do vary).
+ # https://github.com/fonttools/fonttools/issues/1381
+ var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 5)
+ var.optimize([(0, 0)] * 5, [0], isComposite=True)
+ self.assertEqual(var.coordinates, [(0, 0)] * 5)
+
+ # it takes more than 128 (0, 0) deltas before the optimized tuple with
+ # (None) inferred deltas (except for the first) becomes smaller than
+ # the un-optimized one that has all deltas explicitly set to (0, 0).
+ var = TupleVariation({"wght": (0.0, 1.0, 1.0)}, [(0, 0)] * 129)
+ var.optimize([(0, 0)] * 129, list(range(129 - 4)), isComposite=True)
+ self.assertEqual(var.coordinates, [(0, 0)] + [None] * 128)
+
+ def test_sum_deltas_gvar(self):
+ var1 = TupleVariation(
+ {},
+ [
+ (-20, 0),
+ (-20, 0),
+ (20, 0),
+ (20, 0),
+ (0, 0),
+ (0, 0),
+ (0, 0),
+ (0, 0),
+ ],
+ )
+ var2 = TupleVariation(
+ {},
+ [
+ (-10, 0),
+ (-10, 0),
+ (10, 0),
+ (10, 0),
+ (0, 0),
+ (20, 0),
+ (0, 0),
+ (0, 0),
+ ],
+ )
+
+ var1 += var2
+
+ self.assertEqual(
+ var1.coordinates,
+ [
+ (-30, 0),
+ (-30, 0),
+ (30, 0),
+ (30, 0),
+ (0, 0),
+ (20, 0),
+ (0, 0),
+ (0, 0),
+ ],
+ )
+
+ def test_sum_deltas_gvar_invalid_length(self):
+ var1 = TupleVariation({}, [(1, 2)])
+ var2 = TupleVariation({}, [(1, 2), (3, 4)])
+
+ with self.assertRaisesRegex(ValueError, "deltas with different lengths"):
+ var1 += var2
+
+ def test_sum_deltas_gvar_with_inferred_points(self):
+ var1 = TupleVariation({}, [(1, 2), None])
+ var2 = TupleVariation({}, [(2, 3), None])
+
+ with self.assertRaisesRegex(ValueError, "deltas with inferred points"):
+ var1 += var2
+
+ def test_sum_deltas_cvar(self):
+ axes = {"wght": (0.0, 1.0, 1.0)}
+ var1 = TupleVariation(axes, [0, 1, None, None])
+ var2 = TupleVariation(axes, [None, 2, None, 3])
+ var3 = TupleVariation(axes, [None, None, None, 4])
+
+ var1 += var2
+ var1 += var3
+
+ self.assertEqual(var1.coordinates, [0, 3, None, 7])
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_a_n_k_r_test.py b/Tests/ttLib/tables/_a_n_k_r_test.py
index 6c9be16d..ab873328 100644
--- a/Tests/ttLib/tables/_a_n_k_r_test.py
+++ b/Tests/ttLib/tables/_a_n_k_r_test.py
@@ -13,135 +13,134 @@ import unittest
# what our encoder emits. (The value for end-of-table markers
# does not actually matter).
ANKR_FORMAT_0_DATA = deHexStr(
- '0000 0000 ' # 0: Format=0, Flags=0
- '0000 000C ' # 4: LookupTableOffset=12
- '0000 0024 ' # 8: GlyphDataTableOffset=36
- '0006 0004 0002 ' # 12: LookupFormat=6, UnitSize=4, NUnits=2
- '0008 0001 0000 ' # 18: SearchRange=8, EntrySelector=1, RangeShift=0
- '0001 0000 ' # 24: Glyph=A, Offset=0 (+GlyphDataTableOffset=36)
- '0003 0008 ' # 28: Glyph=C, Offset=8 (+GlyphDataTableOffset=44)
- 'FFFF 0000 ' # 32: Glyph=<end>, Offset=<n/a>
- '0000 0001 ' # 36: GlyphData[A].NumPoints=1
- '0235 045E ' # 40: GlyphData[A].Points[0].X=565, .Y=1118
- '0000 0001 ' # 44: GlyphData[C].NumPoints=1
- 'FED2 045E ' # 48: GlyphData[C].Points[0].X=-302, .Y=1118
-) # 52: <end>
+ "0000 0000 " # 0: Format=0, Flags=0
+ "0000 000C " # 4: LookupTableOffset=12
+ "0000 0024 " # 8: GlyphDataTableOffset=36
+ "0006 0004 0002 " # 12: LookupFormat=6, UnitSize=4, NUnits=2
+ "0008 0001 0000 " # 18: SearchRange=8, EntrySelector=1, RangeShift=0
+ "0001 0000 " # 24: Glyph=A, Offset=0 (+GlyphDataTableOffset=36)
+ "0003 0008 " # 28: Glyph=C, Offset=8 (+GlyphDataTableOffset=44)
+ "FFFF 0000 " # 32: Glyph=<end>, Offset=<n/a>
+ "0000 0001 " # 36: GlyphData[A].NumPoints=1
+ "0235 045E " # 40: GlyphData[A].Points[0].X=565, .Y=1118
+ "0000 0001 " # 44: GlyphData[C].NumPoints=1
+ "FED2 045E " # 48: GlyphData[C].Points[0].X=-302, .Y=1118
+) # 52: <end>
assert len(ANKR_FORMAT_0_DATA) == 52
ANKR_FORMAT_0_XML = [
'<AnchorPoints Format="0">',
' <Flags value="0"/>',
- ' <Anchors>',
+ " <Anchors>",
' <Lookup glyph="A">',
- ' <!-- AnchorPointCount=1 -->',
+ " <!-- AnchorPointCount=1 -->",
' <AnchorPoint index="0">',
' <XCoordinate value="565"/>',
' <YCoordinate value="1118"/>',
- ' </AnchorPoint>',
- ' </Lookup>',
+ " </AnchorPoint>",
+ " </Lookup>",
' <Lookup glyph="C">',
- ' <!-- AnchorPointCount=1 -->',
+ " <!-- AnchorPointCount=1 -->",
' <AnchorPoint index="0">',
' <XCoordinate value="-302"/>',
' <YCoordinate value="1118"/>',
- ' </AnchorPoint>',
- ' </Lookup>',
- ' </Anchors>',
- '</AnchorPoints>',
+ " </AnchorPoint>",
+ " </Lookup>",
+ " </Anchors>",
+ "</AnchorPoints>",
]
# Same data as ANKR_FORMAT_0_DATA, but with chunks of unused data
# whose presence should not stop us from decompiling the table.
ANKR_FORMAT_0_STRAY_DATA = deHexStr(
- '0000 0000 ' # 0: Format=0, Flags=0
- '0000 0018 ' # 4: LookupTableOffset=24
- '0000 0034 ' # 8: GlyphDataTableOffset=52
- 'DEAD BEEF CAFE ' # 12: <stray data>
- 'DEAD BEEF CAFE ' # 18: <stray data>
- '0006 0004 0002 ' # 24: LookupFormat=6, UnitSize=4, NUnits=2
- '0008 0001 0000 ' # 30: SearchRange=8, EntrySelector=1, RangeShift=0
- '0001 0000 ' # 36: Glyph=A, Offset=0 (+GlyphDataTableOffset=52)
- '0003 0008 ' # 40: Glyph=C, Offset=8 (+GlyphDataTableOffset=60)
- 'FFFF 0000 ' # 44: Glyph=<end>, Offset=<n/a>
- 'BEEF F00D ' # 48: <stray data>
- '0000 0001 ' # 52: GlyphData[A].NumPoints=1
- '0235 045E ' # 56: GlyphData[A].Points[0].X=565, .Y=1118
- '0000 0001 ' # 60: GlyphData[C].NumPoints=1
- 'FED2 045E ' # 64: GlyphData[C].Points[0].X=-302, .Y=1118
-) # 68: <end>
+ "0000 0000 " # 0: Format=0, Flags=0
+ "0000 0018 " # 4: LookupTableOffset=24
+ "0000 0034 " # 8: GlyphDataTableOffset=52
+ "DEAD BEEF CAFE " # 12: <stray data>
+ "DEAD BEEF CAFE " # 18: <stray data>
+ "0006 0004 0002 " # 24: LookupFormat=6, UnitSize=4, NUnits=2
+ "0008 0001 0000 " # 30: SearchRange=8, EntrySelector=1, RangeShift=0
+ "0001 0000 " # 36: Glyph=A, Offset=0 (+GlyphDataTableOffset=52)
+ "0003 0008 " # 40: Glyph=C, Offset=8 (+GlyphDataTableOffset=60)
+ "FFFF 0000 " # 44: Glyph=<end>, Offset=<n/a>
+ "BEEF F00D " # 48: <stray data>
+ "0000 0001 " # 52: GlyphData[A].NumPoints=1
+ "0235 045E " # 56: GlyphData[A].Points[0].X=565, .Y=1118
+ "0000 0001 " # 60: GlyphData[C].NumPoints=1
+ "FED2 045E " # 64: GlyphData[C].Points[0].X=-302, .Y=1118
+) # 68: <end>
assert len(ANKR_FORMAT_0_STRAY_DATA) == 68
# Constructed test case where glyphs A and D share the same anchor data.
ANKR_FORMAT_0_SHARING_DATA = deHexStr(
- '0000 0000 ' # 0: Format=0, Flags=0
- '0000 000C ' # 4: LookupTableOffset=12
- '0000 0028 ' # 8: GlyphDataTableOffset=40
- '0006 0004 0003 ' # 12: LookupFormat=6, UnitSize=4, NUnits=3
- '0008 0001 0004 ' # 18: SearchRange=8, EntrySelector=1, RangeShift=4
- '0001 0000 ' # 24: Glyph=A, Offset=0 (+GlyphDataTableOffset=36)
- '0003 0008 ' # 28: Glyph=C, Offset=8 (+GlyphDataTableOffset=44)
- '0004 0000 ' # 32: Glyph=D, Offset=0 (+GlyphDataTableOffset=36)
- 'FFFF 0000 ' # 36: Glyph=<end>, Offset=<n/a>
- '0000 0001 ' # 40: GlyphData[A].NumPoints=1
- '0235 045E ' # 44: GlyphData[A].Points[0].X=565, .Y=1118
- '0000 0002 ' # 48: GlyphData[C].NumPoints=2
- '000B 000C ' # 52: GlyphData[C].Points[0].X=11, .Y=12
- '001B 001C ' # 56: GlyphData[C].Points[1].X=27, .Y=28
-) # 60: <end>
+ "0000 0000 " # 0: Format=0, Flags=0
+ "0000 000C " # 4: LookupTableOffset=12
+ "0000 0028 " # 8: GlyphDataTableOffset=40
+ "0006 0004 0003 " # 12: LookupFormat=6, UnitSize=4, NUnits=3
+ "0008 0001 0004 " # 18: SearchRange=8, EntrySelector=1, RangeShift=4
+ "0001 0000 " # 24: Glyph=A, Offset=0 (+GlyphDataTableOffset=36)
+ "0003 0008 " # 28: Glyph=C, Offset=8 (+GlyphDataTableOffset=44)
+ "0004 0000 " # 32: Glyph=D, Offset=0 (+GlyphDataTableOffset=36)
+ "FFFF 0000 " # 36: Glyph=<end>, Offset=<n/a>
+ "0000 0001 " # 40: GlyphData[A].NumPoints=1
+ "0235 045E " # 44: GlyphData[A].Points[0].X=565, .Y=1118
+ "0000 0002 " # 48: GlyphData[C].NumPoints=2
+ "000B 000C " # 52: GlyphData[C].Points[0].X=11, .Y=12
+ "001B 001C " # 56: GlyphData[C].Points[1].X=27, .Y=28
+) # 60: <end>
assert len(ANKR_FORMAT_0_SHARING_DATA) == 60
ANKR_FORMAT_0_SHARING_XML = [
'<AnchorPoints Format="0">',
' <Flags value="0"/>',
- ' <Anchors>',
+ " <Anchors>",
' <Lookup glyph="A">',
- ' <!-- AnchorPointCount=1 -->',
+ " <!-- AnchorPointCount=1 -->",
' <AnchorPoint index="0">',
' <XCoordinate value="565"/>',
' <YCoordinate value="1118"/>',
- ' </AnchorPoint>',
- ' </Lookup>',
+ " </AnchorPoint>",
+ " </Lookup>",
' <Lookup glyph="C">',
- ' <!-- AnchorPointCount=2 -->',
+ " <!-- AnchorPointCount=2 -->",
' <AnchorPoint index="0">',
' <XCoordinate value="11"/>',
' <YCoordinate value="12"/>',
- ' </AnchorPoint>',
+ " </AnchorPoint>",
' <AnchorPoint index="1">',
' <XCoordinate value="27"/>',
' <YCoordinate value="28"/>',
- ' </AnchorPoint>',
- ' </Lookup>',
+ " </AnchorPoint>",
+ " </Lookup>",
' <Lookup glyph="D">',
- ' <!-- AnchorPointCount=1 -->',
+ " <!-- AnchorPointCount=1 -->",
' <AnchorPoint index="0">',
' <XCoordinate value="565"/>',
' <YCoordinate value="1118"/>',
- ' </AnchorPoint>',
- ' </Lookup>',
- ' </Anchors>',
- '</AnchorPoints>',
+ " </AnchorPoint>",
+ " </Lookup>",
+ " </Anchors>",
+ "</AnchorPoints>",
]
class ANKRTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.notdef', 'A', 'B', 'C', 'D'])
+ cls.font = FakeFont([".notdef", "A", "B", "C", "D"])
def decompileToXML(self, data, xml):
- table = newTable('ankr')
+ table = newTable("ankr")
table.decompile(data, self.font)
self.assertEqual(getXML(table.toXML), xml)
def compileFromXML(self, xml, data):
- table = newTable('ankr')
+ table = newTable("ankr")
for name, attrs, content in parseXML(xml):
table.fromXML(name, attrs, content, font=self.font)
self.assertEqual(hexStr(table.compile(self.font)), hexStr(data))
@@ -160,6 +159,7 @@ class ANKRTest(unittest.TestCase):
self.roundtrip(ANKR_FORMAT_0_SHARING_DATA, ANKR_FORMAT_0_SHARING_XML)
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_a_v_a_r_test.py b/Tests/ttLib/tables/_a_v_a_r_test.py
index 429ca2e8..dbe07b3a 100644
--- a/Tests/ttLib/tables/_a_v_a_r_test.py
+++ b/Tests/ttLib/tables/_a_v_a_r_test.py
@@ -1,9 +1,13 @@
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.misc.fixedTools import floatToFixed as fl2fi
+from fontTools.ttLib import TTFont, TTLibError
+import fontTools.ttLib.tables.otTables as otTables
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 fontTools.varLib.models as models
+import fontTools.varLib.varStore as varStore
from io import BytesIO
import unittest
@@ -11,7 +15,8 @@ import unittest
TEST_DATA = deHexStr(
"00 01 00 00 00 00 00 02 "
"00 04 C0 00 C0 00 00 00 00 00 13 33 33 33 40 00 40 00 "
- "00 03 C0 00 C0 00 00 00 00 00 40 00 40 00")
+ "00 03 C0 00 C0 00 00 00 00 00 40 00 40 00"
+)
class AxisVariationTableTest(unittest.TestCase):
@@ -35,43 +40,45 @@ class AxisVariationTableTest(unittest.TestCase):
def test_decompile(self):
avar = table__a_v_a_r()
avar.decompile(TEST_DATA, self.makeFont(["wdth", "wght"]))
- 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)
-
- def test_decompile_unsupportedVersion(self):
- avar = table__a_v_a_r()
- font = self.makeFont(["wdth", "wght"])
- self.assertRaises(TTLibError, avar.decompile, deHexStr("02 01 03 06 00 00 00 00"), font)
+ 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,
+ )
def test_toXML(self):
avar = table__a_v_a_r()
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([
- '<segment axis="opsz">',
+ self.assertEqual(
+ [
+ '<version major="1" minor="0"/>',
+ '<segment axis="opsz">',
'<mapping from="-1.0" to="-1.0"/>',
'<mapping from="0.0" to="0.0"/>',
'<mapping from="0.3" to="0.8"/>',
'<mapping from="1.0" to="1.0"/>',
- '</segment>'
- ], self.xml_lines(writer))
+ "</segment>",
+ ],
+ self.xml_lines(writer),
+ )
def test_fromXML(self):
avar = table__a_v_a_r()
for name, attrs, content in parseXML(
- '<segment axis="wdth">'
- ' <mapping from="-1.0" to="-1.0"/>'
- ' <mapping from="0.0" to="0.0"/>'
- ' <mapping from="0.7" to="0.2"/>'
- ' <mapping from="1.0" to="1.0"/>'
- '</segment>'):
+ '<segment axis="wdth">'
+ ' <mapping from="-1.0" to="-1.0"/>'
+ ' <mapping from="0.0" to="0.0"/>'
+ ' <mapping from="0.7" to="0.2"/>'
+ ' <mapping from="1.0" to="1.0"/>'
+ "</segment>"
+ ):
avar.fromXML(name, attrs, content, ttFont=None)
self.assertAvarAlmostEqual(
- {"wdth": {-1: -1, 0: 0, 0.7000122: 0.2000122, 1.0: 1.0}},
- avar.segments
+ {"wdth": {-1: -1, 0: 0, 0.7000122: 0.2000122, 1.0: 1.0}}, avar.segments
)
@staticmethod
@@ -82,7 +89,9 @@ class AxisVariationTableTest(unittest.TestCase):
axis = Axis()
axis.axisTag = tag
fvar.axes.append(axis)
- return {"fvar": fvar}
+ font = TTFont()
+ font["fvar"] = fvar
+ return font
@staticmethod
def xml_lines(writer):
@@ -90,6 +99,82 @@ class AxisVariationTableTest(unittest.TestCase):
return [line.strip() for line in content.splitlines()][1:]
+class Avar2Test(unittest.TestCase):
+ def test(self):
+ axisTags = ["wght", "wdth"]
+ fvar = table__f_v_a_r()
+ for tag in axisTags:
+ axis = Axis()
+ axis.axisTag = tag
+ fvar.axes.append(axis)
+
+ master_locations_normalized = [
+ {},
+ {"wght": 1, "wdth": -1},
+ ]
+ data = [
+ {},
+ {"wdth": -0.8},
+ ]
+
+ model = models.VariationModel(master_locations_normalized, axisTags)
+ store_builder = varStore.OnlineVarStoreBuilder(axisTags)
+ store_builder.setModel(model)
+ varIdxes = {}
+ for axis in axisTags:
+ masters = [fl2fi(m.get(axis, 0), 14) for m in data]
+ varIdxes[axis] = store_builder.storeMasters(masters)[1]
+ store = store_builder.finish()
+ mapping = store.optimize()
+ varIdxes = {axis: mapping[value] for axis, value in varIdxes.items()}
+ del model, store_builder, mapping
+
+ varIdxMap = otTables.DeltaSetIndexMap()
+ varIdxMap.Format = 1
+ varIdxMap.mapping = []
+ for tag in axisTags:
+ varIdxMap.mapping.append(varIdxes[tag])
+
+ avar = table__a_v_a_r()
+ avar.segments["wght"] = {}
+ avar.segments["wdth"] = {-1.0: -1.0, 0.0: 0.0, 0.4: 0.5, 1.0: 1.0}
+
+ avar.majorVersion = 2
+ avar.table = otTables.avar()
+ avar.table.VarIdxMap = varIdxMap
+ avar.table.VarStore = store
+
+ font = TTFont()
+ font["fvar"] = fvar
+ font["avar"] = avar
+
+ b = BytesIO()
+ font.save(b)
+ b.seek(0)
+ font2 = TTFont(b)
+
+ assert font2["avar"].table.VarStore.VarRegionList.RegionAxisCount == 2
+ assert font2["avar"].table.VarStore.VarRegionList.RegionCount == 1
+
+ xml1 = BytesIO()
+ writer = XMLWriter(xml1)
+ font["avar"].toXML(writer, font)
+
+ xml2 = BytesIO()
+ writer = XMLWriter(xml2)
+ font2["avar"].toXML(writer, font2)
+
+ assert xml1.getvalue() == xml2.getvalue(), (xml1.getvalue(), xml2.getvalue())
+
+ avar = table__a_v_a_r()
+ xml = b"".join(xml2.getvalue().splitlines()[1:])
+ for name, attrs, content in parseXML(xml):
+ avar.fromXML(name, attrs, content, ttFont=TTFont())
+ assert avar.table.VarStore.VarRegionList.RegionAxisCount == 2
+ assert avar.table.VarStore.VarRegionList.RegionCount == 1
+
+
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_b_s_l_n_test.py b/Tests/ttLib/tables/_b_s_l_n_test.py
index e40c1bd2..3ef3195f 100644
--- a/Tests/ttLib/tables/_b_s_l_n_test.py
+++ b/Tests/ttLib/tables/_b_s_l_n_test.py
@@ -7,17 +7,17 @@ import unittest
# Apple's spec of the baseline table gives no example for 'bsln' format 0,
# but the Apple Chancery font contains the following data.
BSLN_FORMAT_0_DATA = deHexStr(
- '0001 0000 0000 ' # 0: Version=1.0, Format=0
- '0000 ' # 6: DefaultBaseline=0 (Roman baseline)
- '0000 01D1 0000 0541 ' # 8: Delta[0..3]=0, 465, 0, 1345
- '01FB 0000 0000 0000 ' # 16: Delta[4..7]=507, 0, 0, 0
- '0000 0000 0000 0000 ' # 24: Delta[8..11]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 32: Delta[12..15]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 40: Delta[16..19]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 48: Delta[20..23]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 56: Delta[24..27]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 64: Delta[28..31]=0, 0, 0, 0
-) # 72: <end>
+ "0001 0000 0000 " # 0: Version=1.0, Format=0
+ "0000 " # 6: DefaultBaseline=0 (Roman baseline)
+ "0000 01D1 0000 0541 " # 8: Delta[0..3]=0, 465, 0, 1345
+ "01FB 0000 0000 0000 " # 16: Delta[4..7]=507, 0, 0, 0
+ "0000 0000 0000 0000 " # 24: Delta[8..11]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 32: Delta[12..15]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 40: Delta[16..19]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 48: Delta[20..23]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 56: Delta[24..27]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 64: Delta[28..31]=0, 0, 0, 0
+) # 72: <end>
assert len(BSLN_FORMAT_0_DATA) == 72
@@ -57,7 +57,7 @@ BSLN_FORMAT_0_XML = [
' <Delta index="29" value="0"/>',
' <Delta index="30" value="0"/>',
' <Delta index="31" value="0"/>',
- '</Baseline>',
+ "</Baseline>",
]
@@ -66,21 +66,21 @@ BSLN_FORMAT_0_XML = [
# The example in the AAT specification uses the value 270 for Seg[0].LastGlyph,
# whereas we use the value 10 for testng to shorten the XML dump.
BSLN_FORMAT_1_DATA = deHexStr(
- '0001 0000 0001 ' # 0: Version=1.0, Format=1
- '0001 ' # 6: DefaultBaseline=1 (Ideographic baseline)
- '0000 0357 0000 05F0 ' # 8: Delta[0..3]=0, 855, 0, 1520
- '0000 0000 0000 0000 ' # 16: Delta[4..7]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 24: Delta[8..11]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 32: Delta[12..15]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 40: Delta[16..19]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 48: Delta[20..23]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 56: Delta[24..27]=0, 0, 0, 0
- '0000 0000 0000 0000 ' # 64: Delta[28..31]=0, 0, 0, 0
- '0002 0006 0001 ' # 72: LookupFormat=2, UnitSize=6, NUnits=1
- '0006 0000 0000 ' # 78: SearchRange=6, EntrySelector=0, RangeShift=0
- '000A 0002 0000 ' # 84: Seg[0].LastGlyph=10 FirstGl=2 Value=0/Roman
- 'FFFF FFFF 0000 ' # 90: Seg[1]=<end>
-) # 96: <end>
+ "0001 0000 0001 " # 0: Version=1.0, Format=1
+ "0001 " # 6: DefaultBaseline=1 (Ideographic baseline)
+ "0000 0357 0000 05F0 " # 8: Delta[0..3]=0, 855, 0, 1520
+ "0000 0000 0000 0000 " # 16: Delta[4..7]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 24: Delta[8..11]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 32: Delta[12..15]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 40: Delta[16..19]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 48: Delta[20..23]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 56: Delta[24..27]=0, 0, 0, 0
+ "0000 0000 0000 0000 " # 64: Delta[28..31]=0, 0, 0, 0
+ "0002 0006 0001 " # 72: LookupFormat=2, UnitSize=6, NUnits=1
+ "0006 0000 0000 " # 78: SearchRange=6, EntrySelector=0, RangeShift=0
+ "000A 0002 0000 " # 84: Seg[0].LastGlyph=10 FirstGl=2 Value=0/Roman
+ "FFFF FFFF 0000 " # 90: Seg[1]=<end>
+) # 96: <end>
assert len(BSLN_FORMAT_1_DATA) == 96
@@ -120,7 +120,7 @@ BSLN_FORMAT_1_XML = [
' <Delta index="29" value="0"/>',
' <Delta index="30" value="0"/>',
' <Delta index="31" value="0"/>',
- ' <BaselineValues>',
+ " <BaselineValues>",
' <Lookup glyph="B" value="0"/>',
' <Lookup glyph="C" value="0"/>',
' <Lookup glyph="D" value="0"/>',
@@ -130,24 +130,24 @@ BSLN_FORMAT_1_XML = [
' <Lookup glyph="H" value="0"/>',
' <Lookup glyph="I" value="0"/>',
' <Lookup glyph="J" value="0"/>',
- ' </BaselineValues>',
- '</Baseline>',
+ " </BaselineValues>",
+ "</Baseline>",
]
BSLN_FORMAT_2_DATA = deHexStr(
- '0001 0000 0002 ' # 0: Version=1.0, Format=2
- '0004 ' # 6: DefaultBaseline=4 (Math)
- '0016 ' # 8: StandardGlyph=22
- '0050 0051 FFFF 0052 ' # 10: ControlPoint[0..3]=80, 81, <none>, 82
- 'FFFF FFFF FFFF FFFF ' # 18: ControlPoint[4..7]=<none>
- 'FFFF FFFF FFFF FFFF ' # 26: ControlPoint[8..11]=<none>
- 'FFFF FFFF FFFF FFFF ' # 34: ControlPoint[12..15]=<none>
- 'FFFF FFFF FFFF FFFF ' # 42: ControlPoint[16..19]=<none>
- 'FFFF FFFF FFFF FFFF ' # 50: ControlPoint[20..23]=<none>
- 'FFFF FFFF FFFF FFFF ' # 58: ControlPoint[24..27]=<none>
- 'FFFF FFFF FFFF FFFF ' # 66: ControlPoint[28..31]=<none>
-) # 74: <end>
+ "0001 0000 0002 " # 0: Version=1.0, Format=2
+ "0004 " # 6: DefaultBaseline=4 (Math)
+ "0016 " # 8: StandardGlyph=22
+ "0050 0051 FFFF 0052 " # 10: ControlPoint[0..3]=80, 81, <none>, 82
+ "FFFF FFFF FFFF FFFF " # 18: ControlPoint[4..7]=<none>
+ "FFFF FFFF FFFF FFFF " # 26: ControlPoint[8..11]=<none>
+ "FFFF FFFF FFFF FFFF " # 34: ControlPoint[12..15]=<none>
+ "FFFF FFFF FFFF FFFF " # 42: ControlPoint[16..19]=<none>
+ "FFFF FFFF FFFF FFFF " # 50: ControlPoint[20..23]=<none>
+ "FFFF FFFF FFFF FFFF " # 58: ControlPoint[24..27]=<none>
+ "FFFF FFFF FFFF FFFF " # 66: ControlPoint[28..31]=<none>
+) # 74: <end>
assert len(BSLN_FORMAT_2_DATA) == 74
@@ -188,7 +188,7 @@ BSLN_FORMAT_2_XML = [
' <ControlPoint index="29" value="65535"/>',
' <ControlPoint index="30" value="65535"/>',
' <ControlPoint index="31" value="65535"/>',
- '</Baseline>',
+ "</Baseline>",
]
@@ -197,22 +197,22 @@ BSLN_FORMAT_2_XML = [
# The example in the AAT specification uses the value 270 for Seg[0].LastGlyph,
# whereas we use the value 10 for testng to shorten the XML dump.
BSLN_FORMAT_3_DATA = deHexStr(
- '0001 0000 0003 ' # 0: Version=1.0, Format=3
- '0001 ' # 6: DefaultBaseline=1 (Ideographic)
- '0016 ' # 8: StandardGlyph=22
- '0050 0051 FFFF 0052 ' # 10: ControlPoint[0..3]=80, 81, <none>, 82
- 'FFFF FFFF FFFF FFFF ' # 18: ControlPoint[4..7]=<none>
- 'FFFF FFFF FFFF FFFF ' # 26: ControlPoint[8..11]=<none>
- 'FFFF FFFF FFFF FFFF ' # 34: ControlPoint[12..15]=<none>
- 'FFFF FFFF FFFF FFFF ' # 42: ControlPoint[16..19]=<none>
- 'FFFF FFFF FFFF FFFF ' # 50: ControlPoint[20..23]=<none>
- 'FFFF FFFF FFFF FFFF ' # 58: ControlPoint[24..27]=<none>
- 'FFFF FFFF FFFF FFFF ' # 66: ControlPoint[28..31]=<none>
- '0002 0006 0001 ' # 74: LookupFormat=2, UnitSize=6, NUnits=1
- '0006 0000 0000 ' # 80: SearchRange=6, EntrySelector=0, RangeShift=0
- '000A 0002 0000 ' # 86: Seg[0].LastGlyph=10 FirstGl=2 Value=0/Roman
- 'FFFF FFFF 0000 ' # 92: Seg[1]=<end>
-) # 98: <end>
+ "0001 0000 0003 " # 0: Version=1.0, Format=3
+ "0001 " # 6: DefaultBaseline=1 (Ideographic)
+ "0016 " # 8: StandardGlyph=22
+ "0050 0051 FFFF 0052 " # 10: ControlPoint[0..3]=80, 81, <none>, 82
+ "FFFF FFFF FFFF FFFF " # 18: ControlPoint[4..7]=<none>
+ "FFFF FFFF FFFF FFFF " # 26: ControlPoint[8..11]=<none>
+ "FFFF FFFF FFFF FFFF " # 34: ControlPoint[12..15]=<none>
+ "FFFF FFFF FFFF FFFF " # 42: ControlPoint[16..19]=<none>
+ "FFFF FFFF FFFF FFFF " # 50: ControlPoint[20..23]=<none>
+ "FFFF FFFF FFFF FFFF " # 58: ControlPoint[24..27]=<none>
+ "FFFF FFFF FFFF FFFF " # 66: ControlPoint[28..31]=<none>
+ "0002 0006 0001 " # 74: LookupFormat=2, UnitSize=6, NUnits=1
+ "0006 0000 0000 " # 80: SearchRange=6, EntrySelector=0, RangeShift=0
+ "000A 0002 0000 " # 86: Seg[0].LastGlyph=10 FirstGl=2 Value=0/Roman
+ "FFFF FFFF 0000 " # 92: Seg[1]=<end>
+) # 98: <end>
assert len(BSLN_FORMAT_3_DATA) == 98
@@ -253,7 +253,7 @@ BSLN_FORMAT_3_XML = [
' <ControlPoint index="29" value="65535"/>',
' <ControlPoint index="30" value="65535"/>',
' <ControlPoint index="31" value="65535"/>',
- ' <BaselineValues>',
+ " <BaselineValues>",
' <Lookup glyph="B" value="0"/>',
' <Lookup glyph="C" value="0"/>',
' <Lookup glyph="D" value="0"/>',
@@ -263,26 +263,24 @@ BSLN_FORMAT_3_XML = [
' <Lookup glyph="H" value="0"/>',
' <Lookup glyph="I" value="0"/>',
' <Lookup glyph="J" value="0"/>',
- ' </BaselineValues>',
- '</Baseline>',
+ " </BaselineValues>",
+ "</Baseline>",
]
class BSLNTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(
- ['.notdef'] + [g for g in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'])
+ cls.font = FakeFont([".notdef"] + [g for g in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"])
def decompileToXML(self, data, xml):
- table = newTable('bsln')
+ table = newTable("bsln")
table.decompile(data, self.font)
self.assertEqual(getXML(table.toXML), xml)
def compileFromXML(self, xml, data):
- table = newTable('bsln')
+ table = newTable("bsln")
for name, attrs, content in parseXML(xml):
table.fromXML(name, attrs, content, font=self.font)
self.assertEqual(hexStr(table.compile(self.font)), hexStr(data))
@@ -304,6 +302,7 @@ class BSLNTest(unittest.TestCase):
self.compileFromXML(BSLN_FORMAT_3_XML, BSLN_FORMAT_3_DATA)
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_c_i_d_g_test.py b/Tests/ttLib/tables/_c_i_d_g_test.py
index 11c1fc0f..315736b8 100644
--- a/Tests/ttLib/tables/_c_i_d_g_test.py
+++ b/Tests/ttLib/tables/_c_i_d_g_test.py
@@ -7,39 +7,39 @@ import unittest
# On macOS X 10.12.6, the first font in /System/Library/Fonts/PingFang.ttc
# has a ‘cidg’ table with a similar structure as this test data, just larger.
CIDG_DATA = deHexStr(
- "0000 0000 " # 0: Format=0, Flags=0
- "0000 0098 " # 4: StructLength=152
- "0000 " # 8: Registry=0
- "41 64 6F 62 65 " # 10: RegistryName="Adobe"
- + ("00" * 59) + # 15: <padding>
- "0002 " # 74: Order=2
- "43 4E 53 31 " # 76: Order="CNS1"
- + ("00" * 60) + # 80: <padding>
- "0000 " # 140: SupplementVersion=0
- "0004 " # 142: Count
- "0000 " # 144: GlyphID[0]=.notdef
- "FFFF " # 146: CIDs[1]=<None>
- "0003 " # 148: CIDs[2]=C
- "0001 " # 150: CIDs[3]=A
-) # 152: <end>
+ "0000 0000 " # 0: Format=0, Flags=0
+ "0000 0098 " # 4: StructLength=152
+ "0000 " # 8: Registry=0
+ "41 64 6F 62 65 " # 10: RegistryName="Adobe"
+ + ("00" * 59)
+ + "0002 " # 15: <padding> # 74: Order=2
+ "43 4E 53 31 " # 76: Order="CNS1"
+ + ("00" * 60)
+ + "0000 " # 80: <padding> # 140: SupplementVersion=0
+ "0004 " # 142: Count
+ "0000 " # 144: GlyphID[0]=.notdef
+ "FFFF " # 146: CIDs[1]=<None>
+ "0003 " # 148: CIDs[2]=C
+ "0001 " # 150: CIDs[3]=A
+) # 152: <end>
assert len(CIDG_DATA) == 152, len(CIDG_DATA)
CIDG_XML = [
- '<CIDGlyphMapping Format="0">',
- ' <DataFormat value="0"/>',
- ' <!-- StructLength=152 -->',
- ' <Registry value="0"/>',
- ' <RegistryName value="Adobe"/>',
- ' <Order value="2"/>',
- ' <OrderName value="CNS1"/>',
- ' <SupplementVersion value="0"/>',
- ' <Mapping>',
- ' <CID cid="0" glyph=".notdef"/>',
- ' <CID cid="2" glyph="C"/>',
- ' <CID cid="3" glyph="A"/>',
- ' </Mapping>',
- '</CIDGlyphMapping>',
+ '<CIDGlyphMapping Format="0">',
+ ' <DataFormat value="0"/>',
+ " <!-- StructLength=152 -->",
+ ' <Registry value="0"/>',
+ ' <RegistryName value="Adobe"/>',
+ ' <Order value="2"/>',
+ ' <OrderName value="CNS1"/>',
+ ' <SupplementVersion value="0"/>',
+ " <Mapping>",
+ ' <CID cid="0" glyph=".notdef"/>',
+ ' <CID cid="2" glyph="C"/>',
+ ' <CID cid="3" glyph="A"/>',
+ " </Mapping>",
+ "</CIDGlyphMapping>",
]
@@ -47,21 +47,21 @@ class GCIDTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.notdef', 'A', 'B', 'C', 'D'])
+ cls.font = FakeFont([".notdef", "A", "B", "C", "D"])
def testDecompileToXML(self):
- table = newTable('cidg')
+ table = newTable("cidg")
table.decompile(CIDG_DATA, self.font)
self.assertEqual(getXML(table.toXML, self.font), CIDG_XML)
def testCompileFromXML(self):
- table = newTable('cidg')
+ table = newTable("cidg")
for name, attrs, content in parseXML(CIDG_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(CIDG_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(CIDG_DATA))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_c_m_a_p_test.py b/Tests/ttLib/tables/_c_m_a_p_test.py
index 63285045..9bf854e1 100644
--- a/Tests/ttLib/tables/_c_m_a_p_test.py
+++ b/Tests/ttLib/tables/_c_m_a_p_test.py
@@ -7,166 +7,182 @@ import unittest
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable, table__c_m_a_p
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
CMAP_FORMAT_14_TTX = os.path.join(DATA_DIR, "_c_m_a_p_format_14.ttx")
-CMAP_FORMAT_14_BW_COMPAT_TTX = os.path.join(DATA_DIR, "_c_m_a_p_format_14_bw_compat.ttx")
+CMAP_FORMAT_14_BW_COMPAT_TTX = os.path.join(
+ DATA_DIR, "_c_m_a_p_format_14_bw_compat.ttx"
+)
+
def strip_VariableItems(string):
# ttlib changes with the fontTools version
- string = re.sub(' ttLibVersion=".*"', '', string)
+ string = re.sub(' ttLibVersion=".*"', "", string)
return string
-class CmapSubtableTest(unittest.TestCase):
- def makeSubtable(self, cmapFormat, platformID, platEncID, langID):
- subtable = CmapSubtable.newSubtable(cmapFormat)
- subtable.platformID, subtable.platEncID, subtable.language = (platformID, platEncID, langID)
- return subtable
-
- def test_toUnicode_utf16be(self):
- subtable = self.makeSubtable(4, 0, 2, 7)
- self.assertEqual("utf_16_be", subtable.getEncoding())
- self.assertEqual(True, subtable.isUnicode())
-
- def test_toUnicode_macroman(self):
- subtable = self.makeSubtable(4, 1, 0, 7) # MacRoman
- self.assertEqual("mac_roman", subtable.getEncoding())
- self.assertEqual(False, subtable.isUnicode())
-
- def test_toUnicode_macromanian(self):
- subtable = self.makeSubtable(4, 1, 0, 37) # Mac Romanian
- self.assertNotEqual(None, subtable.getEncoding())
- self.assertEqual(False, subtable.isUnicode())
-
- def test_extended_mac_encodings(self):
- subtable = self.makeSubtable(4, 1, 1, 0) # Mac Japanese
- self.assertNotEqual(None, subtable.getEncoding())
- self.assertEqual(False, subtable.isUnicode())
-
- def test_extended_unknown(self):
- subtable = self.makeSubtable(4, 10, 11, 12)
- self.assertEqual(subtable.getEncoding(), None)
- self.assertEqual(subtable.getEncoding("ascii"), "ascii")
- self.assertEqual(subtable.getEncoding(default="xyz"), "xyz")
-
- def test_compile_2(self):
- subtable = self.makeSubtable(2, 1, 2, 0)
- subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)}
- font = ttLib.TTFont()
- font.setGlyphOrder([".notdef"] + list(subtable.cmap.values()))
- data = subtable.compile(font)
-
- subtable2 = CmapSubtable.newSubtable(2)
- subtable2.decompile(data, font)
- self.assertEqual(subtable2.cmap, subtable.cmap)
-
- def test_compile_2_rebuild_rev_glyph_order(self):
- for fmt in [2, 4, 12]:
- subtable = self.makeSubtable(fmt, 1, 2, 0)
- subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)}
- font = ttLib.TTFont()
- font.setGlyphOrder([".notdef"] + list(subtable.cmap.values()))
- font._reverseGlyphOrderDict = {} # force first KeyError branch in subtable.compile()
- data = subtable.compile(font)
- subtable2 = CmapSubtable.newSubtable(fmt)
- subtable2.decompile(data, font)
- self.assertEqual(subtable2.cmap, subtable.cmap, str(fmt))
-
- def test_compile_2_gids(self):
- for fmt in [2, 4, 12]:
- subtable = self.makeSubtable(fmt, 1, 3, 0)
- subtable.cmap = {0x0041:'gid001', 0x0042:'gid002'}
- font = ttLib.TTFont()
- font.setGlyphOrder([".notdef"])
- data = subtable.compile(font)
-
- def test_compile_decompile_4_empty(self):
- subtable = self.makeSubtable(4, 3, 1, 0)
- subtable.cmap = {}
- font = ttLib.TTFont()
- font.setGlyphOrder([])
- data = subtable.compile(font)
- subtable2 = CmapSubtable.newSubtable(4)
- subtable2.decompile(data, font)
- self.assertEqual(subtable2.cmap, {})
-
- def test_decompile_4(self):
- subtable = CmapSubtable.newSubtable(4)
- font = ttLib.TTFont()
- font.setGlyphOrder([])
- subtable.decompile(b'\0' * 3 + b'\x10' + b'\0' * 12, font)
-
- def test_decompile_12(self):
- subtable = CmapSubtable.newSubtable(12)
- font = ttLib.TTFont()
- font.setGlyphOrder([])
- subtable.decompile(b'\0' * 7 + b'\x10' + b'\0' * 8, font)
-
- def test_buildReversed(self):
- c4 = self.makeSubtable(4, 3, 1, 0)
- c4.cmap = {0x0041:'A', 0x0391:'A'}
- c12 = self.makeSubtable(12, 3, 10, 0)
- c12.cmap = {0x10314: 'u10314'}
- cmap = table__c_m_a_p()
- cmap.tables = [c4, c12]
- self.assertEqual(cmap.buildReversed(), {'A':{0x0041, 0x0391}, 'u10314':{0x10314}})
-
- def test_getBestCmap(self):
- c4 = self.makeSubtable(4, 3, 1, 0)
- c4.cmap = {0x0041:'A', 0x0391:'A'}
- c12 = self.makeSubtable(12, 3, 10, 0)
- c12.cmap = {0x10314: 'u10314'}
- cmap = table__c_m_a_p()
- cmap.tables = [c4, c12]
- self.assertEqual(cmap.getBestCmap(), {0x10314: 'u10314'})
- self.assertEqual(cmap.getBestCmap(cmapPreferences=[(3, 1)]), {0x0041:'A', 0x0391:'A'})
- self.assertEqual(cmap.getBestCmap(cmapPreferences=[(0, 4)]), None)
-
- def test_font_getBestCmap(self):
- c4 = self.makeSubtable(4, 3, 1, 0)
- c4.cmap = {0x0041:'A', 0x0391:'A'}
- c12 = self.makeSubtable(12, 3, 10, 0)
- c12.cmap = {0x10314: 'u10314'}
- cmap = table__c_m_a_p()
- cmap.tables = [c4, c12]
- font = ttLib.TTFont()
- font["cmap"] = cmap
- self.assertEqual(font.getBestCmap(), {0x10314: 'u10314'})
- self.assertEqual(font.getBestCmap(cmapPreferences=[(3, 1)]), {0x0041:'A', 0x0391:'A'})
- self.assertEqual(font.getBestCmap(cmapPreferences=[(0, 4)]), None)
-
- def test_format_14(self):
- subtable = self.makeSubtable(14, 0, 5, 0)
- subtable.cmap = {} # dummy
- subtable.uvsDict = {
- 0xFE00: [(0x0030, "zero.slash")],
- 0xFE01: [(0x0030, None)],
- }
- fb = FontBuilder(1024, isTTF=True)
- font = fb.font
- fb.setupGlyphOrder([".notdef", "zero.slash"])
- fb.setupMaxp()
- fb.setupPost()
- cmap = table__c_m_a_p()
- cmap.tableVersion = 0
- cmap.tables = [subtable]
- font["cmap"] = cmap
- f = io.BytesIO()
- font.save(f)
- f.seek(0)
- font = ttLib.TTFont(f)
- self.assertEqual(font["cmap"].getcmap(0, 5).uvsDict, subtable.uvsDict)
- f = io.StringIO(newline=None)
- font.saveXML(f, tables=["cmap"])
- ttx = strip_VariableItems(f.getvalue())
- with open(CMAP_FORMAT_14_TTX) as f:
- expected = strip_VariableItems(f.read())
- self.assertEqual(ttx, expected)
- with open(CMAP_FORMAT_14_BW_COMPAT_TTX) as f:
- font.importXML(f)
- self.assertEqual(font["cmap"].getcmap(0, 5).uvsDict, subtable.uvsDict)
+class CmapSubtableTest(unittest.TestCase):
+ def makeSubtable(self, cmapFormat, platformID, platEncID, langID):
+ subtable = CmapSubtable.newSubtable(cmapFormat)
+ subtable.platformID, subtable.platEncID, subtable.language = (
+ platformID,
+ platEncID,
+ langID,
+ )
+ return subtable
+
+ def test_toUnicode_utf16be(self):
+ subtable = self.makeSubtable(4, 0, 2, 7)
+ self.assertEqual("utf_16_be", subtable.getEncoding())
+ self.assertEqual(True, subtable.isUnicode())
+
+ def test_toUnicode_macroman(self):
+ subtable = self.makeSubtable(4, 1, 0, 7) # MacRoman
+ self.assertEqual("mac_roman", subtable.getEncoding())
+ self.assertEqual(False, subtable.isUnicode())
+
+ def test_toUnicode_macromanian(self):
+ subtable = self.makeSubtable(4, 1, 0, 37) # Mac Romanian
+ self.assertNotEqual(None, subtable.getEncoding())
+ self.assertEqual(False, subtable.isUnicode())
+
+ def test_extended_mac_encodings(self):
+ subtable = self.makeSubtable(4, 1, 1, 0) # Mac Japanese
+ self.assertNotEqual(None, subtable.getEncoding())
+ self.assertEqual(False, subtable.isUnicode())
+
+ def test_extended_unknown(self):
+ subtable = self.makeSubtable(4, 10, 11, 12)
+ self.assertEqual(subtable.getEncoding(), None)
+ self.assertEqual(subtable.getEncoding("ascii"), "ascii")
+ self.assertEqual(subtable.getEncoding(default="xyz"), "xyz")
+
+ def test_compile_2(self):
+ subtable = self.makeSubtable(2, 1, 2, 0)
+ subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)}
+ font = ttLib.TTFont()
+ font.setGlyphOrder([".notdef"] + list(subtable.cmap.values()))
+ data = subtable.compile(font)
+
+ subtable2 = CmapSubtable.newSubtable(2)
+ subtable2.decompile(data, font)
+ self.assertEqual(subtable2.cmap, subtable.cmap)
+
+ def test_compile_2_rebuild_rev_glyph_order(self):
+ for fmt in [2, 4, 12]:
+ subtable = self.makeSubtable(fmt, 1, 2, 0)
+ subtable.cmap = {c: "cid%05d" % c for c in range(32, 8192)}
+ font = ttLib.TTFont()
+ font.setGlyphOrder([".notdef"] + list(subtable.cmap.values()))
+ font._reverseGlyphOrderDict = (
+ {}
+ ) # force first KeyError branch in subtable.compile()
+ data = subtable.compile(font)
+ subtable2 = CmapSubtable.newSubtable(fmt)
+ subtable2.decompile(data, font)
+ self.assertEqual(subtable2.cmap, subtable.cmap, str(fmt))
+
+ def test_compile_2_gids(self):
+ for fmt in [2, 4, 12]:
+ subtable = self.makeSubtable(fmt, 1, 3, 0)
+ subtable.cmap = {0x0041: "gid001", 0x0042: "gid002"}
+ font = ttLib.TTFont()
+ font.setGlyphOrder([".notdef"])
+ data = subtable.compile(font)
+
+ def test_compile_decompile_4_empty(self):
+ subtable = self.makeSubtable(4, 3, 1, 0)
+ subtable.cmap = {}
+ font = ttLib.TTFont()
+ font.setGlyphOrder([])
+ data = subtable.compile(font)
+ subtable2 = CmapSubtable.newSubtable(4)
+ subtable2.decompile(data, font)
+ self.assertEqual(subtable2.cmap, {})
+
+ def test_decompile_4(self):
+ subtable = CmapSubtable.newSubtable(4)
+ font = ttLib.TTFont()
+ font.setGlyphOrder([])
+ subtable.decompile(b"\0" * 3 + b"\x10" + b"\0" * 12, font)
+
+ def test_decompile_12(self):
+ subtable = CmapSubtable.newSubtable(12)
+ font = ttLib.TTFont()
+ font.setGlyphOrder([])
+ subtable.decompile(b"\0" * 7 + b"\x10" + b"\0" * 8, font)
+
+ def test_buildReversed(self):
+ c4 = self.makeSubtable(4, 3, 1, 0)
+ c4.cmap = {0x0041: "A", 0x0391: "A"}
+ c12 = self.makeSubtable(12, 3, 10, 0)
+ c12.cmap = {0x10314: "u10314"}
+ cmap = table__c_m_a_p()
+ cmap.tables = [c4, c12]
+ self.assertEqual(
+ cmap.buildReversed(), {"A": {0x0041, 0x0391}, "u10314": {0x10314}}
+ )
+
+ def test_getBestCmap(self):
+ c4 = self.makeSubtable(4, 3, 1, 0)
+ c4.cmap = {0x0041: "A", 0x0391: "A"}
+ c12 = self.makeSubtable(12, 3, 10, 0)
+ c12.cmap = {0x10314: "u10314"}
+ cmap = table__c_m_a_p()
+ cmap.tables = [c4, c12]
+ self.assertEqual(cmap.getBestCmap(), {0x10314: "u10314"})
+ self.assertEqual(
+ cmap.getBestCmap(cmapPreferences=[(3, 1)]), {0x0041: "A", 0x0391: "A"}
+ )
+ self.assertEqual(cmap.getBestCmap(cmapPreferences=[(0, 4)]), None)
+
+ def test_font_getBestCmap(self):
+ c4 = self.makeSubtable(4, 3, 1, 0)
+ c4.cmap = {0x0041: "A", 0x0391: "A"}
+ c12 = self.makeSubtable(12, 3, 10, 0)
+ c12.cmap = {0x10314: "u10314"}
+ cmap = table__c_m_a_p()
+ cmap.tables = [c4, c12]
+ font = ttLib.TTFont()
+ font["cmap"] = cmap
+ self.assertEqual(font.getBestCmap(), {0x10314: "u10314"})
+ self.assertEqual(
+ font.getBestCmap(cmapPreferences=[(3, 1)]), {0x0041: "A", 0x0391: "A"}
+ )
+ self.assertEqual(font.getBestCmap(cmapPreferences=[(0, 4)]), None)
+
+ def test_format_14(self):
+ subtable = self.makeSubtable(14, 0, 5, 0)
+ subtable.cmap = {} # dummy
+ subtable.uvsDict = {
+ 0xFE00: [(0x0030, "zero.slash")],
+ 0xFE01: [(0x0030, None)],
+ }
+ fb = FontBuilder(1024, isTTF=True)
+ font = fb.font
+ fb.setupGlyphOrder([".notdef", "zero.slash"])
+ fb.setupMaxp()
+ fb.setupPost()
+ cmap = table__c_m_a_p()
+ cmap.tableVersion = 0
+ cmap.tables = [subtable]
+ font["cmap"] = cmap
+ f = io.BytesIO()
+ font.save(f)
+ f.seek(0)
+ font = ttLib.TTFont(f)
+ self.assertEqual(font["cmap"].getcmap(0, 5).uvsDict, subtable.uvsDict)
+ f = io.StringIO(newline=None)
+ font.saveXML(f, tables=["cmap"])
+ ttx = strip_VariableItems(f.getvalue())
+ with open(CMAP_FORMAT_14_TTX) as f:
+ expected = strip_VariableItems(f.read())
+ self.assertEqual(ttx, expected)
+ with open(CMAP_FORMAT_14_BW_COMPAT_TTX) as f:
+ font.importXML(f)
+ self.assertEqual(font["cmap"].getcmap(0, 5).uvsDict, subtable.uvsDict)
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_c_v_a_r_test.py b/Tests/ttLib/tables/_c_v_a_r_test.py
index 31c19538..c6fe0113 100644
--- a/Tests/ttLib/tables/_c_v_a_r_test.py
+++ b/Tests/ttLib/tables/_c_v_a_r_test.py
@@ -7,58 +7,62 @@ import unittest
CVAR_DATA = deHexStr(
- "0001 0000 " # 0: majorVersion=1 minorVersion=0
- "8002 0018 " # 4: tupleVariationCount=2|TUPLES_SHARE_POINT_NUMBERS offsetToData=24
- "0004 " # 8: tvHeader[0].variationDataSize=4
- "8000 " # 10: tvHeader[0].tupleIndex=EMBEDDED_PEAK
- "4000 0000 " # 12: tvHeader[0].peakTuple=[1.0, 0.0]
- "0004 " # 16: tvHeader[1].variationDataSize=4
- "8000 " # 18: tvHeader[1].tupleIndex=EMBEDDED_PEAK
- "C000 3333 " # 20: tvHeader[1].peakTuple=[-1.0, 0.8]
+ "0001 0000 " # 0: majorVersion=1 minorVersion=0
+ "8002 0018 " # 4: tupleVariationCount=2|TUPLES_SHARE_POINT_NUMBERS offsetToData=24
+ "0004 " # 8: tvHeader[0].variationDataSize=4
+ "8000 " # 10: tvHeader[0].tupleIndex=EMBEDDED_PEAK
+ "4000 0000 " # 12: tvHeader[0].peakTuple=[1.0, 0.0]
+ "0004 " # 16: tvHeader[1].variationDataSize=4
+ "8000 " # 18: tvHeader[1].tupleIndex=EMBEDDED_PEAK
+ "C000 3333 " # 20: tvHeader[1].peakTuple=[-1.0, 0.8]
"03 02 02 01 01" # 24: shared_pointCount=03, run_count=2 cvt=[2, 3, 4]
- "02 03 01 04 " # 25: deltas=[3, 1, 4]
- "02 09 07 08") # 29: deltas=[9, 7, 8]
+ "02 03 01 04 " # 25: deltas=[3, 1, 4]
+ "02 09 07 08"
+) # 29: deltas=[9, 7, 8]
CVAR_PRIVATE_POINT_DATA = deHexStr(
- "0001 0000 " # 0: majorVersion=1 minorVersion=0
- "0002 0018 " # 4: tupleVariationCount=2 offsetToData=24
- "0009 " # 8: tvHeader[0].variationDataSize=9
- "A000 " # 10: tvHeader[0].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINT_NUMBERS
- "4000 0000 " # 12: tvHeader[0].peakTuple=[1.0, 0.0]
- "0009 " # 16: tvHeader[1].variationDataSize=9
- "A000 " # 18: tvHeader[1].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINT_NUMBERS
- "C000 3333 " # 20: tvHeader[1].peakTuple=[-1.0, 0.8]
- "03 02 02 01 01 02 03 01 04 " # 24: pointCount=3 run_count=2 cvt=2 1 1 run_count=2 deltas=[3, 1, 4]
- "03 02 02 01 01 02 09 07 08 ") # 33: pointCount=3 run_count=2 cvt=2 1 1 run_count=2 deltas=[9, 7, 8]
+ "0001 0000 " # 0: majorVersion=1 minorVersion=0
+ "0002 0018 " # 4: tupleVariationCount=2 offsetToData=24
+ "0009 " # 8: tvHeader[0].variationDataSize=9
+ "A000 " # 10: tvHeader[0].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINT_NUMBERS
+ "4000 0000 " # 12: tvHeader[0].peakTuple=[1.0, 0.0]
+ "0009 " # 16: tvHeader[1].variationDataSize=9
+ "A000 " # 18: tvHeader[1].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINT_NUMBERS
+ "C000 3333 " # 20: tvHeader[1].peakTuple=[-1.0, 0.8]
+ "03 02 02 01 01 02 03 01 04 " # 24: pointCount=3 run_count=2 cvt=2 1 1 run_count=2 deltas=[3, 1, 4]
+ "03 02 02 01 01 02 09 07 08 "
+) # 33: pointCount=3 run_count=2 cvt=2 1 1 run_count=2 deltas=[9, 7, 8]
CVAR_XML = [
'<version major="1" minor="0"/>',
- '<tuple>',
+ "<tuple>",
' <coord axis="wght" value="1.0"/>',
' <delta cvt="2" value="3"/>',
' <delta cvt="3" value="1"/>',
' <delta cvt="4" value="4"/>',
- '</tuple>',
- '<tuple>',
+ "</tuple>",
+ "<tuple>",
' <coord axis="wght" value="-1.0"/>',
' <coord axis="wdth" value="0.8"/>',
' <delta cvt="2" value="9"/>',
' <delta cvt="3" value="7"/>',
' <delta cvt="4" value="8"/>',
- '</tuple>',
+ "</tuple>",
]
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.7999878, 0.7999878)},
- [None, None, 9, 7, 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):
+ 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]
@@ -84,7 +88,9 @@ class CVARTableTest(unittest.TestCase):
def test_compile_shared_points(self):
font, cvar = self.makeFont()
cvar.variations = CVAR_VARIATIONS
- self.assertEqual(hexStr(cvar.compile(font, useSharedPoints=True)), hexStr(CVAR_DATA))
+ self.assertEqual(
+ hexStr(cvar.compile(font, useSharedPoints=True)), hexStr(CVAR_DATA)
+ )
def test_decompile(self):
font, cvar = self.makeFont()
@@ -116,4 +122,5 @@ class CVARTableTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_f_v_a_r_test.py b/Tests/ttLib/tables/_f_v_a_r_test.py
index 2ac5237f..353df9f3 100644
--- a/Tests/ttLib/tables/_f_v_a_r_test.py
+++ b/Tests/ttLib/tables/_f_v_a_r_test.py
@@ -8,22 +8,19 @@ from io import BytesIO
import unittest
-
FVAR_DATA = deHexStr(
"00 01 00 00 00 10 00 02 00 02 00 14 00 02 00 0C "
"77 67 68 74 00 64 00 00 01 90 00 00 03 84 00 00 00 00 01 01 "
"77 64 74 68 00 32 00 00 00 64 00 00 00 c8 00 00 00 00 01 02 "
"01 03 00 00 01 2c 00 00 00 64 00 00 "
- "01 04 00 00 01 2c 00 00 00 4b 00 00")
+ "01 04 00 00 01 2c 00 00 00 4b 00 00"
+)
-FVAR_AXIS_DATA = deHexStr(
- "6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59")
+FVAR_AXIS_DATA = deHexStr("6F 70 73 7a ff ff 80 00 00 01 4c cd 00 01 80 00 00 00 01 59")
-FVAR_INSTANCE_DATA_WITHOUT_PSNAME = deHexStr(
- "01 59 00 00 00 00 b3 33 00 00 80 00")
+FVAR_INSTANCE_DATA_WITHOUT_PSNAME = deHexStr("01 59 00 00 00 00 b3 33 00 00 80 00")
-FVAR_INSTANCE_DATA_WITH_PSNAME = (
- FVAR_INSTANCE_DATA_WITHOUT_PSNAME + deHexStr("02 34"))
+FVAR_INSTANCE_DATA_WITH_PSNAME = FVAR_INSTANCE_DATA_WITHOUT_PSNAME + deHexStr("02 34")
def xml_lines(writer):
@@ -38,7 +35,7 @@ def AddName(font, name):
nameTable.names = []
namerec = NameRecord()
namerec.nameID = 1 + max([n.nameID for n in nameTable.names] + [256])
- namerec.string = name.encode('mac_roman')
+ namerec.string = name.encode("mac_roman")
namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
nameTable.names.append(namerec)
return namerec
@@ -91,15 +88,16 @@ class FontVariationTableTest(unittest.TestCase):
def test_fromXML(self):
fvar = table__f_v_a_r()
for name, attrs, content in parseXML(
- '<Axis>'
- ' <AxisTag>opsz</AxisTag>'
- '</Axis>'
- '<Axis>'
- ' <AxisTag>slnt</AxisTag>'
- ' <Flags>0x123</Flags>'
- '</Axis>'
- '<NamedInstance subfamilyNameID="765"/>'
- '<NamedInstance subfamilyNameID="234"/>'):
+ "<Axis>"
+ " <AxisTag>opsz</AxisTag>"
+ "</Axis>"
+ "<Axis>"
+ " <AxisTag>slnt</AxisTag>"
+ " <Flags>0x123</Flags>"
+ "</Axis>"
+ '<NamedInstance subfamilyNameID="765"/>'
+ '<NamedInstance subfamilyNameID="234"/>'
+ ):
fvar.fromXML(name, attrs, content, ttFont=None)
self.assertEqual(["opsz", "slnt"], [a.axisTag for a in fvar.axes])
self.assertEqual([0, 0x123], [a.flags for a in fvar.axes])
@@ -109,7 +107,7 @@ class FontVariationTableTest(unittest.TestCase):
class AxisTest(unittest.TestCase):
def test_compile(self):
axis = Axis()
- axis.axisTag, axis.axisNameID = ('opsz', 345)
+ axis.axisTag, axis.axisNameID = ("opsz", 345)
axis.minValue, axis.defaultValue, axis.maxValue = (-0.5, 1.3, 1.5)
self.assertEqual(FVAR_AXIS_DATA, axis.compile())
@@ -131,30 +129,34 @@ class AxisTest(unittest.TestCase):
axis.flags = 0xABC
writer = XMLWriter(BytesIO())
axis.toXML(writer, font)
- self.assertEqual([
- '',
- '<!-- Optical Size -->',
- '<Axis>',
- '<AxisTag>opsz</AxisTag>',
- '<Flags>0xABC</Flags>',
- '<MinValue>-0.5</MinValue>',
- '<DefaultValue>1.3</DefaultValue>',
- '<MaxValue>1.5</MaxValue>',
- '<AxisNameID>256</AxisNameID>',
- '</Axis>'
- ], xml_lines(writer))
+ self.assertEqual(
+ [
+ "",
+ "<!-- Optical Size -->",
+ "<Axis>",
+ "<AxisTag>opsz</AxisTag>",
+ "<Flags>0xABC</Flags>",
+ "<MinValue>-0.5</MinValue>",
+ "<DefaultValue>1.3</DefaultValue>",
+ "<MaxValue>1.5</MaxValue>",
+ "<AxisNameID>256</AxisNameID>",
+ "</Axis>",
+ ],
+ xml_lines(writer),
+ )
def test_fromXML(self):
axis = Axis()
for name, attrs, content in parseXML(
- '<Axis>'
- ' <AxisTag>wght</AxisTag>'
- ' <Flags>0x123ABC</Flags>'
- ' <MinValue>100</MinValue>'
- ' <DefaultValue>400</DefaultValue>'
- ' <MaxValue>900</MaxValue>'
- ' <AxisNameID>256</AxisNameID>'
- '</Axis>'):
+ "<Axis>"
+ " <AxisTag>wght</AxisTag>"
+ " <Flags>0x123ABC</Flags>"
+ " <MinValue>100</MinValue>"
+ " <DefaultValue>400</DefaultValue>"
+ " <MaxValue>900</MaxValue>"
+ " <AxisNameID>256</AxisNameID>"
+ "</Axis>"
+ ):
axis.fromXML(name, attrs, content, ttFont=None)
self.assertEqual("wght", axis.axisTag)
self.assertEqual(0x123ABC, axis.flags)
@@ -175,16 +177,18 @@ class NamedInstanceTest(unittest.TestCase):
inst.subfamilyNameID = 345
inst.postscriptNameID = 564
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
- self.assertEqual(FVAR_INSTANCE_DATA_WITH_PSNAME,
- inst.compile(["wght", "wdth"], True))
+ self.assertEqual(
+ FVAR_INSTANCE_DATA_WITH_PSNAME, inst.compile(["wght", "wdth"], True)
+ )
def test_compile_withoutPostScriptName(self):
inst = NamedInstance()
inst.subfamilyNameID = 345
inst.postscriptNameID = 564
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
- self.assertEqual(FVAR_INSTANCE_DATA_WITHOUT_PSNAME,
- inst.compile(["wght", "wdth"], False))
+ self.assertEqual(
+ FVAR_INSTANCE_DATA_WITHOUT_PSNAME, inst.compile(["wght", "wdth"], False)
+ )
def test_decompile_withPostScriptName(self):
inst = NamedInstance()
@@ -209,16 +213,19 @@ class NamedInstanceTest(unittest.TestCase):
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
writer = XMLWriter(BytesIO())
inst.toXML(writer, font)
- self.assertEqual([
- '',
- '<!-- Light Condensed -->',
- '<!-- PostScript: Test-LightCondensed -->',
- '<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">' % (
- inst.postscriptNameID, inst.subfamilyNameID),
- '<coord axis="wght" value="0.7"/>',
- '<coord axis="wdth" value="0.5"/>',
- '</NamedInstance>'
- ], xml_lines(writer))
+ self.assertEqual(
+ [
+ "",
+ "<!-- Light Condensed -->",
+ "<!-- PostScript: Test-LightCondensed -->",
+ '<NamedInstance flags="0xE9" postscriptNameID="%s" subfamilyNameID="%s">'
+ % (inst.postscriptNameID, inst.subfamilyNameID),
+ '<coord axis="wght" value="0.7"/>',
+ '<coord axis="wdth" value="0.5"/>',
+ "</NamedInstance>",
+ ],
+ xml_lines(writer),
+ )
def test_toXML_withoutPostScriptName(self):
font = MakeFont()
@@ -228,23 +235,27 @@ class NamedInstanceTest(unittest.TestCase):
inst.coordinates = {"wght": 0.7, "wdth": 0.5}
writer = XMLWriter(BytesIO())
inst.toXML(writer, font)
- self.assertEqual([
- '',
- '<!-- Light Condensed -->',
- '<NamedInstance flags="0xABC" subfamilyNameID="%s">' %
- inst.subfamilyNameID,
- '<coord axis="wght" value="0.7"/>',
- '<coord axis="wdth" value="0.5"/>',
- '</NamedInstance>'
- ], xml_lines(writer))
+ self.assertEqual(
+ [
+ "",
+ "<!-- Light Condensed -->",
+ '<NamedInstance flags="0xABC" subfamilyNameID="%s">'
+ % inst.subfamilyNameID,
+ '<coord axis="wght" value="0.7"/>',
+ '<coord axis="wdth" value="0.5"/>',
+ "</NamedInstance>",
+ ],
+ xml_lines(writer),
+ )
def test_fromXML_withPostScriptName(self):
inst = NamedInstance()
for name, attrs, content in parseXML(
- '<NamedInstance flags="0x0" postscriptNameID="257" subfamilyNameID="345">'
- ' <coord axis="wght" value="0.7"/>'
- ' <coord axis="wdth" value="0.5"/>'
- '</NamedInstance>'):
+ '<NamedInstance flags="0x0" postscriptNameID="257" subfamilyNameID="345">'
+ ' <coord axis="wght" value="0.7"/>'
+ ' <coord axis="wdth" value="0.5"/>'
+ "</NamedInstance>"
+ ):
inst.fromXML(name, attrs, content, ttFont=MakeFont())
self.assertEqual(257, inst.postscriptNameID)
self.assertEqual(345, inst.subfamilyNameID)
@@ -253,10 +264,11 @@ class NamedInstanceTest(unittest.TestCase):
def test_fromXML_withoutPostScriptName(self):
inst = NamedInstance()
for name, attrs, content in parseXML(
- '<NamedInstance flags="0x123ABC" subfamilyNameID="345">'
- ' <coord axis="wght" value="0.7"/>'
- ' <coord axis="wdth" value="0.5"/>'
- '</NamedInstance>'):
+ '<NamedInstance flags="0x123ABC" subfamilyNameID="345">'
+ ' <coord axis="wght" value="0.7"/>'
+ ' <coord axis="wdth" value="0.5"/>'
+ "</NamedInstance>"
+ ):
inst.fromXML(name, attrs, content, ttFont=MakeFont())
self.assertEqual(0x123ABC, inst.flags)
self.assertEqual(345, inst.subfamilyNameID)
@@ -265,4 +277,5 @@ class NamedInstanceTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_g_c_i_d_test.py b/Tests/ttLib/tables/_g_c_i_d_test.py
index e7666771..c5e027e0 100644
--- a/Tests/ttLib/tables/_g_c_i_d_test.py
+++ b/Tests/ttLib/tables/_g_c_i_d_test.py
@@ -7,39 +7,39 @@ import unittest
# On macOS X 10.12.3, the font /Library/Fonts/AppleGothic.ttf has a ‘gcid’
# table with a similar structure as this test data, just more CIDs.
GCID_DATA = deHexStr(
- "0000 0000 " # 0: Format=0, Flags=0
- "0000 0098 " # 4: Size=152
- "0000 " # 8: Registry=0
- "41 64 6F 62 65 " # 10: RegistryName="Adobe"
- + ("00" * 59) + # 15: <padding>
- "0003 " # 74: Order=3
+ "0000 0000 " # 0: Format=0, Flags=0
+ "0000 0098 " # 4: Size=152
+ "0000 " # 8: Registry=0
+ "41 64 6F 62 65 " # 10: RegistryName="Adobe"
+ + ("00" * 59)
+ + "0003 " # 15: <padding> # 74: Order=3
"4B 6F 72 65 61 31 " # 76: Order="Korea1"
- + ("00" * 58) + # 82: <padding>
- "0001 " # 140: SupplementVersion
- "0004 " # 142: Count
- "1234 " # 144: CIDs[0/.notdef]=4660
- "FFFF " # 146: CIDs[1/A]=None
- "0007 " # 148: CIDs[2/B]=7
- "DEF0 " # 150: CIDs[3/C]=57072
-) # 152: <end>
+ + ("00" * 58)
+ + "0001 " # 82: <padding> # 140: SupplementVersion
+ "0004 " # 142: Count
+ "1234 " # 144: CIDs[0/.notdef]=4660
+ "FFFF " # 146: CIDs[1/A]=None
+ "0007 " # 148: CIDs[2/B]=7
+ "DEF0 " # 150: CIDs[3/C]=57072
+) # 152: <end>
assert len(GCID_DATA) == 152, len(GCID_DATA)
GCID_XML = [
- '<GlyphCIDMapping Format="0">',
- ' <DataFormat value="0"/>',
- ' <!-- StructLength=152 -->',
- ' <Registry value="0"/>',
- ' <RegistryName value="Adobe"/>',
- ' <Order value="3"/>',
- ' <OrderName value="Korea1"/>',
- ' <SupplementVersion value="1"/>',
- ' <Mapping>',
- ' <CID glyph=".notdef" value="4660"/>',
- ' <CID glyph="B" value="7"/>',
- ' <CID glyph="C" value="57072"/>',
- ' </Mapping>',
- '</GlyphCIDMapping>',
+ '<GlyphCIDMapping Format="0">',
+ ' <DataFormat value="0"/>',
+ " <!-- StructLength=152 -->",
+ ' <Registry value="0"/>',
+ ' <RegistryName value="Adobe"/>',
+ ' <Order value="3"/>',
+ ' <OrderName value="Korea1"/>',
+ ' <SupplementVersion value="1"/>',
+ " <Mapping>",
+ ' <CID glyph=".notdef" value="4660"/>',
+ ' <CID glyph="B" value="7"/>',
+ ' <CID glyph="C" value="57072"/>',
+ " </Mapping>",
+ "</GlyphCIDMapping>",
]
@@ -47,21 +47,21 @@ class GCIDTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.notdef', 'A', 'B', 'C', 'D'])
+ cls.font = FakeFont([".notdef", "A", "B", "C", "D"])
def testDecompileToXML(self):
- table = newTable('gcid')
+ table = newTable("gcid")
table.decompile(GCID_DATA, self.font)
self.assertEqual(getXML(table.toXML, self.font), GCID_XML)
def testCompileFromXML(self):
- table = newTable('gcid')
+ table = newTable("gcid")
for name, attrs, content in parseXML(GCID_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(GCID_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(GCID_DATA))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_g_l_y_f_test.py b/Tests/ttLib/tables/_g_l_y_f_test.py
index 84f30dc6..ce2e0e57 100644
--- a/Tests/ttLib/tables/_g_l_y_f_test.py
+++ b/Tests/ttLib/tables/_g_l_y_f_test.py
@@ -1,5 +1,6 @@
from fontTools.misc.fixedTools import otRound
from fontTools.misc.testTools import getXML, parseXML
+from fontTools.misc.transform import Transform
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.pens.recordingPen import RecordingPen, RecordingPointPen
from fontTools.pens.pointPen import PointToSegmentPen
@@ -8,6 +9,9 @@ from fontTools.ttLib.tables._g_l_y_f import (
Glyph,
GlyphCoordinates,
GlyphComponent,
+ dropImpliedOnCurvePoints,
+ flagOnCurve,
+ flagCubic,
ARGS_ARE_XY_VALUES,
SCALED_COMPONENT_OFFSET,
UNSCALED_COMPONENT_OFFSET,
@@ -18,7 +22,8 @@ from fontTools.ttLib.tables._g_l_y_f import (
from fontTools.ttLib.tables import ttProgram
import sys
import array
-from io import StringIO
+from copy import deepcopy
+from io import StringIO, BytesIO
import itertools
import pytest
import re
@@ -27,139 +32,137 @@ import unittest
class GlyphCoordinatesTest(object):
-
def test_translate(self):
- g = GlyphCoordinates([(1,2)])
- g.translate((.5,0))
- assert g == GlyphCoordinates([(1.5,2.0)])
+ g = GlyphCoordinates([(1, 2)])
+ g.translate((0.5, 0))
+ assert g == GlyphCoordinates([(1.5, 2.0)])
def test_scale(self):
- g = GlyphCoordinates([(1,2)])
- g.scale((.5,0))
- assert g == GlyphCoordinates([(0.5,0.0)])
+ g = GlyphCoordinates([(1, 2)])
+ g.scale((0.5, 0))
+ assert g == GlyphCoordinates([(0.5, 0.0)])
def test_transform(self):
- g = GlyphCoordinates([(1,2)])
- g.transform(((.5,0),(.2,.5)))
- assert g[0] == GlyphCoordinates([(0.9,1.0)])[0]
+ g = GlyphCoordinates([(1, 2)])
+ g.transform(((0.5, 0), (0.2, 0.5)))
+ assert g[0] == GlyphCoordinates([(0.9, 1.0)])[0]
def test__eq__(self):
- g = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(1.0,2)])
- g3 = GlyphCoordinates([(1.5,2)])
+ g = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(1.0, 2)])
+ g3 = GlyphCoordinates([(1.5, 2)])
assert g == g2
assert not g == g3
assert not g2 == g3
assert not g == object()
def test__ne__(self):
- g = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(1.0,2)])
- g3 = GlyphCoordinates([(1.5,2)])
+ g = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(1.0, 2)])
+ g3 = GlyphCoordinates([(1.5, 2)])
assert not (g != g2)
assert g != g3
assert g2 != g3
assert g != object()
def test__pos__(self):
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
g2 = +g
assert g == g2
def test__neg__(self):
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
g2 = -g
assert g2 == GlyphCoordinates([(-1, -2)])
- @pytest.mark.skipif(sys.version_info[0] < 3,
- reason="__round___ requires Python 3")
+ @pytest.mark.skipif(sys.version_info[0] < 3, reason="__round___ requires Python 3")
def test__round__(self):
- g = GlyphCoordinates([(-1.5,2)])
+ g = GlyphCoordinates([(-1.5, 2)])
g2 = round(g)
- assert g2 == GlyphCoordinates([(-1,2)])
+ assert g2 == GlyphCoordinates([(-1, 2)])
def test__add__(self):
- g1 = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(3,4)])
- g3 = GlyphCoordinates([(4,6)])
+ g1 = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(3, 4)])
+ g3 = GlyphCoordinates([(4, 6)])
assert g1 + g2 == g3
- assert g1 + (1, 1) == GlyphCoordinates([(2,3)])
+ assert g1 + (1, 1) == GlyphCoordinates([(2, 3)])
with pytest.raises(TypeError) as excinfo:
assert g1 + object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__sub__(self):
- g1 = GlyphCoordinates([(1,2)])
- g2 = GlyphCoordinates([(3,4)])
- g3 = GlyphCoordinates([(-2,-2)])
+ g1 = GlyphCoordinates([(1, 2)])
+ g2 = GlyphCoordinates([(3, 4)])
+ g3 = GlyphCoordinates([(-2, -2)])
assert g1 - g2 == g3
- assert g1 - (1, 1) == GlyphCoordinates([(0,1)])
+ assert g1 - (1, 1) == GlyphCoordinates([(0, 1)])
with pytest.raises(TypeError) as excinfo:
assert g1 - object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__rsub__(self):
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
# other + (-self)
- assert (1, 1) - g == GlyphCoordinates([(0,-1)])
+ assert (1, 1) - g == GlyphCoordinates([(0, -1)])
def test__mul__(self):
- g = GlyphCoordinates([(1,2)])
- assert g * 3 == GlyphCoordinates([(3,6)])
- assert g * (3,2) == GlyphCoordinates([(3,4)])
- assert g * (1,1) == g
+ g = GlyphCoordinates([(1, 2)])
+ assert g * 3 == GlyphCoordinates([(3, 6)])
+ assert g * (3, 2) == GlyphCoordinates([(3, 4)])
+ assert g * (1, 1) == g
with pytest.raises(TypeError) as excinfo:
assert g * object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__truediv__(self):
- g = GlyphCoordinates([(1,2)])
- assert g / 2 == GlyphCoordinates([(.5,1)])
- assert g / (1, 2) == GlyphCoordinates([(1,1)])
+ g = GlyphCoordinates([(1, 2)])
+ assert g / 2 == GlyphCoordinates([(0.5, 1)])
+ assert g / (1, 2) == GlyphCoordinates([(1, 1)])
assert g / (1, 1) == g
with pytest.raises(TypeError) as excinfo:
assert g / object()
- assert 'unsupported operand' in str(excinfo.value)
+ assert "unsupported operand" in str(excinfo.value)
def test__iadd__(self):
- g = GlyphCoordinates([(1,2)])
- g += (.5,0)
+ g = GlyphCoordinates([(1, 2)])
+ g += (0.5, 0)
assert g == GlyphCoordinates([(1.5, 2.0)])
- g2 = GlyphCoordinates([(3,4)])
+ g2 = GlyphCoordinates([(3, 4)])
g += g2
assert g == GlyphCoordinates([(4.5, 6.0)])
def test__isub__(self):
- g = GlyphCoordinates([(1,2)])
- g -= (.5, 0)
+ g = GlyphCoordinates([(1, 2)])
+ g -= (0.5, 0)
assert g == GlyphCoordinates([(0.5, 2.0)])
- g2 = GlyphCoordinates([(3,4)])
+ g2 = GlyphCoordinates([(3, 4)])
g -= g2
assert g == GlyphCoordinates([(-2.5, -2.0)])
def __test__imul__(self):
- g = GlyphCoordinates([(1,2)])
- g *= (2,.5)
+ g = GlyphCoordinates([(1, 2)])
+ g *= (2, 0.5)
g *= 2
assert g == GlyphCoordinates([(4.0, 2.0)])
- g = GlyphCoordinates([(1,2)])
+ g = GlyphCoordinates([(1, 2)])
g *= 2
assert g == GlyphCoordinates([(2, 4)])
def test__itruediv__(self):
- g = GlyphCoordinates([(1,3)])
- g /= (.5,1.5)
+ g = GlyphCoordinates([(1, 3)])
+ g /= (0.5, 1.5)
g /= 2
assert g == GlyphCoordinates([(1.0, 1.0)])
def test__bool__(self):
g = GlyphCoordinates([])
assert bool(g) == False
- g = GlyphCoordinates([(0,0), (0.,0)])
+ g = GlyphCoordinates([(0, 0), (0.0, 0)])
assert bool(g) == True
- g = GlyphCoordinates([(0,0), (1,0)])
+ g = GlyphCoordinates([(0, 0), (1, 0)])
assert bool(g) == True
- g = GlyphCoordinates([(0,.5), (0,0)])
+ g = GlyphCoordinates([(0, 0.5), (0, 0)])
assert bool(g) == True
def test_double_precision_float(self):
@@ -179,21 +182,21 @@ class GlyphCoordinatesTest(object):
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
GLYF_TTX = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.ttx")
GLYF_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.glyf.bin")
HEAD_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.head.bin")
LOCA_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.loca.bin")
MAXP_BIN = os.path.join(DATA_DIR, "_g_l_y_f_outline_flag_bit6.maxp.bin")
+INST_TTX = os.path.join(DATA_DIR, "_g_l_y_f_instructions.ttx")
def strip_ttLibVersion(string):
- return re.sub(' ttLibVersion=".*"', '', string)
+ return re.sub(' ttLibVersion=".*"', "", string)
class GlyfTableTest(unittest.TestCase):
-
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
@@ -203,26 +206,26 @@ class GlyfTableTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
- with open(GLYF_BIN, 'rb') as f:
+ with open(GLYF_BIN, "rb") as f:
cls.glyfData = f.read()
- with open(HEAD_BIN, 'rb') as f:
+ with open(HEAD_BIN, "rb") as f:
cls.headData = f.read()
- with open(LOCA_BIN, 'rb') as f:
+ with open(LOCA_BIN, "rb") as f:
cls.locaData = f.read()
- with open(MAXP_BIN, 'rb') as f:
+ with open(MAXP_BIN, "rb") as f:
cls.maxpData = f.read()
- with open(GLYF_TTX, 'r') as f:
+ with open(GLYF_TTX, "r") as f:
cls.glyfXML = strip_ttLibVersion(f.read()).splitlines()
def test_toXML(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
- glyfTable = font['glyf'] = newTable('glyf')
- font['head'] = newTable('head')
- font['loca'] = newTable('loca')
- font['maxp'] = newTable('maxp')
- font['maxp'].decompile(self.maxpData, font)
- font['head'].decompile(self.headData, font)
- font['loca'].decompile(self.locaData, font)
+ glyfTable = font["glyf"] = newTable("glyf")
+ font["head"] = newTable("head")
+ font["loca"] = newTable("loca")
+ font["maxp"] = newTable("maxp")
+ font["maxp"].decompile(self.maxpData, font)
+ font["head"].decompile(self.headData, font)
+ font["loca"].decompile(self.locaData, font)
glyfTable.decompile(self.glyfData, font)
out = StringIO()
font.saveXML(out)
@@ -232,10 +235,22 @@ class GlyfTableTest(unittest.TestCase):
def test_fromXML(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ glyfTable = font["glyf"]
glyfData = glyfTable.compile(font)
self.assertEqual(glyfData, self.glyfData)
+ def test_instructions_roundtrip(self):
+ font = TTFont(sfntVersion="\x00\x01\x00\x00")
+ font.importXML(INST_TTX)
+ glyfTable = font["glyf"]
+ self.glyfData = glyfTable.compile(font)
+ out = StringIO()
+ font.saveXML(out)
+ glyfXML = strip_ttLibVersion(out.getvalue()).splitlines()
+ with open(INST_TTX, "r") as f:
+ origXML = strip_ttLibVersion(f.read()).splitlines()
+ self.assertEqual(glyfXML, origXML)
+
def test_recursiveComponent(self):
glyphSet = {}
pen_dummy = TTGlyphPen(glyphSet)
@@ -250,7 +265,9 @@ class GlyfTableTest(unittest.TestCase):
glyph_B = pen_B.glyph()
glyphSet["A"] = glyph_A
glyphSet["B"] = glyph_B
- with self.assertRaisesRegex(TTLibError, "glyph '.' contains a recursive component reference"):
+ with self.assertRaisesRegex(
+ TTLibError, "glyph '.' contains a recursive component reference"
+ ):
glyph_A.getCoordinates(glyphSet)
def test_trim_remove_hinting_composite_glyph(self):
@@ -260,7 +277,7 @@ class GlyfTableTest(unittest.TestCase):
pen.addComponent("dummy", (1, 0, 0, 1, 0, 0))
composite = pen.glyph()
p = ttProgram.Program()
- p.fromAssembly(['SVTCA[0]'])
+ p.fromAssembly(["SVTCA[0]"])
composite.program = p
glyphSet["composite"] = composite
@@ -294,22 +311,24 @@ class GlyfTableTest(unittest.TestCase):
# glyph00003 contains a bit 6 flag on the first point,
# which triggered the issue
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ 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', ())]
+ 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):
@@ -318,22 +337,22 @@ class GlyfTableTest(unittest.TestCase):
# glyph00003 contains a bit 6 flag on the first point
# which triggered the issue
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ 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), {}),
+ ("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)
+ 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']
+ glyfTable = font["glyf"]
pen1 = RecordingPen()
pen2 = RecordingPen()
glyfTable["glyph00003"].draw(pen1, glyfTable)
@@ -343,12 +362,12 @@ class GlyfTableTest(unittest.TestCase):
def test_compile_empty_table(self):
font = TTFont(sfntVersion="\x00\x01\x00\x00")
font.importXML(GLYF_TTX)
- glyfTable = font['glyf']
+ 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))
+ self.assertEqual(list(font["loca"]), [0] * (font["maxp"].numGlyphs + 1))
def test_decompile_empty_table(self):
font = TTFont()
@@ -372,16 +391,36 @@ class GlyfTableTest(unittest.TestCase):
font["glyf"] = newTable("glyf")
font["glyf"].decompile(b"\x00", font)
font["hmtx"] = newTable("hmtx")
- font["hmtx"].metrics = {".notdef": (100,0)}
+ font["hmtx"].metrics = {".notdef": (100, 0)}
font["head"] = newTable("head")
font["head"].unitsPerEm = 1000
- self.assertEqual(
- font["glyf"].getPhantomPoints(".notdef", font, 0),
- [(0, 0), (100, 0), (0, 0), (0, -1000)]
- )
+ with pytest.deprecated_call():
+ self.assertEqual(
+ font["glyf"].getPhantomPoints(".notdef", font, 0),
+ [(0, 0), (100, 0), (0, 0), (0, -1000)],
+ )
-class GlyphTest:
+ def test_getGlyphID(self):
+ # https://github.com/fonttools/fonttools/pull/3301#discussion_r1360405861
+ glyf = newTable("glyf")
+ glyf.setGlyphOrder([".notdef", "a", "b"])
+ glyf.glyphs = {}
+ for glyphName in glyf.glyphOrder:
+ glyf[glyphName] = Glyph()
+
+ assert glyf.getGlyphID("a") == 1
+ with pytest.raises(ValueError):
+ glyf.getGlyphID("c")
+
+ glyf["c"] = Glyph()
+ assert glyf.getGlyphID("c") == 3
+
+ del glyf["b"]
+ assert glyf.getGlyphID("c") == 2
+
+
+class GlyphTest:
def test_getCoordinates(self):
glyphSet = {}
pen = TTGlyphPen(glyphSet)
@@ -472,18 +511,30 @@ class GlyphTest:
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,
+ 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,
]
)
@@ -513,7 +564,6 @@ class GlyphTest:
class GlyphComponentTest:
-
def test_toXML_no_transform(self):
comp = GlyphComponent()
comp.glyphName = "a"
@@ -625,7 +675,7 @@ class GlyphComponentTest:
assert hasattr(comp, "transform")
for value, expected in zip(
itertools.chain(*comp.transform),
- [0.5999756, -0.2000122, 0.2000122, 0.2999878]
+ [0.5999756, -0.2000122, 0.2000122, 0.2999878],
):
assert value == pytest.approx(expected)
@@ -652,7 +702,349 @@ class GlyphComponentTest:
assert (comp.firstPt, comp.secondPt) == (1, 2)
assert not hasattr(comp, "transform")
+ def test_trim_varComposite_glyph(self):
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-ac00-ac01.ttf")
+ font = TTFont(font_path)
+ glyf = font["glyf"]
+
+ glyf.glyphs["uniAC00"].trim()
+ glyf.glyphs["uniAC01"].trim()
+
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-6868.ttf")
+ font = TTFont(font_path)
+ glyf = font["glyf"]
+
+ glyf.glyphs["uni6868"].trim()
+
+ def test_varComposite_basic(self):
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-ac00-ac01.ttf")
+ font = TTFont(font_path)
+ tables = [
+ table_tag
+ for table_tag in font.keys()
+ if table_tag not in {"head", "maxp", "hhea"}
+ ]
+ xml = StringIO()
+ font.saveXML(xml)
+ xml1 = StringIO()
+ font.saveXML(xml1, tables=tables)
+ xml.seek(0)
+ font = TTFont()
+ font.importXML(xml)
+ ttf = BytesIO()
+ font.save(ttf)
+ ttf.seek(0)
+ font = TTFont(ttf)
+ xml2 = StringIO()
+ font.saveXML(xml2, tables=tables)
+ assert xml1.getvalue() == xml2.getvalue()
+
+ font_path = os.path.join(DATA_DIR, "..", "..", "data", "varc-6868.ttf")
+ font = TTFont(font_path)
+ tables = [
+ table_tag
+ for table_tag in font.keys()
+ if table_tag not in {"head", "maxp", "hhea", "name", "fvar"}
+ ]
+ xml = StringIO()
+ font.saveXML(xml)
+ xml1 = StringIO()
+ font.saveXML(xml1, tables=tables)
+ xml.seek(0)
+ font = TTFont()
+ font.importXML(xml)
+ ttf = BytesIO()
+ font.save(ttf)
+ ttf.seek(0)
+ font = TTFont(ttf)
+ xml2 = StringIO()
+ font.saveXML(xml2, tables=tables)
+ assert xml1.getvalue() == xml2.getvalue()
+
+
+class GlyphCubicTest:
+ def test_roundtrip(self):
+ font_path = os.path.join(DATA_DIR, "NotoSans-VF-cubic.subset.ttf")
+ font = TTFont(font_path)
+ tables = [table_tag for table_tag in font.keys() if table_tag not in {"head"}]
+ xml = StringIO()
+ font.saveXML(xml)
+ xml1 = StringIO()
+ font.saveXML(xml1, tables=tables)
+ xml.seek(0)
+ font = TTFont()
+ font.importXML(xml)
+ ttf = BytesIO()
+ font.save(ttf)
+ ttf.seek(0)
+ font = TTFont(ttf)
+ xml2 = StringIO()
+ font.saveXML(xml2, tables=tables)
+ assert xml1.getvalue() == xml2.getvalue()
+
+ def test_no_oncurves(self):
+ glyph = Glyph()
+ glyph.numberOfContours = 1
+ glyph.coordinates = GlyphCoordinates(
+ [(0, 0), (1, 0), (1, 0), (1, 1), (1, 1), (0, 1), (0, 1), (0, 0)]
+ )
+ glyph.flags = array.array("B", [flagCubic] * 8)
+ glyph.endPtsOfContours = [7]
+ glyph.program = ttProgram.Program()
+
+ for i in range(2):
+ if i == 1:
+ glyph.compile(None)
+
+ pen = RecordingPen()
+ glyph.draw(pen, None)
+
+ assert pen.value == [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((0, 0), (1, 0), (1, 0))),
+ ("curveTo", ((1, 0), (1, 1), (1, 1))),
+ ("curveTo", ((1, 1), (0, 1), (0, 1))),
+ ("curveTo", ((0, 1), (0, 0), (0, 0))),
+ ("closePath", ()),
+ ]
+
+ def test_spline(self):
+ glyph = Glyph()
+ glyph.numberOfContours = 1
+ glyph.coordinates = GlyphCoordinates(
+ [(0, 0), (1, 0), (1, 0), (1, 1), (1, 1), (0, 1), (0, 1)]
+ )
+ glyph.flags = array.array("B", [flagOnCurve] + [flagCubic] * 6)
+ glyph.endPtsOfContours = [6]
+ glyph.program = ttProgram.Program()
+
+ for i in range(2):
+ if i == 1:
+ glyph.compile(None)
+
+ pen = RecordingPen()
+ glyph.draw(pen, None)
+
+ assert pen.value == [
+ ("moveTo", ((0, 0),)),
+ ("curveTo", ((1, 0), (1, 0), (1.0, 0.5))),
+ ("curveTo", ((1, 1), (1, 1), (0.5, 1.0))),
+ ("curveTo", ((0, 1), (0, 1), (0, 0))),
+ ("closePath", ()),
+ ]
+
+
+def build_interpolatable_glyphs(contours, *transforms):
+ # given a list of lists of (point, flag) tuples (one per contour), build a Glyph
+ # then make len(transforms) copies transformed accordingly, and return a
+ # list of such interpolatable glyphs.
+ glyph1 = Glyph()
+ glyph1.numberOfContours = len(contours)
+ glyph1.coordinates = GlyphCoordinates(
+ [pt for contour in contours for pt, _flag in contour]
+ )
+ glyph1.flags = array.array(
+ "B", [flag for contour in contours for _pt, flag in contour]
+ )
+ glyph1.endPtsOfContours = [
+ sum(len(contour) for contour in contours[: i + 1]) - 1
+ for i in range(len(contours))
+ ]
+ result = [glyph1]
+ for t in transforms:
+ glyph = deepcopy(glyph1)
+ glyph.coordinates.transform((t[0:2], t[2:4]))
+ glyph.coordinates.translate(t[4:6])
+ result.append(glyph)
+ return result
+
+
+def test_dropImpliedOnCurvePoints_all_quad_off_curves():
+ # Two interpolatable glyphs with same structure, the coordinates of one are 2x the
+ # other; all the on-curve points are impliable in each one, thus are dropped from
+ # both, leaving contours with off-curve points only.
+ glyph1, glyph2 = build_interpolatable_glyphs(
+ [
+ [
+ ((0, 1), flagOnCurve),
+ ((1, 1), 0),
+ ((1, 0), flagOnCurve),
+ ((1, -1), 0),
+ ((0, -1), flagOnCurve),
+ ((-1, -1), 0),
+ ((-1, 0), flagOnCurve),
+ ((-1, 1), 0),
+ ],
+ [
+ ((0, 2), flagOnCurve),
+ ((2, 2), 0),
+ ((2, 0), flagOnCurve),
+ ((2, -2), 0),
+ ((0, -2), flagOnCurve),
+ ((-2, -2), 0),
+ ((-2, 0), flagOnCurve),
+ ((-2, 2), 0),
+ ],
+ ],
+ Transform().scale(2.0),
+ )
+ # also add an empty glyph (will be ignored); we use this trick for 'sparse' masters
+ glyph3 = Glyph()
+ glyph3.numberOfContours = 0
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {
+ 0,
+ 2,
+ 4,
+ 6,
+ 8,
+ 10,
+ 12,
+ 14,
+ }
+
+ assert glyph1.flags == glyph2.flags == array.array("B", [0, 0, 0, 0, 0, 0, 0, 0])
+ assert glyph1.coordinates == GlyphCoordinates(
+ [(1, 1), (1, -1), (-1, -1), (-1, 1), (2, 2), (2, -2), (-2, -2), (-2, 2)]
+ )
+ assert glyph2.coordinates == GlyphCoordinates(
+ [(2, 2), (2, -2), (-2, -2), (-2, 2), (4, 4), (4, -4), (-4, -4), (-4, 4)]
+ )
+ assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [3, 7]
+ assert glyph3.numberOfContours == 0
+
+
+def test_dropImpliedOnCurvePoints_all_cubic_off_curves():
+ # same as above this time using cubic curves
+ glyph1, glyph2 = build_interpolatable_glyphs(
+ [
+ [
+ ((0, 1), flagOnCurve),
+ ((1, 1), flagCubic),
+ ((1, 1), flagCubic),
+ ((1, 0), flagOnCurve),
+ ((1, -1), flagCubic),
+ ((1, -1), flagCubic),
+ ((0, -1), flagOnCurve),
+ ((-1, -1), flagCubic),
+ ((-1, -1), flagCubic),
+ ((-1, 0), flagOnCurve),
+ ((-1, 1), flagCubic),
+ ((-1, 1), flagCubic),
+ ]
+ ],
+ Transform().translate(10.0),
+ )
+ glyph3 = Glyph()
+ glyph3.numberOfContours = 0
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {0, 3, 6, 9}
+
+ assert glyph1.flags == glyph2.flags == array.array("B", [flagCubic] * 8)
+ assert glyph1.coordinates == GlyphCoordinates(
+ [(1, 1), (1, 1), (1, -1), (1, -1), (-1, -1), (-1, -1), (-1, 1), (-1, 1)]
+ )
+ assert glyph2.coordinates == GlyphCoordinates(
+ [(11, 1), (11, 1), (11, -1), (11, -1), (9, -1), (9, -1), (9, 1), (9, 1)]
+ )
+ assert glyph1.endPtsOfContours == glyph2.endPtsOfContours == [7]
+ assert glyph3.numberOfContours == 0
+
+
+def test_dropImpliedOnCurvePoints_not_all_impliable():
+ # same input as in in test_dropImpliedOnCurvePoints_all_quad_off_curves but we
+ # perturbate one of the glyphs such that the 2nd on-curve is no longer half-way
+ # between the neighboring off-curves.
+ glyph1, glyph2, glyph3 = build_interpolatable_glyphs(
+ [
+ [
+ ((0, 1), flagOnCurve),
+ ((1, 1), 0),
+ ((1, 0), flagOnCurve),
+ ((1, -1), 0),
+ ((0, -1), flagOnCurve),
+ ((-1, -1), 0),
+ ((-1, 0), flagOnCurve),
+ ((-1, 1), 0),
+ ]
+ ],
+ Transform().translate(10.0),
+ Transform().translate(10.0).scale(2.0),
+ )
+ p2 = glyph2.coordinates[2]
+ glyph2.coordinates[2] = (p2[0] + 2.0, p2[1] - 2.0)
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2, glyph3) == {
+ 0,
+ # 2, this is NOT implied because it's no longer impliable for all glyphs
+ 4,
+ 6,
+ }
+
+ assert glyph2.flags == array.array("B", [0, flagOnCurve, 0, 0, 0])
+
+
+def test_dropImpliedOnCurvePoints_all_empty_glyphs():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 0
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 0
+
+ assert dropImpliedOnCurvePoints(glyph1, glyph2) == set()
+
+
+def test_dropImpliedOnCurvePoints_incompatible_number_of_contours():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 1
+ glyph1.endPtsOfContours = [3]
+ glyph1.flags = array.array("B", [1, 1, 1, 1])
+ glyph1.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 2
+ glyph2.endPtsOfContours = [1, 3]
+ glyph2.flags = array.array("B", [1, 1, 1, 1])
+ glyph2.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ with pytest.raises(ValueError, match="Incompatible numberOfContours"):
+ dropImpliedOnCurvePoints(glyph1, glyph2)
+
+
+def test_dropImpliedOnCurvePoints_incompatible_flags():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 1
+ glyph1.endPtsOfContours = [3]
+ glyph1.flags = array.array("B", [1, 1, 1, 1])
+ glyph1.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 1
+ glyph2.endPtsOfContours = [3]
+ glyph2.flags = array.array("B", [0, 0, 0, 0])
+ glyph2.coordinates = GlyphCoordinates([(0, 0), (1, 1), (2, 2), (3, 3)])
+
+ with pytest.raises(ValueError, match="Incompatible flags"):
+ dropImpliedOnCurvePoints(glyph1, glyph2)
+
+
+def test_dropImpliedOnCurvePoints_incompatible_endPtsOfContours():
+ glyph1 = Glyph()
+ glyph1.numberOfContours = 2
+ glyph1.endPtsOfContours = [2, 6]
+ glyph1.flags = array.array("B", [1, 1, 1, 1, 1, 1, 1])
+ glyph1.coordinates = GlyphCoordinates([(i, i) for i in range(7)])
+
+ glyph2 = Glyph()
+ glyph2.numberOfContours = 2
+ glyph2.endPtsOfContours = [3, 6]
+ glyph2.flags = array.array("B", [1, 1, 1, 1, 1, 1, 1])
+ glyph2.coordinates = GlyphCoordinates([(i, i) for i in range(7)])
+
+ with pytest.raises(ValueError, match="Incompatible endPtsOfContours"):
+ dropImpliedOnCurvePoints(glyph1, glyph2)
+
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_g_v_a_r_test.py b/Tests/ttLib/tables/_g_v_a_r_test.py
index 077bb639..4fe3ae96 100644
--- a/Tests/ttLib/tables/_g_v_a_r_test.py
+++ b/Tests/ttLib/tables/_g_v_a_r_test.py
@@ -9,12 +9,12 @@ gvarClass = getTableClass("gvar")
GVAR_DATA = deHexStr(
- "0001 0000 " # 0: majorVersion=1 minorVersion=0
- "0002 0000 " # 4: axisCount=2 sharedTupleCount=0
- "0000001C " # 8: offsetToSharedTuples=28
- "0003 0000 " # 12: glyphCount=3 flags=0
- "0000001C " # 16: offsetToGlyphVariationData=28
- "0000 0000 000C 002F " # 20: offsets=[0,0,12,47], times 2: [0,0,24,94],
+ "0001 0000 " # 0: majorVersion=1 minorVersion=0
+ "0002 0000 " # 4: axisCount=2 sharedTupleCount=0
+ "0000001C " # 8: offsetToSharedTuples=28
+ "0003 0000 " # 12: glyphCount=3 flags=0
+ "0000001C " # 16: offsetToGlyphVariationData=28
+ "0000 0000 000C 002F " # 20: offsets=[0,0,12,47], times 2: [0,0,24,94],
# # +offsetToGlyphVariationData: [28,28,52,122]
#
# 28: Glyph variation data for glyph #0, ".notdef"
@@ -23,56 +23,57 @@ GVAR_DATA = deHexStr(
#
# 28: Glyph variation data for glyph #1, "space"
# ----------------------------------------------
- "8001 000C " # 28: tupleVariationCount=1|TUPLES_SHARE_POINT_NUMBERS, offsetToData=12(+28=40)
- "000A " # 32: tvHeader[0].variationDataSize=10
- "8000 " # 34: tvHeader[0].tupleIndex=EMBEDDED_PEAK
- "0000 2CCD " # 36: tvHeader[0].peakTuple={wght:0.0, wdth:0.7}
- "00 " # 40: all points
- "03 01 02 03 04 " # 41: deltaX=[1, 2, 3, 4]
- "03 0b 16 21 2C " # 46: deltaY=[11, 22, 33, 44]
- "00 " # 51: padding
+ "8001 000C " # 28: tupleVariationCount=1|TUPLES_SHARE_POINT_NUMBERS, offsetToData=12(+28=40)
+ "000A " # 32: tvHeader[0].variationDataSize=10
+ "8000 " # 34: tvHeader[0].tupleIndex=EMBEDDED_PEAK
+ "0000 2CCD " # 36: tvHeader[0].peakTuple={wght:0.0, wdth:0.7}
+ "00 " # 40: all points
+ "03 01 02 03 04 " # 41: deltaX=[1, 2, 3, 4]
+ "03 0b 16 21 2C " # 46: deltaY=[11, 22, 33, 44]
+ "00 " # 51: padding
#
# 52: Glyph variation data for glyph #2, "I"
# ------------------------------------------
- "8002 001c " # 52: tupleVariationCount=2|TUPLES_SHARE_POINT_NUMBERS, offsetToData=28(+52=80)
- "0012 " # 56: tvHeader[0].variationDataSize=18
- "C000 " # 58: tvHeader[0].tupleIndex=EMBEDDED_PEAK|INTERMEDIATE_REGION
- "2000 0000 " # 60: tvHeader[0].peakTuple={wght:0.5, wdth:0.0}
- "0000 0000 " # 64: tvHeader[0].intermediateStart={wght:0.0, wdth:0.0}
- "4000 0000 " # 68: tvHeader[0].intermediateEnd={wght:1.0, wdth:0.0}
- "0016 " # 72: tvHeader[1].variationDataSize=22
- "A000 " # 74: tvHeader[1].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINTS
- "C000 3333 " # 76: tvHeader[1].peakTuple={wght:-1.0, wdth:0.8}
- "00 " # 80: all points
- "07 03 01 04 01 " # 81: deltaX.len=7, deltaX=[3, 1, 4, 1,
- "05 09 02 06 " # 86: 5, 9, 2, 6]
- "07 03 01 04 01 " # 90: deltaY.len=7, deltaY=[3, 1, 4, 1,
- "05 09 02 06 " # 95: 5, 9, 2, 6]
- "06 " # 99: 6 points
- "05 00 01 03 01 " # 100: runLen=5(+1=6); delta-encoded run=[0, 1, 4, 5,
- "01 01 " # 105: 6, 7]
+ "8002 001c " # 52: tupleVariationCount=2|TUPLES_SHARE_POINT_NUMBERS, offsetToData=28(+52=80)
+ "0012 " # 56: tvHeader[0].variationDataSize=18
+ "C000 " # 58: tvHeader[0].tupleIndex=EMBEDDED_PEAK|INTERMEDIATE_REGION
+ "2000 0000 " # 60: tvHeader[0].peakTuple={wght:0.5, wdth:0.0}
+ "0000 0000 " # 64: tvHeader[0].intermediateStart={wght:0.0, wdth:0.0}
+ "4000 0000 " # 68: tvHeader[0].intermediateEnd={wght:1.0, wdth:0.0}
+ "0016 " # 72: tvHeader[1].variationDataSize=22
+ "A000 " # 74: tvHeader[1].tupleIndex=EMBEDDED_PEAK|PRIVATE_POINTS
+ "C000 3333 " # 76: tvHeader[1].peakTuple={wght:-1.0, wdth:0.8}
+ "00 " # 80: all points
+ "07 03 01 04 01 " # 81: deltaX.len=7, deltaX=[3, 1, 4, 1,
+ "05 09 02 06 " # 86: 5, 9, 2, 6]
+ "07 03 01 04 01 " # 90: deltaY.len=7, deltaY=[3, 1, 4, 1,
+ "05 09 02 06 " # 95: 5, 9, 2, 6]
+ "06 " # 99: 6 points
+ "05 00 01 03 01 " # 100: runLen=5(+1=6); delta-encoded run=[0, 1, 4, 5,
+ "01 01 " # 105: 6, 7]
"05 f8 07 fc 03 fe 01 " # 107: deltaX.len=5, deltaX=[-8,7,-4,3,-2,1]
"05 a8 4d 2c 21 ea 0b " # 114: deltaY.len=5, deltaY=[-88,77,44,33,-22,11]
- "00" # 121: padding
-) # 122: <end>
+ "00" # 121: padding
+) # 122: <end>
assert len(GVAR_DATA) == 122
GVAR_VARIATIONS = {
- ".notdef": [
- ],
+ ".notdef": [],
"space": [
TupleVariation(
- {"wdth": (0.0, 0.7000122, 0.7000122)},
- [(1, 11), (2, 22), (3, 33), (4, 44)]),
+ {"wdth": (0.0, 0.7000122, 0.7000122)}, [(1, 11), (2, 22), (3, 33), (4, 44)]
+ ),
],
"I": [
TupleVariation(
{"wght": (0.0, 0.5, 1.0)},
- [(3,3), (1,1), (4,4), (1,1), (5,5), (9,9), (2,2), (6,6)]),
+ [(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.7999878, 0.7999878)},
- [(-8,-88), (7,77), None, None, (-4,44), (3,33), (-2,-22), (1,11)]),
+ [(-8, -88), (7, 77), None, None, (-4, 44), (3, 33), (-2, -22), (1, 11)],
+ ),
],
}
@@ -81,7 +82,7 @@ GVAR_XML = [
'<version value="1"/>',
'<reserved value="0"/>',
'<glyphVariations glyph="I">',
- ' <tuple>',
+ " <tuple>",
' <coord axis="wght" min="0.0" value="0.5" max="1.0"/>',
' <delta pt="0" x="3" y="3"/>',
' <delta pt="1" x="1" y="1"/>',
@@ -91,8 +92,8 @@ GVAR_XML = [
' <delta pt="5" x="9" y="9"/>',
' <delta pt="6" x="2" y="2"/>',
' <delta pt="7" x="6" y="6"/>',
- ' </tuple>',
- ' <tuple>',
+ " </tuple>",
+ " <tuple>",
' <coord axis="wght" value="-1.0"/>',
' <coord axis="wdth" value="0.8"/>',
' <delta pt="0" x="-8" y="-88"/>',
@@ -101,125 +102,137 @@ GVAR_XML = [
' <delta pt="5" x="3" y="33"/>',
' <delta pt="6" x="-2" y="-22"/>',
' <delta pt="7" x="1" y="11"/>',
- ' </tuple>',
- '</glyphVariations>',
+ " </tuple>",
+ "</glyphVariations>",
'<glyphVariations glyph="space">',
- ' <tuple>',
+ " <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>',
+ " </tuple>",
+ "</glyphVariations>",
]
GVAR_DATA_EMPTY_VARIATIONS = deHexStr(
- "0001 0000 " # 0: majorVersion=1 minorVersion=0
- "0002 0000 " # 4: axisCount=2 sharedTupleCount=0
- "0000001c " # 8: offsetToSharedTuples=28
- "0003 0000 " # 12: glyphCount=3 flags=0
- "0000001c " # 16: offsetToGlyphVariationData=28
+ "0001 0000 " # 0: majorVersion=1 minorVersion=0
+ "0002 0000 " # 4: axisCount=2 sharedTupleCount=0
+ "0000001c " # 8: offsetToSharedTuples=28
+ "0003 0000 " # 12: glyphCount=3 flags=0
+ "0000001c " # 16: offsetToGlyphVariationData=28
"0000 0000 0000 0000" # 20: offsets=[0, 0, 0, 0]
-) # 28: <end>
+) # 28: <end>
def hexencode(s):
- h = hexStr(s).upper()
- return ' '.join([h[i:i+2] for i in range(0, len(h), 2)])
+ h = hexStr(s).upper()
+ return " ".join([h[i : i + 2] for i in range(0, len(h), 2)])
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
- Glyph = getTableModule("glyf").Glyph
- glyf, fvar, gvar = newTable("glyf"), newTable("fvar"), newTable("gvar")
- font = FakeFont(glyphs)
- font.tables = {"glyf": glyf, "gvar": gvar, "fvar": fvar}
- glyf.glyphs = {glyph: Glyph() for glyph in glyphs}
- glyf.glyphs["I"].coordinates = [(10, 10), (10, 20), (20, 20), (20, 10)]
- fvar.axes = [Axis(), Axis()]
- fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth"
- gvar.variations = variations
- return font, gvar
-
- def test_compile(self):
- font, gvar = self.makeFont(GVAR_VARIATIONS)
- self.assertEqual(hexStr(gvar.compile(font)), hexStr(GVAR_DATA))
-
- def test_compile_noVariations(self):
- font, gvar = self.makeFont({})
- self.assertEqual(hexStr(gvar.compile(font)),
- hexStr(GVAR_DATA_EMPTY_VARIATIONS))
-
- def test_compile_emptyVariations(self):
- font, gvar = self.makeFont({".notdef": [], "space": [], "I": []})
- self.assertEqual(hexStr(gvar.compile(font)),
- hexStr(GVAR_DATA_EMPTY_VARIATIONS))
-
- def test_decompile(self):
- font, gvar = self.makeFont({})
- gvar.decompile(GVAR_DATA, font)
- self.assertVariationsAlmostEqual(gvar.variations, GVAR_VARIATIONS)
-
- def test_decompile_noVariations(self):
- font, gvar = self.makeFont({})
- gvar.decompile(GVAR_DATA_EMPTY_VARIATIONS, font)
- self.assertEqual(gvar.variations,
- {".notdef": [], "space": [], "I": []})
-
- def test_fromXML(self):
- font, gvar = self.makeFont({})
- for name, attrs, content in parseXML(GVAR_XML):
- gvar.fromXML(name, attrs, content, ttFont=font)
- 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)
- self.assertEqual(getXML(gvar.toXML, font), GVAR_XML)
-
- def test_compileOffsets_shortFormat(self):
- self.assertEqual((deHexStr("00 00 00 02 FF C0"), 0),
- gvarClass.compileOffsets_([0, 4, 0x1ff80]))
-
- def test_compileOffsets_longFormat(self):
- self.assertEqual((deHexStr("00 00 00 00 00 00 00 04 CA FE BE EF"), 1),
- gvarClass.compileOffsets_([0, 4, 0xCAFEBEEF]))
-
- def test_decompileOffsets_shortFormat(self):
- decompileOffsets = gvarClass.decompileOffsets_
- data = deHexStr("00 11 22 33 44 55 66 77 88 99 aa bb")
- self.assertEqual(
- [2*0x0011, 2*0x2233, 2*0x4455, 2*0x6677, 2*0x8899, 2*0xaabb],
- list(decompileOffsets(data, tableFormat=0, glyphCount=5)))
-
- def test_decompileOffsets_longFormat(self):
- decompileOffsets = gvarClass.decompileOffsets_
- data = deHexStr("00 11 22 33 44 55 66 77 88 99 aa bb")
- self.assertEqual(
- [0x00112233, 0x44556677, 0x8899aabb],
- list(decompileOffsets(data, tableFormat=1, glyphCount=2)))
+ 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
+ Glyph = getTableModule("glyf").Glyph
+ glyf, fvar, gvar = newTable("glyf"), newTable("fvar"), newTable("gvar")
+ font = FakeFont(glyphs)
+ font.tables = {"glyf": glyf, "gvar": gvar, "fvar": fvar}
+ glyf.glyphs = {glyph: Glyph() for glyph in glyphs}
+ glyf.glyphs["I"].coordinates = [(10, 10), (10, 20), (20, 20), (20, 10)]
+ fvar.axes = [Axis(), Axis()]
+ fvar.axes[0].axisTag, fvar.axes[1].axisTag = "wght", "wdth"
+ gvar.variations = variations
+ return font, gvar
+
+ def test_compile(self):
+ font, gvar = self.makeFont(GVAR_VARIATIONS)
+ self.assertEqual(hexStr(gvar.compile(font)), hexStr(GVAR_DATA))
+
+ def test_compile_noVariations(self):
+ font, gvar = self.makeFont({})
+ self.assertEqual(hexStr(gvar.compile(font)), hexStr(GVAR_DATA_EMPTY_VARIATIONS))
+
+ def test_compile_emptyVariations(self):
+ font, gvar = self.makeFont({".notdef": [], "space": [], "I": []})
+ self.assertEqual(hexStr(gvar.compile(font)), hexStr(GVAR_DATA_EMPTY_VARIATIONS))
+
+ def test_decompile(self):
+ for lazy in (True, False, None):
+ with self.subTest(lazy=lazy):
+ font, gvar = self.makeFont({})
+ font.lazy = lazy
+ gvar.decompile(GVAR_DATA, font)
+
+ self.assertEqual(
+ all(callable(v) for v in gvar.variations.data.values()),
+ lazy is not False,
+ )
+
+ self.assertVariationsAlmostEqual(gvar.variations, GVAR_VARIATIONS)
+
+ def test_decompile_noVariations(self):
+ font, gvar = self.makeFont({})
+ gvar.decompile(GVAR_DATA_EMPTY_VARIATIONS, font)
+ self.assertEqual(gvar.variations, {".notdef": [], "space": [], "I": []})
+
+ def test_fromXML(self):
+ font, gvar = self.makeFont({})
+ for name, attrs, content in parseXML(GVAR_XML):
+ gvar.fromXML(name, attrs, content, ttFont=font)
+ 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)
+ self.assertEqual(getXML(gvar.toXML, font), GVAR_XML)
+
+ def test_compileOffsets_shortFormat(self):
+ self.assertEqual(
+ (deHexStr("00 00 00 02 FF C0"), 0),
+ gvarClass.compileOffsets_([0, 4, 0x1FF80]),
+ )
+
+ def test_compileOffsets_longFormat(self):
+ self.assertEqual(
+ (deHexStr("00 00 00 00 00 00 00 04 CA FE BE EF"), 1),
+ gvarClass.compileOffsets_([0, 4, 0xCAFEBEEF]),
+ )
+
+ def test_decompileOffsets_shortFormat(self):
+ decompileOffsets = gvarClass.decompileOffsets_
+ data = deHexStr("00 11 22 33 44 55 66 77 88 99 aa bb")
+ self.assertEqual(
+ [2 * 0x0011, 2 * 0x2233, 2 * 0x4455, 2 * 0x6677, 2 * 0x8899, 2 * 0xAABB],
+ list(decompileOffsets(data, tableFormat=0, glyphCount=5)),
+ )
+
+ def test_decompileOffsets_longFormat(self):
+ decompileOffsets = gvarClass.decompileOffsets_
+ data = deHexStr("00 11 22 33 44 55 66 77 88 99 aa bb")
+ self.assertEqual(
+ [0x00112233, 0x44556677, 0x8899AABB],
+ list(decompileOffsets(data, tableFormat=1, glyphCount=2)),
+ )
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_h_h_e_a_test.py b/Tests/ttLib/tables/_h_h_e_a_test.py
index e04fd7bb..4b5c6338 100644
--- a/Tests/ttLib/tables/_h_h_e_a_test.py
+++ b/Tests/ttLib/tables/_h_h_e_a_test.py
@@ -8,47 +8,47 @@ import unittest
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
HHEA_DATA = deHexStr(
- '0001 0000 ' # 1.0 version
- '02EE ' # 750 ascent
- 'FF06 ' # -250 descent
- '00C8 ' # 200 lineGap
- '03E8 ' # 1000 advanceWidthMax
- 'FFE7 ' # -25 minLeftSideBearing
- 'FFEC ' # -20 minRightSideBearing
- '03D1 ' # 977 xMaxExtent
- '0000 ' # 0 caretSlopeRise
- '0001 ' # 1 caretSlopeRun
- '0010 ' # 16 caretOffset
- '0000 ' # 0 reserved0
- '0000 ' # 0 reserved1
- '0000 ' # 0 reserved2
- '0000 ' # 0 reserved3
- '0000 ' # 0 metricDataFormat
- '002A ' # 42 numberOfHMetrics
+ "0001 0000 " # 1.0 version
+ "02EE " # 750 ascent
+ "FF06 " # -250 descent
+ "00C8 " # 200 lineGap
+ "03E8 " # 1000 advanceWidthMax
+ "FFE7 " # -25 minLeftSideBearing
+ "FFEC " # -20 minRightSideBearing
+ "03D1 " # 977 xMaxExtent
+ "0000 " # 0 caretSlopeRise
+ "0001 " # 1 caretSlopeRun
+ "0010 " # 16 caretOffset
+ "0000 " # 0 reserved0
+ "0000 " # 0 reserved1
+ "0000 " # 0 reserved2
+ "0000 " # 0 reserved3
+ "0000 " # 0 metricDataFormat
+ "002A " # 42 numberOfHMetrics
)
HHEA_AS_DICT = {
- 'tableTag': 'hhea',
- 'tableVersion': 0x00010000,
- 'ascent': 750,
- 'descent': -250,
- 'lineGap': 200,
- 'advanceWidthMax': 1000,
- 'minLeftSideBearing': -25,
- 'minRightSideBearing': -20,
- 'xMaxExtent': 977,
- 'caretSlopeRise': 0,
- 'caretSlopeRun': 1,
- 'caretOffset': 16,
- 'reserved0': 0,
- 'reserved1': 0,
- 'reserved2': 0,
- 'reserved3': 0,
- 'metricDataFormat': 0,
- 'numberOfHMetrics': 42,
+ "tableTag": "hhea",
+ "tableVersion": 0x00010000,
+ "ascent": 750,
+ "descent": -250,
+ "lineGap": 200,
+ "advanceWidthMax": 1000,
+ "minLeftSideBearing": -25,
+ "minRightSideBearing": -20,
+ "xMaxExtent": 977,
+ "caretSlopeRise": 0,
+ "caretSlopeRun": 1,
+ "caretOffset": 16,
+ "reserved0": 0,
+ "reserved1": 0,
+ "reserved2": 0,
+ "reserved3": 0,
+ "metricDataFormat": 0,
+ "numberOfHMetrics": 42,
}
HHEA_XML = [
@@ -77,9 +77,8 @@ HHEA_XML_VERSION_AS_FLOAT = [
class HheaCompileOrToXMLTest(unittest.TestCase):
-
def setUp(self):
- hhea = newTable('hhea')
+ hhea = newTable("hhea")
hhea.tableVersion = 0x00010000
hhea.ascent = 750
hhea.descent = -250
@@ -94,39 +93,45 @@ class HheaCompileOrToXMLTest(unittest.TestCase):
hhea.metricDataFormat = 0
hhea.numberOfHMetrics = 42
hhea.reserved0 = hhea.reserved1 = hhea.reserved2 = hhea.reserved3 = 0
- self.font = TTFont(sfntVersion='OTTO')
- self.font['hhea'] = hhea
+ self.font = TTFont(sfntVersion="OTTO")
+ self.font["hhea"] = hhea
def test_compile(self):
- hhea = self.font['hhea']
+ hhea = self.font["hhea"]
hhea.tableVersion = 0x00010000
self.assertEqual(HHEA_DATA, hhea.compile(self.font))
def test_compile_version_10_as_float(self):
- hhea = self.font['hhea']
+ hhea = self.font["hhea"]
hhea.tableVersion = 1.0
with CapturingLogHandler(log, "WARNING") as captor:
self.assertEqual(HHEA_DATA, hhea.compile(self.font))
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
def test_toXML(self):
- hhea = self.font['hhea']
- self.font['hhea'].tableVersion = 0x00010000
+ hhea = self.font["hhea"]
+ self.font["hhea"].tableVersion = 0x00010000
self.assertEqual(getXML(hhea.toXML), HHEA_XML)
def test_toXML_version_as_float(self):
- hhea = self.font['hhea']
+ hhea = self.font["hhea"]
hhea.tableVersion = 1.0
with CapturingLogHandler(log, "WARNING") as captor:
self.assertEqual(getXML(hhea.toXML), HHEA_XML)
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ 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']
+ hhea = self.font["hhea"]
self.assertEqual(hhea.ascent, hhea.ascender)
self.assertEqual(hhea.descent, hhea.descender)
hhea.ascender = 800
@@ -138,44 +143,46 @@ class HheaCompileOrToXMLTest(unittest.TestCase):
hhea.descent = -299
self.assertEqual(hhea.descender, -299)
-class HheaDecompileOrFromXMLTest(unittest.TestCase):
+class HheaDecompileOrFromXMLTest(unittest.TestCase):
def setUp(self):
- hhea = newTable('hhea')
- self.font = TTFont(sfntVersion='OTTO')
- self.font['hhea'] = hhea
+ hhea = newTable("hhea")
+ self.font = TTFont(sfntVersion="OTTO")
+ self.font["hhea"] = hhea
def test_decompile(self):
- hhea = self.font['hhea']
+ hhea = self.font["hhea"]
hhea.decompile(HHEA_DATA, self.font)
for key in hhea.__dict__:
self.assertEqual(getattr(hhea, key), HHEA_AS_DICT[key])
def test_fromXML(self):
- hhea = self.font['hhea']
+ hhea = self.font["hhea"]
for name, attrs, content in parseXML(HHEA_XML):
hhea.fromXML(name, attrs, content, self.font)
for key in hhea.__dict__:
self.assertEqual(getattr(hhea, key), HHEA_AS_DICT[key])
def test_fromXML_version_as_float(self):
- hhea = self.font['hhea']
+ hhea = self.font["hhea"]
with CapturingLogHandler(log, "WARNING") as captor:
for name, attrs, content in parseXML(HHEA_XML_VERSION_AS_FLOAT):
hhea.fromXML(name, attrs, content, self.font)
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
for key in hhea.__dict__:
self.assertEqual(getattr(hhea, key), HHEA_AS_DICT[key])
class HheaRecalcTest(unittest.TestCase):
-
def test_recalc_TTF(self):
font = TTFont()
- font.importXML(os.path.join(DATA_DIR, '_h_h_e_a_recalc_TTF.ttx'))
- hhea = font['hhea']
+ font.importXML(os.path.join(DATA_DIR, "_h_h_e_a_recalc_TTF.ttx"))
+ hhea = font["hhea"]
hhea.recalc(font)
self.assertEqual(hhea.advanceWidthMax, 600)
self.assertEqual(hhea.minLeftSideBearing, -56)
@@ -184,8 +191,8 @@ class HheaRecalcTest(unittest.TestCase):
def test_recalc_OTF(self):
font = TTFont()
- font.importXML(os.path.join(DATA_DIR, '_h_h_e_a_recalc_OTF.ttx'))
- hhea = font['hhea']
+ font.importXML(os.path.join(DATA_DIR, "_h_h_e_a_recalc_OTF.ttx"))
+ hhea = font["hhea"]
hhea.recalc(font)
self.assertEqual(hhea.advanceWidthMax, 600)
self.assertEqual(hhea.minLeftSideBearing, -56)
@@ -194,8 +201,8 @@ class HheaRecalcTest(unittest.TestCase):
def test_recalc_empty(self):
font = TTFont()
- font.importXML(os.path.join(DATA_DIR, '_h_h_e_a_recalc_empty.ttx'))
- hhea = font['hhea']
+ font.importXML(os.path.join(DATA_DIR, "_h_h_e_a_recalc_empty.ttx"))
+ hhea = font["hhea"]
hhea.recalc(font)
self.assertEqual(hhea.advanceWidthMax, 600)
self.assertEqual(hhea.minLeftSideBearing, 0)
@@ -205,4 +212,5 @@ class HheaRecalcTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_h_m_t_x_test.py b/Tests/ttLib/tables/_h_m_t_x_test.py
index 79d0cb7e..f7ab8b1d 100644
--- a/Tests/ttLib/tables/_h_m_t_x_test.py
+++ b/Tests/ttLib/tables/_h_m_t_x_test.py
@@ -8,7 +8,6 @@ import unittest
class HmtxTableTest(unittest.TestCase):
-
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
@@ -23,10 +22,10 @@ class HmtxTableTest(unittest.TestCase):
def makeFont(self, numGlyphs, numberOfMetrics):
font = TTFont()
- maxp = font['maxp'] = newTable('maxp')
+ maxp = font["maxp"] = newTable("maxp")
maxp.numGlyphs = numGlyphs
# from A to ...
- font.glyphOrder = [chr(i) for i in range(65, 65+numGlyphs)]
+ font.glyphOrder = [chr(i) for i in range(65, 65 + numGlyphs)]
headerTag = self.tableClass.headerTag
font[headerTag] = newTable(headerTag)
numberOfMetricsName = self.tableClass.numberOfMetricsName
@@ -40,9 +39,9 @@ class HmtxTableTest(unittest.TestCase):
mtxTable = newTable(self.tag)
mtxTable.decompile(data, font)
- self.assertEqual(mtxTable['A'], (674, -11))
- self.assertEqual(mtxTable['B'], (632, 79))
- self.assertEqual(mtxTable['C'], (710, 54))
+ self.assertEqual(mtxTable["A"], (674, -11))
+ self.assertEqual(mtxTable["B"], (632, 79))
+ self.assertEqual(mtxTable["C"], (710, 54))
def test_decompile_additional_SB(self):
font = self.makeFont(numGlyphs=4, numberOfMetrics=2)
@@ -53,11 +52,11 @@ class HmtxTableTest(unittest.TestCase):
mtxTable = newTable(self.tag)
mtxTable.decompile(data, font)
- self.assertEqual(mtxTable['A'], (674, -11))
- self.assertEqual(mtxTable['B'], (632, 79))
+ self.assertEqual(mtxTable["A"], (674, -11))
+ self.assertEqual(mtxTable["B"], (632, 79))
# all following have same width as the previous
- self.assertEqual(mtxTable['C'], (632, 54))
- self.assertEqual(mtxTable['D'], (632, -4))
+ self.assertEqual(mtxTable["C"], (632, 54))
+ self.assertEqual(mtxTable["D"], (632, -4))
def test_decompile_not_enough_data(self):
font = self.makeFont(numGlyphs=1, numberOfMetrics=1)
@@ -75,20 +74,20 @@ class HmtxTableTest(unittest.TestCase):
with CapturingLogHandler(log, "WARNING") as captor:
mtxTable.decompile(b"\0\0\0\0\0", font)
- self.assertTrue(
- len([r for r in captor.records if msg == r.msg]) == 1)
+ self.assertTrue(len([r for r in captor.records if msg == r.msg]) == 1)
def test_decompile_num_metrics_greater_than_glyphs(self):
font = self.makeFont(numGlyphs=1, numberOfMetrics=2)
mtxTable = newTable(self.tag)
msg = "The %s.%s exceeds the maxp.numGlyphs" % (
- self.tableClass.headerTag, self.tableClass.numberOfMetricsName)
+ self.tableClass.headerTag,
+ self.tableClass.numberOfMetricsName,
+ )
with CapturingLogHandler(log, "WARNING") as captor:
mtxTable.decompile(b"\0\0\0\0", font)
- self.assertTrue(
- len([r for r in captor.records if msg == r.msg]) == 1)
+ self.assertTrue(len([r for r in captor.records if msg == r.msg]) == 1)
def test_decompile_possibly_negative_advance(self):
font = self.makeFont(numGlyphs=1, numberOfMetrics=1)
@@ -101,12 +100,12 @@ class HmtxTableTest(unittest.TestCase):
mtxTable.decompile(data, font)
self.assertTrue(
- len([r for r in captor.records
- if "has a huge advance" in r.msg]) == 1)
+ len([r for r in captor.records if "has a huge advance" in r.msg]) == 1
+ )
def test_decompile_no_header_table(self):
font = TTFont()
- maxp = font['maxp'] = newTable('maxp')
+ maxp = font["maxp"] = newTable("maxp")
maxp.numGlyphs = 3
font.glyphOrder = ["A", "B", "C"]
@@ -122,7 +121,7 @@ class HmtxTableTest(unittest.TestCase):
"A": (400, 30),
"B": (400, 40),
"C": (400, 50),
- }
+ },
)
def test_compile(self):
@@ -130,9 +129,9 @@ class HmtxTableTest(unittest.TestCase):
font = self.makeFont(numGlyphs=3, numberOfMetrics=4)
mtxTable = font[self.tag] = newTable(self.tag)
mtxTable.metrics = {
- 'A': (674, -11),
- 'B': (632, 79),
- 'C': (710, 54),
+ "A": (674, -11),
+ "B": (632, 79),
+ "C": (710, 54),
}
data = mtxTable.compile(font)
@@ -140,17 +139,16 @@ class HmtxTableTest(unittest.TestCase):
self.assertEqual(data, deHexStr("02A2 FFF5 0278 004F 02C6 0036"))
headerTable = font[self.tableClass.headerTag]
- self.assertEqual(
- getattr(headerTable, self.tableClass.numberOfMetricsName), 3)
+ self.assertEqual(getattr(headerTable, self.tableClass.numberOfMetricsName), 3)
def test_compile_additional_SB(self):
font = self.makeFont(numGlyphs=4, numberOfMetrics=1)
mtxTable = font[self.tag] = newTable(self.tag)
mtxTable.metrics = {
- 'A': (632, -11),
- 'B': (632, 79),
- 'C': (632, 54),
- 'D': (632, -4),
+ "A": (632, -11),
+ "B": (632, 79),
+ "C": (632, 54),
+ "D": (632, -4),
}
data = mtxTable.compile(font)
@@ -160,20 +158,23 @@ class HmtxTableTest(unittest.TestCase):
def test_compile_negative_advance(self):
font = self.makeFont(numGlyphs=1, numberOfMetrics=1)
mtxTable = font[self.tag] = newTable(self.tag)
- mtxTable.metrics = {'A': [-1, 0]}
+ mtxTable.metrics = {"A": [-1, 0]}
with CapturingLogHandler(log, "ERROR") as captor:
with self.assertRaisesRegex(TTLibError, "negative advance"):
mtxTable.compile(font)
self.assertTrue(
- len([r for r in captor.records
- if "Glyph 'A' has negative advance" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Glyph 'A' has negative advance" in r.msg]
+ )
+ == 1
+ )
def test_compile_struct_out_of_range(self):
font = self.makeFont(numGlyphs=1, numberOfMetrics=1)
mtxTable = font[self.tag] = newTable(self.tag)
- mtxTable.metrics = {'A': (0xFFFF+1, -0x8001)}
+ mtxTable.metrics = {"A": (0xFFFF + 1, -0x8001)}
with self.assertRaises(struct.error):
mtxTable.compile(font)
@@ -182,9 +183,9 @@ class HmtxTableTest(unittest.TestCase):
font = self.makeFont(numGlyphs=3, numberOfMetrics=2)
mtxTable = font[self.tag] = newTable(self.tag)
mtxTable.metrics = {
- 'A': (0.5, 0.5), # round -> (1, 1)
- 'B': (0.1, 0.9), # round -> (0, 1)
- 'C': (0.1, 0.1), # round -> (0, 0)
+ "A": (0.5, 0.5), # round -> (1, 1)
+ "B": (0.1, 0.9), # round -> (0, 1)
+ "C": (0.1, 0.1), # round -> (0, 0)
}
data = mtxTable.compile(font)
@@ -193,7 +194,7 @@ class HmtxTableTest(unittest.TestCase):
def test_compile_no_header_table(self):
font = TTFont()
- maxp = font['maxp'] = newTable('maxp')
+ maxp = font["maxp"] = newTable("maxp")
maxp.numGlyphs = 3
font.glyphOrder = [chr(i) for i in range(65, 68)]
mtxTable = font[self.tag] = newTable(self.tag)
@@ -212,44 +213,46 @@ class HmtxTableTest(unittest.TestCase):
def test_toXML(self):
font = self.makeFont(numGlyphs=2, numberOfMetrics=2)
mtxTable = font[self.tag] = newTable(self.tag)
- mtxTable.metrics = {'B': (632, 79), 'A': (674, -11)}
+ mtxTable.metrics = {"B": (632, 79), "A": (674, -11)}
self.assertEqual(
getXML(mtxTable.toXML),
- ('<mtx name="A" %s="674" %s="-11"/>\n'
- '<mtx name="B" %s="632" %s="79"/>' % (
- (self.tableClass.advanceName,
- self.tableClass.sideBearingName) * 2)).split('\n'))
+ (
+ '<mtx name="A" %s="674" %s="-11"/>\n'
+ '<mtx name="B" %s="632" %s="79"/>'
+ % ((self.tableClass.advanceName, self.tableClass.sideBearingName) * 2)
+ ).split("\n"),
+ )
def test_fromXML(self):
mtxTable = newTable(self.tag)
for name, attrs, content in parseXML(
- '<mtx name="A" %s="674" %s="-11"/>'
- '<mtx name="B" %s="632" %s="79"/>' % (
- (self.tableClass.advanceName,
- self.tableClass.sideBearingName) * 2)):
+ '<mtx name="A" %s="674" %s="-11"/>'
+ '<mtx name="B" %s="632" %s="79"/>'
+ % ((self.tableClass.advanceName, self.tableClass.sideBearingName) * 2)
+ ):
mtxTable.fromXML(name, attrs, content, ttFont=None)
- self.assertEqual(
- mtxTable.metrics, {'A': (674, -11), 'B': (632, 79)})
+ self.assertEqual(mtxTable.metrics, {"A": (674, -11), "B": (632, 79)})
def test_delitem(self):
mtxTable = newTable(self.tag)
- mtxTable.metrics = {'A': (0, 0)}
+ mtxTable.metrics = {"A": (0, 0)}
- del mtxTable['A']
+ del mtxTable["A"]
- self.assertTrue('A' not in mtxTable.metrics)
+ self.assertTrue("A" not in mtxTable.metrics)
def test_setitem(self):
mtxTable = newTable(self.tag)
- mtxTable.metrics = {'A': (674, -11), 'B': (632, 79)}
- mtxTable['B'] = [0, 0] # list is converted to tuple
+ mtxTable.metrics = {"A": (674, -11), "B": (632, 79)}
+ mtxTable["B"] = [0, 0] # list is converted to tuple
- self.assertEqual(mtxTable.metrics, {'A': (674, -11), 'B': (0, 0)})
+ self.assertEqual(mtxTable.metrics, {"A": (674, -11), "B": (0, 0)})
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_k_e_r_n_test.py b/Tests/ttLib/tables/_k_e_r_n_test.py
index eb48bae6..be0fe9aa 100644
--- a/Tests/ttLib/tables/_k_e_r_n_test.py
+++ b/Tests/ttLib/tables/_k_e_r_n_test.py
@@ -1,6 +1,5 @@
from fontTools.ttLib import newTable
-from fontTools.ttLib.tables._k_e_r_n import (
- KernTable_format_0, KernTable_format_unkown)
+from fontTools.ttLib.tables._k_e_r_n import KernTable_format_0, KernTable_format_unkown
from fontTools.misc.textTools import deHexStr
from fontTools.misc.testTools import FakeFont, getXML, parseXML
import itertools
@@ -8,19 +7,19 @@ import pytest
KERN_VER_0_FMT_0_DATA = deHexStr(
- '0000 ' # 0: version=0
- '0001 ' # 2: nTables=1
- '0000 ' # 4: version=0 (bogus field, unused)
- '0020 ' # 6: length=32
- '00 ' # 8: format=0
- '01 ' # 9: coverage=1
- '0003 ' # 10: nPairs=3
- '000C ' # 12: searchRange=12
- '0001 ' # 14: entrySelector=1
- '0006 ' # 16: rangeShift=6
- '0004 000C FFD8 ' # 18: l=4, r=12, v=-40
- '0004 001C 0028 ' # 24: l=4, r=28, v=40
- '0005 0028 FFCE ' # 30: l=5, r=40, v=-50
+ "0000 " # 0: version=0
+ "0001 " # 2: nTables=1
+ "0000 " # 4: version=0 (bogus field, unused)
+ "0020 " # 6: length=32
+ "00 " # 8: format=0
+ "01 " # 9: coverage=1
+ "0003 " # 10: nPairs=3
+ "000C " # 12: searchRange=12
+ "0001 " # 14: entrySelector=1
+ "0006 " # 16: rangeShift=6
+ "0004 000C FFD8 " # 18: l=4, r=12, v=-40
+ "0004 001C 0028 " # 24: l=4, r=28, v=40
+ "0005 0028 FFCE " # 30: l=5, r=40, v=-50
)
assert len(KERN_VER_0_FMT_0_DATA) == 36
@@ -30,23 +29,23 @@ KERN_VER_0_FMT_0_XML = [
' <pair l="E" r="M" v="-40"/>',
' <pair l="E" r="c" v="40"/>',
' <pair l="F" r="o" v="-50"/>',
- '</kernsubtable>',
+ "</kernsubtable>",
]
KERN_VER_1_FMT_0_DATA = deHexStr(
- '0001 0000 ' # 0: version=1
- '0000 0001 ' # 4: nTables=1
- '0000 0022 ' # 8: length=34
- '00 ' # 12: coverage=0
- '00 ' # 13: format=0
- '0000 ' # 14: tupleIndex=0
- '0003 ' # 16: nPairs=3
- '000C ' # 18: searchRange=12
- '0001 ' # 20: entrySelector=1
- '0006 ' # 22: rangeShift=6
- '0004 000C FFD8 ' # 24: l=4, r=12, v=-40
- '0004 001C 0028 ' # 30: l=4, r=28, v=40
- '0005 0028 FFCE ' # 36: l=5, r=40, v=-50
+ "0001 0000 " # 0: version=1
+ "0000 0001 " # 4: nTables=1
+ "0000 0022 " # 8: length=34
+ "00 " # 12: coverage=0
+ "00 " # 13: format=0
+ "0000 " # 14: tupleIndex=0
+ "0003 " # 16: nPairs=3
+ "000C " # 18: searchRange=12
+ "0001 " # 20: entrySelector=1
+ "0006 " # 22: rangeShift=6
+ "0004 000C FFD8 " # 24: l=4, r=12, v=-40
+ "0004 001C 0028 " # 30: l=4, r=28, v=40
+ "0005 0028 FFCE " # 36: l=5, r=40, v=-50
)
assert len(KERN_VER_1_FMT_0_DATA) == 42
@@ -56,22 +55,22 @@ KERN_VER_1_FMT_0_XML = [
' <pair l="E" r="M" v="-40"/>',
' <pair l="E" r="c" v="40"/>',
' <pair l="F" r="o" v="-50"/>',
- '</kernsubtable>',
+ "</kernsubtable>",
]
KERN_VER_0_FMT_UNKNOWN_DATA = deHexStr(
- '0000 ' # 0: version=0
- '0002 ' # 2: nTables=2
- '0000 ' # 4: version=0
- '000A ' # 6: length=10
- '04 ' # 8: format=4 (format 4 doesn't exist)
- '01 ' # 9: coverage=1
- '1234 5678 ' # 10: garbage...
- '0000 ' # 14: version=0
- '000A ' # 16: length=10
- '05 ' # 18: format=5 (format 5 doesn't exist)
- '01 ' # 19: coverage=1
- '9ABC DEF0 ' # 20: garbage...
+ "0000 " # 0: version=0
+ "0002 " # 2: nTables=2
+ "0000 " # 4: version=0
+ "000A " # 6: length=10
+ "04 " # 8: format=4 (format 4 doesn't exist)
+ "01 " # 9: coverage=1
+ "1234 5678 " # 10: garbage...
+ "0000 " # 14: version=0
+ "000A " # 16: length=10
+ "05 " # 18: format=5 (format 5 doesn't exist)
+ "01 " # 19: coverage=1
+ "9ABC DEF0 " # 20: garbage...
)
assert len(KERN_VER_0_FMT_UNKNOWN_DATA) == 24
@@ -79,29 +78,29 @@ KERN_VER_0_FMT_UNKNOWN_XML = [
'<version value="0"/>',
'<kernsubtable format="4">',
" <!-- unknown 'kern' subtable format -->",
- ' 0000000A 04011234',
- ' 5678 ',
- '</kernsubtable>',
+ " 0000000A 04011234",
+ " 5678 ",
+ "</kernsubtable>",
'<kernsubtable format="5">',
"<!-- unknown 'kern' subtable format -->",
- ' 0000000A 05019ABC',
- ' DEF0 ',
- '</kernsubtable>',
+ " 0000000A 05019ABC",
+ " DEF0 ",
+ "</kernsubtable>",
]
KERN_VER_1_FMT_UNKNOWN_DATA = deHexStr(
- '0001 0000 ' # 0: version=1
- '0000 0002 ' # 4: nTables=2
- '0000 000C ' # 8: length=12
- '00 ' # 12: coverage=0
- '04 ' # 13: format=4 (format 4 doesn't exist)
- '0000 ' # 14: tupleIndex=0
- '1234 5678' # 16: garbage...
- '0000 000C ' # 20: length=12
- '00 ' # 24: coverage=0
- '05 ' # 25: format=5 (format 5 doesn't exist)
- '0000 ' # 26: tupleIndex=0
- '9ABC DEF0 ' # 28: garbage...
+ "0001 0000 " # 0: version=1
+ "0000 0002 " # 4: nTables=2
+ "0000 000C " # 8: length=12
+ "00 " # 12: coverage=0
+ "04 " # 13: format=4 (format 4 doesn't exist)
+ "0000 " # 14: tupleIndex=0
+ "1234 5678" # 16: garbage...
+ "0000 000C " # 20: length=12
+ "00 " # 24: coverage=0
+ "05 " # 25: format=5 (format 5 doesn't exist)
+ "0000 " # 26: tupleIndex=0
+ "9ABC DEF0 " # 28: garbage...
)
assert len(KERN_VER_1_FMT_UNKNOWN_DATA) == 32
@@ -109,37 +108,39 @@ KERN_VER_1_FMT_UNKNOWN_XML = [
'<version value="1"/>',
'<kernsubtable format="4">',
" <!-- unknown 'kern' subtable format -->",
- ' 0000000C 00040000',
- ' 12345678 ',
- '</kernsubtable>',
+ " 0000000C 00040000",
+ " 12345678 ",
+ "</kernsubtable>",
'<kernsubtable format="5">',
" <!-- unknown 'kern' subtable format -->",
- ' 0000000C 00050000',
- ' 9ABCDEF0 ',
- '</kernsubtable>',
+ " 0000000C 00050000",
+ " 9ABCDEF0 ",
+ "</kernsubtable>",
]
KERN_VER_0_FMT_0_OVERFLOWING_DATA = deHexStr(
- '0000 ' # 0: version=0
- '0001 ' # 2: nTables=1
- '0000 ' # 4: version=0 (bogus field, unused)
- '0274 ' # 6: length=628 (bogus value for 66164 % 0x10000)
- '00 ' # 8: format=0
- '01 ' # 9: coverage=1
- '2B11 ' # 10: nPairs=11025
- 'C000 ' # 12: searchRange=49152
- '000D ' # 14: entrySelector=13
- '4266 ' # 16: rangeShift=16998
-) + deHexStr(' '.join(
- '%04X %04X %04X' % (a, b, 0)
- for (a, b) in itertools.product(range(105), repeat=2)
-))
+ "0000 " # 0: version=0
+ "0001 " # 2: nTables=1
+ "0000 " # 4: version=0 (bogus field, unused)
+ "0274 " # 6: length=628 (bogus value for 66164 % 0x10000)
+ "00 " # 8: format=0
+ "01 " # 9: coverage=1
+ "2B11 " # 10: nPairs=11025
+ "C000 " # 12: searchRange=49152
+ "000D " # 14: entrySelector=13
+ "4266 " # 16: rangeShift=16998
+) + deHexStr(
+ " ".join(
+ "%04X %04X %04X" % (a, b, 0)
+ for (a, b) in itertools.product(range(105), repeat=2)
+ )
+)
@pytest.fixture
def font():
- return FakeFont(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"))
+ return FakeFont(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz"))
+
@pytest.fixture
def overflowing_font():
@@ -147,14 +148,13 @@ def overflowing_font():
class KernTableTest(object):
-
@pytest.mark.parametrize(
"data, version",
[
(KERN_VER_0_FMT_0_DATA, 0),
(KERN_VER_1_FMT_0_DATA, 1.0),
],
- ids=["version_0", "version_1"]
+ ids=["version_0", "version_1"],
)
def test_decompile_single_format_0(self, data, font, version):
kern = newTable("kern")
@@ -171,11 +171,7 @@ class KernTableTest(object):
assert st.coverage == (0 if st.apple else 1)
assert st.tupleIndex == (0 if st.apple else None)
assert len(st.kernTable) == 3
- assert st.kernTable == {
- ('E', 'M'): -40,
- ('E', 'c'): 40,
- ('F', 'o'): -50
- }
+ assert st.kernTable == {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50}
@pytest.mark.parametrize(
"version, expected",
@@ -183,7 +179,7 @@ class KernTableTest(object):
(0, KERN_VER_0_FMT_0_DATA),
(1.0, KERN_VER_1_FMT_0_DATA),
],
- ids=["version_0", "version_1"]
+ ids=["version_0", "version_1"],
)
def test_compile_single_format_0(self, font, version, expected):
kern = newTable("kern")
@@ -191,13 +187,9 @@ class KernTableTest(object):
apple = version == 1.0
st = KernTable_format_0(apple)
kern.kernTables = [st]
- st.coverage = (0 if apple else 1)
+ st.coverage = 0 if apple else 1
st.tupleIndex = 0 if apple else None
- st.kernTable = {
- ('E', 'M'): -40,
- ('E', 'c'): 40,
- ('F', 'o'): -50
- }
+ st.kernTable = {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50}
data = kern.compile(font)
assert data == expected
@@ -207,7 +199,7 @@ class KernTableTest(object):
(KERN_VER_0_FMT_0_XML, 0),
(KERN_VER_1_FMT_0_XML, 1.0),
],
- ids=["version_0", "version_1"]
+ ids=["version_0", "version_1"],
)
def test_fromXML_single_format_0(self, xml, font, version):
kern = newTable("kern")
@@ -223,11 +215,7 @@ class KernTableTest(object):
assert st.coverage == (0 if st.apple else 1)
assert st.tupleIndex == (0 if st.apple else None)
assert len(st.kernTable) == 3
- assert st.kernTable == {
- ('E', 'M'): -40,
- ('E', 'c'): 40,
- ('F', 'o'): -50
- }
+ assert st.kernTable == {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50}
@pytest.mark.parametrize(
"version, expected",
@@ -235,7 +223,7 @@ class KernTableTest(object):
(0, KERN_VER_0_FMT_0_XML),
(1.0, KERN_VER_1_FMT_0_XML),
],
- ids=["version_0", "version_1"]
+ ids=["version_0", "version_1"],
)
def test_toXML_single_format_0(self, font, version, expected):
kern = newTable("kern")
@@ -245,11 +233,7 @@ class KernTableTest(object):
kern.kernTables = [st]
st.coverage = 0 if apple else 1
st.tupleIndex = 0 if apple else None
- st.kernTable = {
- ('E', 'M'): -40,
- ('E', 'c'): 40,
- ('F', 'o'): -50
- }
+ st.kernTable = {("E", "M"): -40, ("E", "c"): 40, ("F", "o"): -50}
xml = getXML(kern.toXML, font)
assert xml == expected
@@ -259,10 +243,11 @@ class KernTableTest(object):
(KERN_VER_0_FMT_UNKNOWN_DATA, 0, 4, 10),
(KERN_VER_1_FMT_UNKNOWN_DATA, 1.0, 8, 12),
],
- ids=["version_0", "version_1"]
+ ids=["version_0", "version_1"],
)
def test_decompile_format_unknown(
- self, data, font, version, header_length, st_length):
+ self, data, font, version, header_length, st_length
+ ):
kern = newTable("kern")
kern.decompile(data, font)
@@ -285,7 +270,7 @@ class KernTableTest(object):
(0, 10, KERN_VER_0_FMT_UNKNOWN_DATA),
(1.0, 12, KERN_VER_1_FMT_UNKNOWN_DATA),
],
- ids=["version_0", "version_1"]
+ ids=["version_0", "version_1"],
)
def test_compile_format_unknown(self, version, st_length, expected):
kern = newTable("kern")
@@ -296,13 +281,13 @@ class KernTableTest(object):
if version > 0:
coverage = 0
header_fmt = deHexStr(
- "%08X %02X %02X %04X" % (
- st_length, coverage, unknown_fmt, 0))
+ "%08X %02X %02X %04X" % (st_length, coverage, unknown_fmt, 0)
+ )
else:
coverage = 1
header_fmt = deHexStr(
- "%04X %04X %02X %02X" % (
- 0, st_length, unknown_fmt, coverage))
+ "%04X %04X %02X %02X" % (0, st_length, unknown_fmt, coverage)
+ )
st = KernTable_format_unkown(unknown_fmt)
st.data = header_fmt + deHexStr(kern_data)
kern.kernTables.append(st)
@@ -316,7 +301,7 @@ class KernTableTest(object):
(KERN_VER_0_FMT_UNKNOWN_XML, 0, 10),
(KERN_VER_1_FMT_UNKNOWN_XML, 1.0, 12),
],
- ids=["version_0", "version_1"]
+ ids=["version_0", "version_1"],
)
def test_fromXML_format_unknown(self, xml, font, version, st_length):
kern = newTable("kern")
@@ -334,8 +319,7 @@ class KernTableTest(object):
assert st1.format == 5
assert len(st1.data) == st_length
- @pytest.mark.parametrize(
- "version", [0, 1.0], ids=["version_0", "version_1"])
+ @pytest.mark.parametrize("version", [0, 1.0], ids=["version_0", "version_1"])
def test_toXML_format_unknown(self, font, version):
kern = newTable("kern")
kern.version = version
@@ -348,9 +332,9 @@ class KernTableTest(object):
assert xml == [
'<version value="%s"/>' % version,
'<kernsubtable format="4">',
- ' <!-- unknown \'kern\' subtable format -->',
- ' 41424344 ',
- '</kernsubtable>',
+ " <!-- unknown 'kern' subtable format -->",
+ " 41424344 ",
+ "</kernsubtable>",
]
def test_getkern(self):
@@ -371,15 +355,32 @@ class KernTableTest(object):
class KernTable_format_0_Test(object):
-
def test_decompileBadGlyphId(self, font):
subtable = KernTable_format_0()
subtable.decompile(
- b'\x00' + b'\x00' + b'\x00' + b'\x1a' + b'\x00' + b'\x00' +
- b'\x00' + b'\x02' + b'\x00' * 6 +
- b'\x00' + b'\x01' + b'\x00' + b'\x03' + b'\x00' + b'\x01' +
- b'\x00' + b'\x01' + b'\xFF' + b'\xFF' + b'\x00' + b'\x02',
- font)
+ b"\x00"
+ + b"\x00"
+ + b"\x00"
+ + b"\x1a"
+ + b"\x00"
+ + b"\x00"
+ + b"\x00"
+ + b"\x02"
+ + b"\x00" * 6
+ + b"\x00"
+ + b"\x01"
+ + b"\x00"
+ + b"\x03"
+ + b"\x00"
+ + b"\x01"
+ + b"\x00"
+ + b"\x01"
+ + b"\xFF"
+ + b"\xFF"
+ + b"\x00"
+ + b"\x02",
+ font,
+ )
assert subtable[("B", "D")] == 1
assert subtable[("B", "glyph65535")] == 2
@@ -392,9 +393,7 @@ class KernTable_format_0_Test(object):
st.coverage = 1
st.tupleIndex = None
st.kernTable = {
- (a, b): 0
- for (a, b) in itertools.product(
- font.getGlyphOrder(), repeat=2)
+ (a, b): 0 for (a, b) in itertools.product(font.getGlyphOrder(), repeat=2)
}
assert len(st.kernTable) == 11025
data = kern.compile(font)
@@ -408,12 +407,11 @@ class KernTable_format_0_Test(object):
st = kern.kernTables[0]
assert st.kernTable == {
- (a, b): 0
- for (a, b) in itertools.product(
- font.getGlyphOrder(), repeat=2)
+ (a, b): 0 for (a, b) in itertools.product(font.getGlyphOrder(), repeat=2)
}
if __name__ == "__main__":
import sys
+
sys.exit(pytest.main(sys.argv))
diff --git a/Tests/ttLib/tables/_l_c_a_r_test.py b/Tests/ttLib/tables/_l_c_a_r_test.py
index 5837a07a..79a80249 100644
--- a/Tests/ttLib/tables/_l_c_a_r_test.py
+++ b/Tests/ttLib/tables/_l_c_a_r_test.py
@@ -7,101 +7,99 @@ import unittest
# Example: Format 0 Ligature Caret Table
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6lcar.html
LCAR_FORMAT_0_DATA = deHexStr(
- '0001 0000 0000 ' # 0: Version=1.0, Format=0
- '0006 0004 0002 ' # 6: LookupFormat=6, UnitSize=4, NUnits=2
- '0008 0001 0000 ' # 12: SearchRange=8, EntrySelector=1, RangeShift=0
- '0001 001E ' # 18: Glyph=1 (f_r), OffsetOfLigCaretEntry=30
- '0003 0022 ' # 22: Glyph=3 (f_f_l), OffsetOfLigCaretEntry=34
- 'FFFF 0000 ' # 26: Glyph=<end>, OffsetOfLigCaretEntry=0
- '0001 00DC ' # 30: DivisionPointCount=1, DivisionPoint=[220]
- '0002 00EF 01D8 ' # 34: DivisionPointCount=2, DivisionPoint=[239, 475]
-) # 40: <end>
-assert(len(LCAR_FORMAT_0_DATA) == 40)
+ "0001 0000 0000 " # 0: Version=1.0, Format=0
+ "0006 0004 0002 " # 6: LookupFormat=6, UnitSize=4, NUnits=2
+ "0008 0001 0000 " # 12: SearchRange=8, EntrySelector=1, RangeShift=0
+ "0001 001E " # 18: Glyph=1 (f_r), OffsetOfLigCaretEntry=30
+ "0003 0022 " # 22: Glyph=3 (f_f_l), OffsetOfLigCaretEntry=34
+ "FFFF 0000 " # 26: Glyph=<end>, OffsetOfLigCaretEntry=0
+ "0001 00DC " # 30: DivisionPointCount=1, DivisionPoint=[220]
+ "0002 00EF 01D8 " # 34: DivisionPointCount=2, DivisionPoint=[239, 475]
+) # 40: <end>
+assert len(LCAR_FORMAT_0_DATA) == 40
LCAR_FORMAT_0_XML = [
'<Version value="0x00010000"/>',
'<LigatureCarets Format="0">',
- ' <Carets>',
+ " <Carets>",
' <Lookup glyph="f_f_l">',
- ' <!-- DivsionPointCount=2 -->',
+ " <!-- DivsionPointCount=2 -->",
' <DivisionPoint index="0" value="239"/>',
' <DivisionPoint index="1" value="472"/>',
- ' </Lookup>',
+ " </Lookup>",
' <Lookup glyph="f_r">',
- ' <!-- DivsionPointCount=1 -->',
+ " <!-- DivsionPointCount=1 -->",
' <DivisionPoint index="0" value="220"/>',
- ' </Lookup>',
- ' </Carets>',
- '</LigatureCarets>',
+ " </Lookup>",
+ " </Carets>",
+ "</LigatureCarets>",
]
# Example: Format 1 Ligature Caret Table
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6lcar.html
LCAR_FORMAT_1_DATA = deHexStr(
- '0001 0000 0001 ' # 0: Version=1.0, Format=1
- '0006 0004 0002 ' # 6: LookupFormat=6, UnitSize=4, NUnits=2
- '0008 0001 0000 ' # 12: SearchRange=8, EntrySelector=1, RangeShift=0
- '0001 001E ' # 18: Glyph=1 (f_r), OffsetOfLigCaretEntry=30
- '0003 0022 ' # 22: Glyph=3 (f_f_l), OffsetOfLigCaretEntry=34
- 'FFFF 0000 ' # 26: Glyph=<end>, OffsetOfLigCaretEntry=0
- '0001 0032 ' # 30: DivisionPointCount=1, DivisionPoint=[50]
- '0002 0037 004B ' # 34: DivisionPointCount=2, DivisionPoint=[55, 75]
-) # 40: <end>
-assert(len(LCAR_FORMAT_1_DATA) == 40)
+ "0001 0000 0001 " # 0: Version=1.0, Format=1
+ "0006 0004 0002 " # 6: LookupFormat=6, UnitSize=4, NUnits=2
+ "0008 0001 0000 " # 12: SearchRange=8, EntrySelector=1, RangeShift=0
+ "0001 001E " # 18: Glyph=1 (f_r), OffsetOfLigCaretEntry=30
+ "0003 0022 " # 22: Glyph=3 (f_f_l), OffsetOfLigCaretEntry=34
+ "FFFF 0000 " # 26: Glyph=<end>, OffsetOfLigCaretEntry=0
+ "0001 0032 " # 30: DivisionPointCount=1, DivisionPoint=[50]
+ "0002 0037 004B " # 34: DivisionPointCount=2, DivisionPoint=[55, 75]
+) # 40: <end>
+assert len(LCAR_FORMAT_1_DATA) == 40
LCAR_FORMAT_1_XML = [
'<Version value="0x00010000"/>',
'<LigatureCarets Format="1">',
- ' <Carets>',
+ " <Carets>",
' <Lookup glyph="f_f_l">',
- ' <!-- DivsionPointCount=2 -->',
+ " <!-- DivsionPointCount=2 -->",
' <DivisionPoint index="0" value="55"/>',
' <DivisionPoint index="1" value="75"/>',
- ' </Lookup>',
+ " </Lookup>",
' <Lookup glyph="f_r">',
- ' <!-- DivsionPointCount=1 -->',
+ " <!-- DivsionPointCount=1 -->",
' <DivisionPoint index="0" value="50"/>',
- ' </Lookup>',
- ' </Carets>',
- '</LigatureCarets>',
+ " </Lookup>",
+ " </Carets>",
+ "</LigatureCarets>",
]
class LCARTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.notdef', 'f_r', 'X', 'f_f_l'])
+ cls.font = FakeFont([".notdef", "f_r", "X", "f_f_l"])
def test_decompile_toXML_format0(self):
- table = newTable('lcar')
+ table = newTable("lcar")
table.decompile(LCAR_FORMAT_0_DATA, self.font)
self.assertEqual(getXML(table.toXML), LCAR_FORMAT_0_XML)
def test_compile_fromXML_format0(self):
- table = newTable('lcar')
+ table = newTable("lcar")
for name, attrs, content in parseXML(LCAR_FORMAT_0_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(LCAR_FORMAT_0_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(LCAR_FORMAT_0_DATA))
def test_decompile_toXML_format1(self):
- table = newTable('lcar')
+ table = newTable("lcar")
table.decompile(LCAR_FORMAT_1_DATA, self.font)
self.assertEqual(getXML(table.toXML), LCAR_FORMAT_1_XML)
def test_compile_fromXML_format1(self):
- table = newTable('lcar')
+ table = newTable("lcar")
for name, attrs, content in parseXML(LCAR_FORMAT_1_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(LCAR_FORMAT_1_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(LCAR_FORMAT_1_DATA))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_l_t_a_g_test.py b/Tests/ttLib/tables/_l_t_a_g_test.py
index 29119903..aa73cde6 100644
--- a/Tests/ttLib/tables/_l_t_a_g_test.py
+++ b/Tests/ttLib/tables/_l_t_a_g_test.py
@@ -7,55 +7,64 @@ from fontTools.ttLib import newTable
class Test_l_t_a_g(unittest.TestCase):
+ DATA_ = (
+ struct.pack(b">LLLHHHHHH", 1, 0, 3, 24 + 0, 2, 24 + 2, 7, 24 + 2, 2)
+ + b"enzh-Hant"
+ )
+ TAGS_ = ["en", "zh-Hant", "zh"]
- DATA_ = struct.pack(b">LLLHHHHHH", 1, 0, 3, 24 + 0, 2, 24 + 2, 7, 24 + 2, 2) + b"enzh-Hant"
- TAGS_ = ["en", "zh-Hant", "zh"]
-
- def test_addTag(self):
- table = newTable("ltag")
- self.assertEqual(table.addTag("de-CH"), 0)
- self.assertEqual(table.addTag("gsw-LI"), 1)
- self.assertEqual(table.addTag("de-CH"), 0)
- self.assertEqual(table.tags, ["de-CH", "gsw-LI"])
-
- def test_decompile_compile(self):
- table = newTable("ltag")
- table.decompile(self.DATA_, ttFont=None)
- self.assertEqual(1, table.version)
- self.assertEqual(0, table.flags)
- self.assertEqual(self.TAGS_, table.tags)
- compiled = table.compile(ttFont=None)
- self.assertEqual(self.DATA_, compiled)
- self.assertIsInstance(compiled, bytes)
-
- def test_fromXML(self):
- table = newTable("ltag")
- for name, attrs, content in parseXML(
- '<version value="1"/>'
- '<flags value="777"/>'
- '<LanguageTag tag="sr-Latn"/>'
- '<LanguageTag tag="fa"/>'):
- table.fromXML(name, attrs, content, ttFont=None)
- self.assertEqual(1, table.version)
- self.assertEqual(777, table.flags)
- self.assertEqual(["sr-Latn", "fa"], table.tags)
-
- def test_toXML(self):
- writer = XMLWriter(BytesIO())
- table = newTable("ltag")
- table.decompile(self.DATA_, ttFont=None)
- table.toXML(writer, ttFont=None)
- expected = "\n".join([
- '<?xml version="1.0" encoding="UTF-8"?>',
- '<version value="1"/>',
- '<flags value="0"/>',
- '<LanguageTag tag="en"/>',
- '<LanguageTag tag="zh-Hant"/>',
- '<LanguageTag tag="zh"/>'
- ]) + "\n"
- self.assertEqual(expected.encode("utf_8"), writer.file.getvalue())
-
-
-if __name__ == '__main__':
- import sys
- sys.exit(unittest.main())
+ def test_addTag(self):
+ table = newTable("ltag")
+ self.assertEqual(table.addTag("de-CH"), 0)
+ self.assertEqual(table.addTag("gsw-LI"), 1)
+ self.assertEqual(table.addTag("de-CH"), 0)
+ self.assertEqual(table.tags, ["de-CH", "gsw-LI"])
+
+ def test_decompile_compile(self):
+ table = newTable("ltag")
+ table.decompile(self.DATA_, ttFont=None)
+ self.assertEqual(1, table.version)
+ self.assertEqual(0, table.flags)
+ self.assertEqual(self.TAGS_, table.tags)
+ compiled = table.compile(ttFont=None)
+ self.assertEqual(self.DATA_, compiled)
+ self.assertIsInstance(compiled, bytes)
+
+ def test_fromXML(self):
+ table = newTable("ltag")
+ for name, attrs, content in parseXML(
+ '<version value="1"/>'
+ '<flags value="777"/>'
+ '<LanguageTag tag="sr-Latn"/>'
+ '<LanguageTag tag="fa"/>'
+ ):
+ table.fromXML(name, attrs, content, ttFont=None)
+ self.assertEqual(1, table.version)
+ self.assertEqual(777, table.flags)
+ self.assertEqual(["sr-Latn", "fa"], table.tags)
+
+ def test_toXML(self):
+ writer = XMLWriter(BytesIO())
+ table = newTable("ltag")
+ table.decompile(self.DATA_, ttFont=None)
+ table.toXML(writer, ttFont=None)
+ expected = (
+ "\n".join(
+ [
+ '<?xml version="1.0" encoding="UTF-8"?>',
+ '<version value="1"/>',
+ '<flags value="0"/>',
+ '<LanguageTag tag="en"/>',
+ '<LanguageTag tag="zh-Hant"/>',
+ '<LanguageTag tag="zh"/>',
+ ]
+ )
+ + "\n"
+ )
+ self.assertEqual(expected.encode("utf_8"), writer.file.getvalue())
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_m_e_t_a_test.py b/Tests/ttLib/tables/_m_e_t_a_test.py
index f05ff576..e96c492c 100644
--- a/Tests/ttLib/tables/_m_e_t_a_test.py
+++ b/Tests/ttLib/tables/_m_e_t_a_test.py
@@ -11,7 +11,8 @@ import unittest
# and shortened the payload.
META_DATA = deHexStr(
"00 00 00 01 00 00 00 00 00 00 00 1C 00 00 00 01 "
- "54 45 53 54 00 00 00 1C 00 00 00 04 CA FE BE EF")
+ "54 45 53 54 00 00 00 1C 00 00 00 04 CA FE BE EF"
+)
# The 'dlng' and 'slng' tag with text data containing "augmented" BCP 47
# comma-separated or comma-space-separated tags. These should be UTF-8 encoded
@@ -21,7 +22,9 @@ META_DATA_TEXT = deHexStr(
"64 6C 6E 67 00 00 00 28 00 00 00 0E 73 6C 6E 67 "
"00 00 00 36 00 00 00 0E 4C 61 74 6E 2C 47 72 65 "
"6B 2C 43 79 72 6C 4C 61 74 6E 2C 47 72 65 6B 2C "
- "43 79 72 6C")
+ "43 79 72 6C"
+)
+
class MetaTableTest(unittest.TestCase):
def test_decompile(self):
@@ -37,13 +40,14 @@ class MetaTableTest(unittest.TestCase):
def test_decompile_text(self):
table = table__m_e_t_a()
table.decompile(META_DATA_TEXT, ttFont={"meta": table})
- self.assertEqual({"dlng": u"Latn,Grek,Cyrl",
- "slng": u"Latn,Grek,Cyrl"}, table.data)
+ self.assertEqual(
+ {"dlng": "Latn,Grek,Cyrl", "slng": "Latn,Grek,Cyrl"}, table.data
+ )
def test_compile_text(self):
table = table__m_e_t_a()
- table.data["dlng"] = u"Latn,Grek,Cyrl"
- table.data["slng"] = u"Latn,Grek,Cyrl"
+ table.data["dlng"] = "Latn,Grek,Cyrl"
+ table.data["slng"] = "Latn,Grek,Cyrl"
self.assertEqual(META_DATA_TEXT, table.compile(ttFont={"meta": table}))
def test_toXML(self):
@@ -52,11 +56,10 @@ class MetaTableTest(unittest.TestCase):
writer = XMLWriter(BytesIO())
table.toXML(writer, {"meta": table})
xml = writer.file.getvalue().decode("utf-8")
- self.assertEqual([
- '<hexdata tag="TEST">',
- 'cafebeef',
- '</hexdata>'
- ], [line.strip() for line in xml.splitlines()][1:])
+ self.assertEqual(
+ ['<hexdata tag="TEST">', "cafebeef", "</hexdata>"],
+ [line.strip() for line in xml.splitlines()][1:],
+ )
def test_toXML_ascii_data(self):
table = table__m_e_t_a()
@@ -64,44 +67,45 @@ class MetaTableTest(unittest.TestCase):
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:])
+ 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(
- '<hexdata tag="TEST">'
- ' cafebeef'
- '</hexdata>'):
+ '<hexdata tag="TEST">' " cafebeef" "</hexdata>"
+ ):
table.fromXML(name, attrs, content, ttFont=None)
self.assertEqual({"TEST": b"\xCA\xFE\xBE\xEF"}, table.data)
def test_toXML_text(self):
table = table__m_e_t_a()
- table.data["dlng"] = u"Latn,Grek,Cyrl"
+ table.data["dlng"] = "Latn,Grek,Cyrl"
writer = XMLWriter(BytesIO())
table.toXML(writer, {"meta": table})
xml = writer.file.getvalue().decode("utf-8")
- self.assertEqual([
- '<text tag="dlng">',
- 'Latn,Grek,Cyrl',
- '</text>'
- ], [line.strip() for line in xml.splitlines()][1:])
+ self.assertEqual(
+ ['<text tag="dlng">', "Latn,Grek,Cyrl", "</text>"],
+ [line.strip() for line in xml.splitlines()][1:],
+ )
def test_fromXML_text(self):
table = table__m_e_t_a()
for name, attrs, content in parseXML(
- '<text tag="dlng">'
- ' Latn,Grek,Cyrl'
- '</text>'):
+ '<text tag="dlng">' " Latn,Grek,Cyrl" "</text>"
+ ):
table.fromXML(name, attrs, content, ttFont=None)
- self.assertEqual({"dlng": u"Latn,Grek,Cyrl"}, table.data)
+ self.assertEqual({"dlng": "Latn,Grek,Cyrl"}, table.data)
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_m_o_r_t_test.py b/Tests/ttLib/tables/_m_o_r_t_test.py
index 3e7169be..fb711158 100644
--- a/Tests/ttLib/tables/_m_o_r_t_test.py
+++ b/Tests/ttLib/tables/_m_o_r_t_test.py
@@ -15,99 +15,100 @@ import unittest
# character; the non-contiguous glyph range for the AAT lookup makes
# format 6 to be most compact.
MORT_NONCONTEXTUAL_DATA = deHexStr(
- '0001 0000 ' # 0: Version=1.0
- '0000 0001 ' # 4: MorphChainCount=1
- '0000 0001 ' # 8: DefaultFlags=1
- '0000 0050 ' # 12: StructLength=80
- '0003 0001 ' # 16: MorphFeatureCount=3, MorphSubtableCount=1
- '0004 0000 ' # 20: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
- '0000 0001 ' # 24: Feature[0].EnableFlags=0x00000001
- 'FFFF FFFF ' # 28: Feature[0].DisableFlags=0xFFFFFFFF
- '0004 0001 ' # 32: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
- '0000 0000 ' # 36: Feature[1].EnableFlags=0x00000000
- 'FFFF FFFE ' # 40: Feature[1].DisableFlags=0xFFFFFFFE
- '0000 0001 ' # 44: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
- '0000 0000 ' # 48: Feature[2].EnableFlags=0 (required for last feature)
- '0000 0000 ' # 52: Feature[2].EnableFlags=0 (required for last feature)
- '0020 ' # 56: Subtable[0].StructLength=32
- '80 ' # 58: Subtable[0].CoverageFlags=0x80
- '04 ' # 59: Subtable[0].MorphType=4/NoncontextualMorph
- '0000 0001 ' # 60: Subtable[0].SubFeatureFlags=0x1
- '0006 0004 ' # 64: LookupFormat=6, UnitSize=4
- '0002 0008 ' # 68: NUnits=2, SearchRange=8
- '0001 0000 ' # 72: EntrySelector=1, RangeShift=0
- '000B 0087 ' # 76: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
- '000D 0088 ' # 80: Glyph=13 (parenright); Value=136 (parenright.vertical)
- 'FFFF 0000 ' # 84: Glyph=<end>; Value=0
-) # 88: <end>
+ "0001 0000 " # 0: Version=1.0
+ "0000 0001 " # 4: MorphChainCount=1
+ "0000 0001 " # 8: DefaultFlags=1
+ "0000 0050 " # 12: StructLength=80
+ "0003 0001 " # 16: MorphFeatureCount=3, MorphSubtableCount=1
+ "0004 0000 " # 20: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
+ "0000 0001 " # 24: Feature[0].EnableFlags=0x00000001
+ "FFFF FFFF " # 28: Feature[0].DisableFlags=0xFFFFFFFF
+ "0004 0001 " # 32: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
+ "0000 0000 " # 36: Feature[1].EnableFlags=0x00000000
+ "FFFF FFFE " # 40: Feature[1].DisableFlags=0xFFFFFFFE
+ "0000 0001 " # 44: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
+ "0000 0000 " # 48: Feature[2].EnableFlags=0 (required for last feature)
+ "0000 0000 " # 52: Feature[2].EnableFlags=0 (required for last feature)
+ "0020 " # 56: Subtable[0].StructLength=32
+ "80 " # 58: Subtable[0].CoverageFlags=0x80
+ "04 " # 59: Subtable[0].MorphType=4/NoncontextualMorph
+ "0000 0001 " # 60: Subtable[0].SubFeatureFlags=0x1
+ "0006 0004 " # 64: LookupFormat=6, UnitSize=4
+ "0002 0008 " # 68: NUnits=2, SearchRange=8
+ "0001 0000 " # 72: EntrySelector=1, RangeShift=0
+ "000B 0087 " # 76: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
+ "000D 0088 " # 80: Glyph=13 (parenright); Value=136 (parenright.vertical)
+ "FFFF 0000 " # 84: Glyph=<end>; Value=0
+) # 88: <end>
assert len(MORT_NONCONTEXTUAL_DATA) == 88
MORT_NONCONTEXTUAL_XML = [
'<Version value="0x00010000"/>',
- '<!-- MorphChainCount=1 -->',
+ "<!-- MorphChainCount=1 -->",
'<MorphChain index="0">',
' <DefaultFlags value="0x00000001"/>',
- ' <!-- StructLength=80 -->',
- ' <!-- MorphFeatureCount=3 -->',
- ' <!-- MorphSubtableCount=1 -->',
+ " <!-- StructLength=80 -->",
+ " <!-- MorphFeatureCount=3 -->",
+ " <!-- MorphSubtableCount=1 -->",
' <MorphFeature index="0">',
' <FeatureType value="4"/>',
' <FeatureSetting value="0"/>',
' <EnableFlags value="0x00000001"/>',
' <DisableFlags value="0xFFFFFFFF"/>',
- ' </MorphFeature>',
+ " </MorphFeature>",
' <MorphFeature index="1">',
' <FeatureType value="4"/>',
' <FeatureSetting value="1"/>',
' <EnableFlags value="0x00000000"/>',
' <DisableFlags value="0xFFFFFFFE"/>',
- ' </MorphFeature>',
+ " </MorphFeature>",
' <MorphFeature index="2">',
' <FeatureType value="0"/>',
' <FeatureSetting value="1"/>',
' <EnableFlags value="0x00000000"/>',
' <DisableFlags value="0x00000000"/>',
- ' </MorphFeature>',
+ " </MorphFeature>",
' <MorphSubtable index="0">',
- ' <!-- StructLength=32 -->',
+ " <!-- StructLength=32 -->",
' <CoverageFlags value="128"/>',
- ' <!-- MorphType=4 -->',
+ " <!-- MorphType=4 -->",
' <SubFeatureFlags value="0x00000001"/>',
- ' <NoncontextualMorph>',
- ' <Substitution>',
+ " <NoncontextualMorph>",
+ " <Substitution>",
' <Lookup glyph="parenleft" value="parenleft.vertical"/>',
' <Lookup glyph="parenright" value="parenright.vertical"/>',
- ' </Substitution>',
- ' </NoncontextualMorph>',
- ' </MorphSubtable>',
- '</MorphChain>',
+ " </Substitution>",
+ " </NoncontextualMorph>",
+ " </MorphSubtable>",
+ "</MorphChain>",
]
class MORTNoncontextualGlyphSubstitutionTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- glyphs = ['.notdef'] + ['g.%d' % i for i in range (1, 140)]
- glyphs[11], glyphs[13] = 'parenleft', 'parenright'
- glyphs[135], glyphs[136] = 'parenleft.vertical', 'parenright.vertical'
+ glyphs = [".notdef"] + ["g.%d" % i for i in range(1, 140)]
+ glyphs[11], glyphs[13] = "parenleft", "parenright"
+ glyphs[135], glyphs[136] = "parenleft.vertical", "parenright.vertical"
cls.font = FakeFont(glyphs)
def test_decompile_toXML(self):
- table = newTable('mort')
+ table = newTable("mort")
table.decompile(MORT_NONCONTEXTUAL_DATA, self.font)
self.assertEqual(getXML(table.toXML), MORT_NONCONTEXTUAL_XML)
def test_compile_fromXML(self):
- table = newTable('mort')
+ table = newTable("mort")
for name, attrs, content in parseXML(MORT_NONCONTEXTUAL_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(MORT_NONCONTEXTUAL_DATA))
+ self.assertEqual(
+ hexStr(table.compile(self.font)), hexStr(MORT_NONCONTEXTUAL_DATA)
+ )
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_m_o_r_x_test.py b/Tests/ttLib/tables/_m_o_r_x_test.py
index d65619ca..eae3efc0 100644
--- a/Tests/ttLib/tables/_m_o_r_x_test.py
+++ b/Tests/ttLib/tables/_m_o_r_x_test.py
@@ -9,131 +9,131 @@ import unittest
# The test case has therefore been adapted from the example 'mort' table in
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html
MORX_NONCONTEXTUAL_DATA = deHexStr(
- '0002 0000 ' # 0: Version=2, Reserved=0
- '0000 0001 ' # 4: MorphChainCount=1
- '0000 0001 ' # 8: DefaultFlags=1
- '0000 0058 ' # 12: StructLength=88
- '0000 0003 ' # 16: MorphFeatureCount=3
- '0000 0001 ' # 20: MorphSubtableCount=1
- '0004 0000 ' # 24: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
- '0000 0001 ' # 28: Feature[0].EnableFlags=0x00000001
- 'FFFF FFFF ' # 32: Feature[0].DisableFlags=0xFFFFFFFF
- '0004 0001 ' # 36: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
- '0000 0000 ' # 40: Feature[1].EnableFlags=0x00000000
- 'FFFF FFFE ' # 44: Feature[1].DisableFlags=0xFFFFFFFE
- '0000 0001 ' # 48: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
- '0000 0000 ' # 52: Feature[2].EnableFlags=0 (required for last feature)
- '0000 0000 ' # 56: Feature[2].EnableFlags=0 (required for last feature)
- '0000 0024 ' # 60: Subtable[0].StructLength=36
- '80 ' # 64: Subtable[0].CoverageFlags=0x80
- '00 00 ' # 65: Subtable[0].Reserved=0
- '04 ' # 67: Subtable[0].MorphType=4/NoncontextualMorph
- '0000 0001 ' # 68: Subtable[0].SubFeatureFlags=0x1
- '0006 0004 ' # 72: LookupFormat=6, UnitSize=4
- '0002 0008 ' # 76: NUnits=2, SearchRange=8
- '0001 0000 ' # 80: EntrySelector=1, RangeShift=0
- '000B 0087 ' # 84: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
- '000D 0088 ' # 88: Glyph=13 (parenright); Value=136 (parenright.vertical)
- 'FFFF 0000 ' # 92: Glyph=<end>; Value=0
-) # 96: <end>
+ "0002 0000 " # 0: Version=2, Reserved=0
+ "0000 0001 " # 4: MorphChainCount=1
+ "0000 0001 " # 8: DefaultFlags=1
+ "0000 0058 " # 12: StructLength=88
+ "0000 0003 " # 16: MorphFeatureCount=3
+ "0000 0001 " # 20: MorphSubtableCount=1
+ "0004 0000 " # 24: Feature[0].FeatureType=4/VertSubst, .FeatureSetting=on
+ "0000 0001 " # 28: Feature[0].EnableFlags=0x00000001
+ "FFFF FFFF " # 32: Feature[0].DisableFlags=0xFFFFFFFF
+ "0004 0001 " # 36: Feature[1].FeatureType=4/VertSubst, .FeatureSetting=off
+ "0000 0000 " # 40: Feature[1].EnableFlags=0x00000000
+ "FFFF FFFE " # 44: Feature[1].DisableFlags=0xFFFFFFFE
+ "0000 0001 " # 48: Feature[2].FeatureType=0/GlyphEffects, .FeatSetting=off
+ "0000 0000 " # 52: Feature[2].EnableFlags=0 (required for last feature)
+ "0000 0000 " # 56: Feature[2].EnableFlags=0 (required for last feature)
+ "0000 0024 " # 60: Subtable[0].StructLength=36
+ "80 " # 64: Subtable[0].CoverageFlags=0x80
+ "00 00 " # 65: Subtable[0].Reserved=0
+ "04 " # 67: Subtable[0].MorphType=4/NoncontextualMorph
+ "0000 0001 " # 68: Subtable[0].SubFeatureFlags=0x1
+ "0006 0004 " # 72: LookupFormat=6, UnitSize=4
+ "0002 0008 " # 76: NUnits=2, SearchRange=8
+ "0001 0000 " # 80: EntrySelector=1, RangeShift=0
+ "000B 0087 " # 84: Glyph=11 (parenleft); Value=135 (parenleft.vertical)
+ "000D 0088 " # 88: Glyph=13 (parenright); Value=136 (parenright.vertical)
+ "FFFF 0000 " # 92: Glyph=<end>; Value=0
+) # 96: <end>
assert len(MORX_NONCONTEXTUAL_DATA) == 96
MORX_NONCONTEXTUAL_XML = [
'<Version value="2"/>',
'<Reserved value="0"/>',
- '<!-- MorphChainCount=1 -->',
+ "<!-- MorphChainCount=1 -->",
'<MorphChain index="0">',
' <DefaultFlags value="0x00000001"/>',
- ' <!-- StructLength=88 -->',
- ' <!-- MorphFeatureCount=3 -->',
- ' <!-- MorphSubtableCount=1 -->',
+ " <!-- StructLength=88 -->",
+ " <!-- MorphFeatureCount=3 -->",
+ " <!-- MorphSubtableCount=1 -->",
' <MorphFeature index="0">',
' <FeatureType value="4"/>',
' <FeatureSetting value="0"/>',
' <EnableFlags value="0x00000001"/>',
' <DisableFlags value="0xFFFFFFFF"/>',
- ' </MorphFeature>',
+ " </MorphFeature>",
' <MorphFeature index="1">',
' <FeatureType value="4"/>',
' <FeatureSetting value="1"/>',
' <EnableFlags value="0x00000000"/>',
' <DisableFlags value="0xFFFFFFFE"/>',
- ' </MorphFeature>',
+ " </MorphFeature>",
' <MorphFeature index="2">',
' <FeatureType value="0"/>',
' <FeatureSetting value="1"/>',
' <EnableFlags value="0x00000000"/>',
' <DisableFlags value="0x00000000"/>',
- ' </MorphFeature>',
+ " </MorphFeature>",
' <MorphSubtable index="0">',
- ' <!-- StructLength=36 -->',
+ " <!-- StructLength=36 -->",
' <TextDirection value="Vertical"/>',
' <ProcessingOrder value="LayoutOrder"/>',
- ' <!-- MorphType=4 -->',
+ " <!-- MorphType=4 -->",
' <SubFeatureFlags value="0x00000001"/>',
- ' <NoncontextualMorph>',
- ' <Substitution>',
+ " <NoncontextualMorph>",
+ " <Substitution>",
' <Lookup glyph="parenleft" value="parenleft.vertical"/>',
' <Lookup glyph="parenright" value="parenright.vertical"/>',
- ' </Substitution>',
- ' </NoncontextualMorph>',
- ' </MorphSubtable>',
- '</MorphChain>',
+ " </Substitution>",
+ " </NoncontextualMorph>",
+ " </MorphSubtable>",
+ "</MorphChain>",
]
MORX_REARRANGEMENT_DATA = deHexStr(
- '0002 0000 ' # 0: Version=2, Reserved=0
- '0000 0001 ' # 4: MorphChainCount=1
- '0000 0001 ' # 8: DefaultFlags=1
- '0000 0078 ' # 12: StructLength=120 (+8=128)
- '0000 0000 ' # 16: MorphFeatureCount=0
- '0000 0001 ' # 20: MorphSubtableCount=1
- '0000 0068 ' # 24: Subtable[0].StructLength=104 (+24=128)
- '80 ' # 28: Subtable[0].CoverageFlags=0x80
- '00 00 ' # 29: Subtable[0].Reserved=0
- '00 ' # 31: Subtable[0].MorphType=0/RearrangementMorph
- '0000 0001 ' # 32: Subtable[0].SubFeatureFlags=0x1
- '0000 0006 ' # 36: STXHeader.ClassCount=6
- '0000 0010 ' # 40: STXHeader.ClassTableOffset=16 (+36=52)
- '0000 0028 ' # 44: STXHeader.StateArrayOffset=40 (+36=76)
- '0000 004C ' # 48: STXHeader.EntryTableOffset=76 (+36=112)
- '0006 0004 ' # 52: ClassTable.LookupFormat=6, .UnitSize=4
- '0002 0008 ' # 56: .NUnits=2, .SearchRange=8
- '0001 0000 ' # 60: .EntrySelector=1, .RangeShift=0
- '0001 0005 ' # 64: Glyph=A; Class=5
- '0003 0004 ' # 68: Glyph=C; Class=4
- 'FFFF 0000 ' # 72: Glyph=<end>; Value=0
- '0000 0001 0002 0003 0002 0001 ' # 76: State[0][0..5]
- '0003 0003 0003 0003 0003 0003 ' # 88: State[1][0..5]
- '0001 0003 0003 0003 0002 0002 ' # 100: State[2][0..5]
- '0002 FFFF ' # 112: Entries[0].NewState=2, .Flags=0xFFFF
- '0001 A00D ' # 116: Entries[1].NewState=1, .Flags=0xA00D
- '0000 8006 ' # 120: Entries[2].NewState=0, .Flags=0x8006
- '0002 0000 ' # 124: Entries[3].NewState=2, .Flags=0x0000
-) # 128: <end>
+ "0002 0000 " # 0: Version=2, Reserved=0
+ "0000 0001 " # 4: MorphChainCount=1
+ "0000 0001 " # 8: DefaultFlags=1
+ "0000 0078 " # 12: StructLength=120 (+8=128)
+ "0000 0000 " # 16: MorphFeatureCount=0
+ "0000 0001 " # 20: MorphSubtableCount=1
+ "0000 0068 " # 24: Subtable[0].StructLength=104 (+24=128)
+ "80 " # 28: Subtable[0].CoverageFlags=0x80
+ "00 00 " # 29: Subtable[0].Reserved=0
+ "00 " # 31: Subtable[0].MorphType=0/RearrangementMorph
+ "0000 0001 " # 32: Subtable[0].SubFeatureFlags=0x1
+ "0000 0006 " # 36: STXHeader.ClassCount=6
+ "0000 0010 " # 40: STXHeader.ClassTableOffset=16 (+36=52)
+ "0000 0028 " # 44: STXHeader.StateArrayOffset=40 (+36=76)
+ "0000 004C " # 48: STXHeader.EntryTableOffset=76 (+36=112)
+ "0006 0004 " # 52: ClassTable.LookupFormat=6, .UnitSize=4
+ "0002 0008 " # 56: .NUnits=2, .SearchRange=8
+ "0001 0000 " # 60: .EntrySelector=1, .RangeShift=0
+ "0001 0005 " # 64: Glyph=A; Class=5
+ "0003 0004 " # 68: Glyph=C; Class=4
+ "FFFF 0000 " # 72: Glyph=<end>; Value=0
+ "0000 0001 0002 0003 0002 0001 " # 76: State[0][0..5]
+ "0003 0003 0003 0003 0003 0003 " # 88: State[1][0..5]
+ "0001 0003 0003 0003 0002 0002 " # 100: State[2][0..5]
+ "0002 FFFF " # 112: Entries[0].NewState=2, .Flags=0xFFFF
+ "0001 A00D " # 116: Entries[1].NewState=1, .Flags=0xA00D
+ "0000 8006 " # 120: Entries[2].NewState=0, .Flags=0x8006
+ "0002 0000 " # 124: Entries[3].NewState=2, .Flags=0x0000
+) # 128: <end>
assert len(MORX_REARRANGEMENT_DATA) == 128, len(MORX_REARRANGEMENT_DATA)
MORX_REARRANGEMENT_XML = [
'<Version value="2"/>',
'<Reserved value="0"/>',
- '<!-- MorphChainCount=1 -->',
+ "<!-- MorphChainCount=1 -->",
'<MorphChain index="0">',
' <DefaultFlags value="0x00000001"/>',
- ' <!-- StructLength=120 -->',
- ' <!-- MorphFeatureCount=0 -->',
- ' <!-- MorphSubtableCount=1 -->',
+ " <!-- StructLength=120 -->",
+ " <!-- MorphFeatureCount=0 -->",
+ " <!-- MorphSubtableCount=1 -->",
' <MorphSubtable index="0">',
- ' <!-- StructLength=104 -->',
+ " <!-- StructLength=104 -->",
' <TextDirection value="Vertical"/>',
' <ProcessingOrder value="LayoutOrder"/>',
- ' <!-- MorphType=0 -->',
+ " <!-- MorphType=0 -->",
' <SubFeatureFlags value="0x00000001"/>',
- ' <RearrangementMorph>',
- ' <StateTable>',
- ' <!-- GlyphClassCount=6 -->',
+ " <RearrangementMorph>",
+ " <StateTable>",
+ " <!-- GlyphClassCount=6 -->",
' <GlyphClass glyph="A" value="5"/>',
' <GlyphClass glyph="C" value="4"/>',
' <State index="0">',
@@ -142,91 +142,91 @@ MORX_REARRANGEMENT_XML = [
' <Flags value="MarkFirst,DontAdvance,MarkLast"/>',
' <ReservedFlags value="0x1FF0"/>',
' <Verb value="15"/><!-- ABxCD ⇒ DCxBA -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="1"/>',
' <Flags value="MarkFirst,MarkLast"/>',
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
' <Flags value="MarkFirst"/>',
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="0"/>',
' <Flags value="MarkFirst"/>',
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="1"/>',
' <Flags value="MarkFirst,MarkLast"/>',
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="1">',
' <Transition onGlyphClass="0">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="2">',
' <Transition onGlyphClass="0">',
' <NewState value="1"/>',
' <Flags value="MarkFirst,MarkLast"/>',
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="2"/>',
' <Verb value="0"/><!-- no change -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="0"/>',
' <Flags value="MarkFirst"/>',
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="0"/>',
' <Flags value="MarkFirst"/>',
' <Verb value="6"/><!-- xCD ⇒ CDx -->',
- ' </Transition>',
- ' </State>',
- ' </StateTable>',
- ' </RearrangementMorph>',
- ' </MorphSubtable>',
- '</MorphChain>',
+ " </Transition>",
+ " </State>",
+ " </StateTable>",
+ " </RearrangementMorph>",
+ " </MorphSubtable>",
+ "</MorphChain>",
]
@@ -266,83 +266,77 @@ MORX_REARRANGEMENT_XML = [
#
# TODO: Ask Apple to fix “Example 1” in the ‘morx’ specification.
MORX_CONTEXTUAL_DATA = deHexStr(
- '0002 0000 ' # 0: Version=2, Reserved=0
- '0000 0001 ' # 4: MorphChainCount=1
- '0000 0001 ' # 8: DefaultFlags=1
- '0000 00B4 ' # 12: StructLength=180 (+8=188)
- '0000 0000 ' # 16: MorphFeatureCount=0
- '0000 0001 ' # 20: MorphSubtableCount=1
- '0000 00A4 ' # 24: Subtable[0].StructLength=164 (+24=188)
- '80 ' # 28: Subtable[0].CoverageFlags=0x80
- '00 00 ' # 29: Subtable[0].Reserved=0
- '01 ' # 31: Subtable[0].MorphType=1/ContextualMorph
- '0000 0001 ' # 32: Subtable[0].SubFeatureFlags=0x1
- '0000 0006 ' # 36: STXHeader.ClassCount=6
- '0000 0014 ' # 40: STXHeader.ClassTableOffset=20 (+36=56)
- '0000 0038 ' # 44: STXHeader.StateArrayOffset=56 (+36=92)
- '0000 005C ' # 48: STXHeader.EntryTableOffset=92 (+36=128)
- '0000 0074 ' # 52: STXHeader.PerGlyphTableOffset=116 (+36=152)
-
+ "0002 0000 " # 0: Version=2, Reserved=0
+ "0000 0001 " # 4: MorphChainCount=1
+ "0000 0001 " # 8: DefaultFlags=1
+ "0000 00B4 " # 12: StructLength=180 (+8=188)
+ "0000 0000 " # 16: MorphFeatureCount=0
+ "0000 0001 " # 20: MorphSubtableCount=1
+ "0000 00A4 " # 24: Subtable[0].StructLength=164 (+24=188)
+ "80 " # 28: Subtable[0].CoverageFlags=0x80
+ "00 00 " # 29: Subtable[0].Reserved=0
+ "01 " # 31: Subtable[0].MorphType=1/ContextualMorph
+ "0000 0001 " # 32: Subtable[0].SubFeatureFlags=0x1
+ "0000 0006 " # 36: STXHeader.ClassCount=6
+ "0000 0014 " # 40: STXHeader.ClassTableOffset=20 (+36=56)
+ "0000 0038 " # 44: STXHeader.StateArrayOffset=56 (+36=92)
+ "0000 005C " # 48: STXHeader.EntryTableOffset=92 (+36=128)
+ "0000 0074 " # 52: STXHeader.PerGlyphTableOffset=116 (+36=152)
# Glyph class table.
- '0006 0004 ' # 56: ClassTable.LookupFormat=6, .UnitSize=4
- '0005 0010 ' # 60: .NUnits=5, .SearchRange=16
- '0002 0004 ' # 64: .EntrySelector=2, .RangeShift=4
- '0032 0004 ' # 68: Glyph=50; Class=4
- '0034 0004 ' # 72: Glyph=52; Class=4
- '0050 0005 ' # 76: Glyph=80; Class=5
- '00C9 0004 ' # 80: Glyph=201; Class=4
- '00CA 0004 ' # 84: Glyph=202; Class=4
- 'FFFF 0000 ' # 88: Glyph=<end>; Value=<filler>
-
+ "0006 0004 " # 56: ClassTable.LookupFormat=6, .UnitSize=4
+ "0005 0010 " # 60: .NUnits=5, .SearchRange=16
+ "0002 0004 " # 64: .EntrySelector=2, .RangeShift=4
+ "0032 0004 " # 68: Glyph=50; Class=4
+ "0034 0004 " # 72: Glyph=52; Class=4
+ "0050 0005 " # 76: Glyph=80; Class=5
+ "00C9 0004 " # 80: Glyph=201; Class=4
+ "00CA 0004 " # 84: Glyph=202; Class=4
+ "FFFF 0000 " # 88: Glyph=<end>; Value=<filler>
# State array.
- '0000 0000 0000 0000 0000 0001 ' # 92: State[0][0..5]
- '0000 0000 0000 0000 0000 0001 ' # 104: State[1][0..5]
- '0000 0000 0000 0000 0002 0001 ' # 116: State[2][0..5]
-
+ "0000 0000 0000 0000 0000 0001 " # 92: State[0][0..5]
+ "0000 0000 0000 0000 0000 0001 " # 104: State[1][0..5]
+ "0000 0000 0000 0000 0002 0001 " # 116: State[2][0..5]
# Entry table.
- '0000 0000 ' # 128: Entries[0].NewState=0, .Flags=0
- 'FFFF FFFF ' # 132: Entries[0].MarkSubst=None, .CurSubst=None
- '0002 0000 ' # 136: Entries[1].NewState=2, .Flags=0
- 'FFFF FFFF ' # 140: Entries[1].MarkSubst=None, .CurSubst=None
- '0000 0000 ' # 144: Entries[2].NewState=0, .Flags=0
- 'FFFF 0000 ' # 148: Entries[2].MarkSubst=None, .CurSubst=PerGlyph #0
- # 152: <no padding needed for 4-byte alignment>
-
+ "0000 0000 " # 128: Entries[0].NewState=0, .Flags=0
+ "FFFF FFFF " # 132: Entries[0].MarkSubst=None, .CurSubst=None
+ "0002 0000 " # 136: Entries[1].NewState=2, .Flags=0
+ "FFFF FFFF " # 140: Entries[1].MarkSubst=None, .CurSubst=None
+ "0000 0000 " # 144: Entries[2].NewState=0, .Flags=0
+ "FFFF 0000 " # 148: Entries[2].MarkSubst=None, .CurSubst=PerGlyph #0
+ # 152: <no padding needed for 4-byte alignment>
# Per-glyph lookup tables.
- '0000 0004 ' # 152: Offset from this point to per-glyph lookup #0.
-
+ "0000 0004 " # 152: Offset from this point to per-glyph lookup #0.
# Per-glyph lookup #0.
- '0006 0004 ' # 156: ClassTable.LookupFormat=6, .UnitSize=4
- '0004 0010 ' # 160: .NUnits=4, .SearchRange=16
- '0002 0000 ' # 164: .EntrySelector=2, .RangeShift=0
- '0032 0258 ' # 168: Glyph=50; ReplacementGlyph=600
- '0034 0259 ' # 172: Glyph=52; ReplacementGlyph=601
- '00C9 025A ' # 176: Glyph=201; ReplacementGlyph=602
- '00CA 0384 ' # 180: Glyph=202; ReplacementGlyph=900
- 'FFFF 0000 ' # 184: Glyph=<end>; Value=<filler>
-
-) # 188: <end>
+ "0006 0004 " # 156: ClassTable.LookupFormat=6, .UnitSize=4
+ "0004 0010 " # 160: .NUnits=4, .SearchRange=16
+ "0002 0000 " # 164: .EntrySelector=2, .RangeShift=0
+ "0032 0258 " # 168: Glyph=50; ReplacementGlyph=600
+ "0034 0259 " # 172: Glyph=52; ReplacementGlyph=601
+ "00C9 025A " # 176: Glyph=201; ReplacementGlyph=602
+ "00CA 0384 " # 180: Glyph=202; ReplacementGlyph=900
+ "FFFF 0000 " # 184: Glyph=<end>; Value=<filler>
+) # 188: <end>
assert len(MORX_CONTEXTUAL_DATA) == 188, len(MORX_CONTEXTUAL_DATA)
MORX_CONTEXTUAL_XML = [
'<Version value="2"/>',
'<Reserved value="0"/>',
- '<!-- MorphChainCount=1 -->',
+ "<!-- MorphChainCount=1 -->",
'<MorphChain index="0">',
' <DefaultFlags value="0x00000001"/>',
- ' <!-- StructLength=180 -->',
- ' <!-- MorphFeatureCount=0 -->',
- ' <!-- MorphSubtableCount=1 -->',
+ " <!-- StructLength=180 -->",
+ " <!-- MorphFeatureCount=0 -->",
+ " <!-- MorphSubtableCount=1 -->",
' <MorphSubtable index="0">',
- ' <!-- StructLength=164 -->',
+ " <!-- StructLength=164 -->",
' <TextDirection value="Vertical"/>',
' <ProcessingOrder value="LayoutOrder"/>',
- ' <!-- MorphType=1 -->',
+ " <!-- MorphType=1 -->",
' <SubFeatureFlags value="0x00000001"/>',
- ' <ContextualMorph>',
- ' <StateTable>',
- ' <!-- GlyphClassCount=6 -->',
+ " <ContextualMorph>",
+ " <StateTable>",
+ " <!-- GlyphClassCount=6 -->",
' <GlyphClass glyph="A" value="4"/>',
' <GlyphClass glyph="B" value="4"/>',
' <GlyphClass glyph="C" value="5"/>',
@@ -353,107 +347,107 @@ MORX_CONTEXTUAL_XML = [
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="2"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="1">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="2"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="2">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="0"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="2"/>',
' <MarkIndex value="65535"/>',
' <CurrentIndex value="65535"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <PerGlyphLookup index="0">',
' <Lookup glyph="A" value="A.swash"/>',
' <Lookup glyph="B" value="B.swash"/>',
' <Lookup glyph="X" value="X.swash"/>',
' <Lookup glyph="Y" value="Y.swash"/>',
- ' </PerGlyphLookup>',
- ' </StateTable>',
- ' </ContextualMorph>',
- ' </MorphSubtable>',
- '</MorphChain>',
+ " </PerGlyphLookup>",
+ " </StateTable>",
+ " </ContextualMorph>",
+ " </MorphSubtable>",
+ "</MorphChain>",
]
@@ -482,91 +476,84 @@ MORX_CONTEXTUAL_XML = [
#
# TODO: Ask Apple to fix “Example 2” in the ‘morx’ specification.
MORX_LIGATURE_DATA = deHexStr(
- '0002 0000 ' # 0: Version=2, Reserved=0
- '0000 0001 ' # 4: MorphChainCount=1
- '0000 0001 ' # 8: DefaultFlags=1
- '0000 00DA ' # 12: StructLength=218 (+8=226)
- '0000 0000 ' # 16: MorphFeatureCount=0
- '0000 0001 ' # 20: MorphSubtableCount=1
- '0000 00CA ' # 24: Subtable[0].StructLength=202 (+24=226)
- '80 ' # 28: Subtable[0].CoverageFlags=0x80
- '00 00 ' # 29: Subtable[0].Reserved=0
- '02 ' # 31: Subtable[0].MorphType=2/LigatureMorph
- '0000 0001 ' # 32: Subtable[0].SubFeatureFlags=0x1
-
+ "0002 0000 " # 0: Version=2, Reserved=0
+ "0000 0001 " # 4: MorphChainCount=1
+ "0000 0001 " # 8: DefaultFlags=1
+ "0000 00DA " # 12: StructLength=218 (+8=226)
+ "0000 0000 " # 16: MorphFeatureCount=0
+ "0000 0001 " # 20: MorphSubtableCount=1
+ "0000 00CA " # 24: Subtable[0].StructLength=202 (+24=226)
+ "80 " # 28: Subtable[0].CoverageFlags=0x80
+ "00 00 " # 29: Subtable[0].Reserved=0
+ "02 " # 31: Subtable[0].MorphType=2/LigatureMorph
+ "0000 0001 " # 32: Subtable[0].SubFeatureFlags=0x1
# State table header.
- '0000 0007 ' # 36: STXHeader.ClassCount=7
- '0000 001C ' # 40: STXHeader.ClassTableOffset=28 (+36=64)
- '0000 0040 ' # 44: STXHeader.StateArrayOffset=64 (+36=100)
- '0000 0078 ' # 48: STXHeader.EntryTableOffset=120 (+36=156)
- '0000 0090 ' # 52: STXHeader.LigActionsOffset=144 (+36=180)
- '0000 009C ' # 56: STXHeader.LigComponentsOffset=156 (+36=192)
- '0000 00AE ' # 60: STXHeader.LigListOffset=174 (+36=210)
-
+ "0000 0007 " # 36: STXHeader.ClassCount=7
+ "0000 001C " # 40: STXHeader.ClassTableOffset=28 (+36=64)
+ "0000 0040 " # 44: STXHeader.StateArrayOffset=64 (+36=100)
+ "0000 0078 " # 48: STXHeader.EntryTableOffset=120 (+36=156)
+ "0000 0090 " # 52: STXHeader.LigActionsOffset=144 (+36=180)
+ "0000 009C " # 56: STXHeader.LigComponentsOffset=156 (+36=192)
+ "0000 00AE " # 60: STXHeader.LigListOffset=174 (+36=210)
# Glyph class table.
- '0002 0006 ' # 64: ClassTable.LookupFormat=2, .UnitSize=6
- '0003 000C ' # 68: .NUnits=3, .SearchRange=12
- '0001 0006 ' # 72: .EntrySelector=1, .RangeShift=6
- '0016 0014 0004 ' # 76: GlyphID 20..22 [a..c] -> GlyphClass 4
- '0018 0017 0005 ' # 82: GlyphID 23..24 [d..e] -> GlyphClass 5
- '001C 001A 0006 ' # 88: GlyphID 26..28 [g..i] -> GlyphClass 6
- 'FFFF FFFF 0000 ' # 94: <end of lookup>
-
+ "0002 0006 " # 64: ClassTable.LookupFormat=2, .UnitSize=6
+ "0003 000C " # 68: .NUnits=3, .SearchRange=12
+ "0001 0006 " # 72: .EntrySelector=1, .RangeShift=6
+ "0016 0014 0004 " # 76: GlyphID 20..22 [a..c] -> GlyphClass 4
+ "0018 0017 0005 " # 82: GlyphID 23..24 [d..e] -> GlyphClass 5
+ "001C 001A 0006 " # 88: GlyphID 26..28 [g..i] -> GlyphClass 6
+ "FFFF FFFF 0000 " # 94: <end of lookup>
# State array.
- '0000 0000 0000 0000 0001 0000 0000 ' # 100: State[0][0..6]
- '0000 0000 0000 0000 0001 0000 0000 ' # 114: State[1][0..6]
- '0000 0000 0000 0000 0001 0002 0000 ' # 128: State[2][0..6]
- '0000 0000 0000 0000 0001 0002 0003 ' # 142: State[3][0..6]
-
+ "0000 0000 0000 0000 0001 0000 0000 " # 100: State[0][0..6]
+ "0000 0000 0000 0000 0001 0000 0000 " # 114: State[1][0..6]
+ "0000 0000 0000 0000 0001 0002 0000 " # 128: State[2][0..6]
+ "0000 0000 0000 0000 0001 0002 0003 " # 142: State[3][0..6]
# Entry table.
- '0000 0000 ' # 156: Entries[0].NewState=0, .Flags=0
- '0000 ' # 160: Entries[0].ActionIndex=<n/a> because no 0x2000 flag
- '0002 8000 ' # 162: Entries[1].NewState=2, .Flags=0x8000 (SetComponent)
- '0000 ' # 166: Entries[1].ActionIndex=<n/a> because no 0x2000 flag
- '0003 8000 ' # 168: Entries[2].NewState=3, .Flags=0x8000 (SetComponent)
- '0000 ' # 172: Entries[2].ActionIndex=<n/a> because no 0x2000 flag
- '0000 A000 ' # 174: Entries[3].NewState=0, .Flags=0xA000 (SetComponent,Act)
- '0000 ' # 178: Entries[3].ActionIndex=0 (start at Action[0])
-
+ "0000 0000 " # 156: Entries[0].NewState=0, .Flags=0
+ "0000 " # 160: Entries[0].ActionIndex=<n/a> because no 0x2000 flag
+ "0002 8000 " # 162: Entries[1].NewState=2, .Flags=0x8000 (SetComponent)
+ "0000 " # 166: Entries[1].ActionIndex=<n/a> because no 0x2000 flag
+ "0003 8000 " # 168: Entries[2].NewState=3, .Flags=0x8000 (SetComponent)
+ "0000 " # 172: Entries[2].ActionIndex=<n/a> because no 0x2000 flag
+ "0000 A000 " # 174: Entries[3].NewState=0, .Flags=0xA000 (SetComponent,Act)
+ "0000 " # 178: Entries[3].ActionIndex=0 (start at Action[0])
# Ligature actions table.
- '3FFF FFE7 ' # 180: Action[0].Flags=0, .GlyphIndexDelta=-25
- '3FFF FFED ' # 184: Action[1].Flags=0, .GlyphIndexDelta=-19
- 'BFFF FFF2 ' # 188: Action[2].Flags=<end of list>, .GlyphIndexDelta=-14
-
+ "3FFF FFE7 " # 180: Action[0].Flags=0, .GlyphIndexDelta=-25
+ "3FFF FFED " # 184: Action[1].Flags=0, .GlyphIndexDelta=-19
+ "BFFF FFF2 " # 188: Action[2].Flags=<end of list>, .GlyphIndexDelta=-14
# Ligature component table.
- '0000 0001 ' # 192: LigComponent[0]=0, LigComponent[1]=1
- '0002 0003 ' # 196: LigComponent[2]=2, LigComponent[3]=3
- '0000 0004 ' # 200: LigComponent[4]=0, LigComponent[5]=4
- '0000 0008 ' # 204: LigComponent[6]=0, LigComponent[7]=8
- '0010 ' # 208: LigComponent[8]=16
-
+ "0000 0001 " # 192: LigComponent[0]=0, LigComponent[1]=1
+ "0002 0003 " # 196: LigComponent[2]=2, LigComponent[3]=3
+ "0000 0004 " # 200: LigComponent[4]=0, LigComponent[5]=4
+ "0000 0008 " # 204: LigComponent[6]=0, LigComponent[7]=8
+ "0010 " # 208: LigComponent[8]=16
# Ligature list.
- '03E8 03E9 ' # 210: LigList[0]=1000, LigList[1]=1001
- '03EA 03EB ' # 214: LigList[2]=1002, LigList[3]=1003
- '03EC 03ED ' # 218: LigList[4]=1004, LigList[3]=1005
- '03EE 03EF ' # 222: LigList[5]=1006, LigList[6]=1007
-) # 226: <end>
+ "03E8 03E9 " # 210: LigList[0]=1000, LigList[1]=1001
+ "03EA 03EB " # 214: LigList[2]=1002, LigList[3]=1003
+ "03EC 03ED " # 218: LigList[4]=1004, LigList[3]=1005
+ "03EE 03EF " # 222: LigList[5]=1006, LigList[6]=1007
+) # 226: <end>
assert len(MORX_LIGATURE_DATA) == 226, len(MORX_LIGATURE_DATA)
MORX_LIGATURE_XML = [
'<Version value="2"/>',
'<Reserved value="0"/>',
- '<!-- MorphChainCount=1 -->',
+ "<!-- MorphChainCount=1 -->",
'<MorphChain index="0">',
' <DefaultFlags value="0x00000001"/>',
- ' <!-- StructLength=218 -->',
- ' <!-- MorphFeatureCount=0 -->',
- ' <!-- MorphSubtableCount=1 -->',
+ " <!-- StructLength=218 -->",
+ " <!-- MorphFeatureCount=0 -->",
+ " <!-- MorphSubtableCount=1 -->",
' <MorphSubtable index="0">',
- ' <!-- StructLength=202 -->',
+ " <!-- StructLength=202 -->",
' <TextDirection value="Vertical"/>',
' <ProcessingOrder value="LayoutOrder"/>',
- ' <!-- MorphType=2 -->',
+ " <!-- MorphType=2 -->",
' <SubFeatureFlags value="0x00000001"/>',
- ' <LigatureMorph>',
- ' <StateTable>',
- ' <!-- GlyphClassCount=7 -->',
+ " <LigatureMorph>",
+ " <StateTable>",
+ " <!-- GlyphClassCount=7 -->",
' <GlyphClass glyph="a" value="4"/>',
' <GlyphClass glyph="b" value="4"/>',
' <GlyphClass glyph="c" value="4"/>',
@@ -578,106 +565,106 @@ MORX_LIGATURE_XML = [
' <State index="0">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
' <Flags value="SetComponent"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="6">',
' <NewState value="0"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="1">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
' <Flags value="SetComponent"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="6">',
' <NewState value="0"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="2">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
' <Flags value="SetComponent"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="3"/>',
' <Flags value="SetComponent"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="6">',
' <NewState value="0"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="3">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
' <Flags value="SetComponent"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="3"/>',
' <Flags value="SetComponent"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="6">',
' <NewState value="0"/>',
' <Flags value="SetComponent"/>',
' <Action GlyphIndexDelta="-25"/>',
' <Action GlyphIndexDelta="-19"/>',
' <Action GlyphIndexDelta="-14"/>',
- ' </Transition>',
- ' </State>',
- ' <LigComponents>',
+ " </Transition>",
+ " </State>",
+ " <LigComponents>",
' <LigComponent index="0" value="0"/>',
' <LigComponent index="1" value="1"/>',
' <LigComponent index="2" value="2"/>',
@@ -687,8 +674,8 @@ MORX_LIGATURE_XML = [
' <LigComponent index="6" value="0"/>',
' <LigComponent index="7" value="8"/>',
' <LigComponent index="8" value="16"/>',
- ' </LigComponents>',
- ' <Ligatures>',
+ " </LigComponents>",
+ " <Ligatures>",
' <Ligature glyph="adf" index="0"/>',
' <Ligature glyph="adg" index="1"/>',
' <Ligature glyph="adh" index="2"/>',
@@ -697,84 +684,84 @@ MORX_LIGATURE_XML = [
' <Ligature glyph="aeg" index="5"/>',
' <Ligature glyph="aeh" index="6"/>',
' <Ligature glyph="aei" index="7"/>',
- ' </Ligatures>',
- ' </StateTable>',
- ' </LigatureMorph>',
- ' </MorphSubtable>',
- '</MorphChain>',
+ " </Ligatures>",
+ " </StateTable>",
+ " </LigatureMorph>",
+ " </MorphSubtable>",
+ "</MorphChain>",
]
# Taken from the `morx` table of the second font in DevanagariSangamMN.ttc
# on macOS X 10.12.6; manually pruned to just contain the insertion lookup.
MORX_INSERTION_DATA = deHexStr(
- '0002 0000 ' # 0: Version=2, Reserved=0
- '0000 0001 ' # 4: MorphChainCount=1
- '0000 0001 ' # 8: DefaultFlags=1
- '0000 00A4 ' # 12: StructLength=164 (+8=172)
- '0000 0000 ' # 16: MorphFeatureCount=0
- '0000 0001 ' # 20: MorphSubtableCount=1
- '0000 0094 ' # 24: Subtable[0].StructLength=148 (+24=172)
- '00 ' # 28: Subtable[0].CoverageFlags=0x00
- '00 00 ' # 29: Subtable[0].Reserved=0
- '05 ' # 31: Subtable[0].MorphType=5/InsertionMorph
- '0000 0001 ' # 32: Subtable[0].SubFeatureFlags=0x1
- '0000 0006 ' # 36: STXHeader.ClassCount=6
- '0000 0014 ' # 40: STXHeader.ClassTableOffset=20 (+36=56)
- '0000 004A ' # 44: STXHeader.StateArrayOffset=74 (+36=110)
- '0000 006E ' # 48: STXHeader.EntryTableOffset=110 (+36=146)
- '0000 0086 ' # 52: STXHeader.InsertionActionOffset=134 (+36=170)
- # Glyph class table.
- '0002 0006 ' # 56: ClassTable.LookupFormat=2, .UnitSize=6
- '0006 0018 ' # 60: .NUnits=6, .SearchRange=24
- '0002 000C ' # 64: .EntrySelector=2, .RangeShift=12
- '00AC 00AC 0005 ' # 68: GlyphID 172..172 -> GlyphClass 5
- '01EB 01E6 0005 ' # 74: GlyphID 486..491 -> GlyphClass 5
- '01F0 01F0 0004 ' # 80: GlyphID 496..496 -> GlyphClass 4
- '01F8 01F6 0004 ' # 88: GlyphID 502..504 -> GlyphClass 4
- '01FC 01FA 0004 ' # 92: GlyphID 506..508 -> GlyphClass 4
- '0250 0250 0005 ' # 98: GlyphID 592..592 -> GlyphClass 5
- 'FFFF FFFF 0000 ' # 104: <end of lookup>
+ "0002 0000 " # 0: Version=2, Reserved=0
+ "0000 0001 " # 4: MorphChainCount=1
+ "0000 0001 " # 8: DefaultFlags=1
+ "0000 00A4 " # 12: StructLength=164 (+8=172)
+ "0000 0000 " # 16: MorphFeatureCount=0
+ "0000 0001 " # 20: MorphSubtableCount=1
+ "0000 0094 " # 24: Subtable[0].StructLength=148 (+24=172)
+ "00 " # 28: Subtable[0].CoverageFlags=0x00
+ "00 00 " # 29: Subtable[0].Reserved=0
+ "05 " # 31: Subtable[0].MorphType=5/InsertionMorph
+ "0000 0001 " # 32: Subtable[0].SubFeatureFlags=0x1
+ "0000 0006 " # 36: STXHeader.ClassCount=6
+ "0000 0014 " # 40: STXHeader.ClassTableOffset=20 (+36=56)
+ "0000 004A " # 44: STXHeader.StateArrayOffset=74 (+36=110)
+ "0000 006E " # 48: STXHeader.EntryTableOffset=110 (+36=146)
+ "0000 0086 " # 52: STXHeader.InsertionActionOffset=134 (+36=170)
+ # Glyph class table.
+ "0002 0006 " # 56: ClassTable.LookupFormat=2, .UnitSize=6
+ "0006 0018 " # 60: .NUnits=6, .SearchRange=24
+ "0002 000C " # 64: .EntrySelector=2, .RangeShift=12
+ "00AC 00AC 0005 " # 68: GlyphID 172..172 -> GlyphClass 5
+ "01EB 01E6 0005 " # 74: GlyphID 486..491 -> GlyphClass 5
+ "01F0 01F0 0004 " # 80: GlyphID 496..496 -> GlyphClass 4
+ "01F8 01F6 0004 " # 88: GlyphID 502..504 -> GlyphClass 4
+ "01FC 01FA 0004 " # 92: GlyphID 506..508 -> GlyphClass 4
+ "0250 0250 0005 " # 98: GlyphID 592..592 -> GlyphClass 5
+ "FFFF FFFF 0000 " # 104: <end of lookup>
# State array.
- '0000 0000 0000 0000 0001 0000 ' # 110: State[0][0..5]
- '0000 0000 0000 0000 0001 0000 ' # 122: State[1][0..5]
- '0000 0000 0001 0000 0001 0002 ' # 134: State[2][0..5]
+ "0000 0000 0000 0000 0001 0000 " # 110: State[0][0..5]
+ "0000 0000 0000 0000 0001 0000 " # 122: State[1][0..5]
+ "0000 0000 0001 0000 0001 0002 " # 134: State[2][0..5]
# Entry table.
- '0000 0000 ' # 146: Entries[0].NewState=0, .Flags=0
- 'FFFF ' # 150: Entries[0].CurrentInsertIndex=<None>
- 'FFFF ' # 152: Entries[0].MarkedInsertIndex=<None>
- '0002 0000 ' # 154: Entries[1].NewState=0, .Flags=0
- 'FFFF ' # 158: Entries[1].CurrentInsertIndex=<None>
- 'FFFF ' # 160: Entries[1].MarkedInsertIndex=<None>
- '0000 ' # 162: Entries[2].NewState=0
- '2820 ' # 164: .Flags=CurrentIsKashidaLike,CurrentInsertBefore
- # .CurrentInsertCount=1, .MarkedInsertCount=0
- '0000 ' # 166: Entries[1].CurrentInsertIndex=0
- 'FFFF ' # 168: Entries[1].MarkedInsertIndex=<None>
+ "0000 0000 " # 146: Entries[0].NewState=0, .Flags=0
+ "FFFF " # 150: Entries[0].CurrentInsertIndex=<None>
+ "FFFF " # 152: Entries[0].MarkedInsertIndex=<None>
+ "0002 0000 " # 154: Entries[1].NewState=0, .Flags=0
+ "FFFF " # 158: Entries[1].CurrentInsertIndex=<None>
+ "FFFF " # 160: Entries[1].MarkedInsertIndex=<None>
+ "0000 " # 162: Entries[2].NewState=0
+ "2820 " # 164: .Flags=CurrentIsKashidaLike,CurrentInsertBefore
+ # .CurrentInsertCount=1, .MarkedInsertCount=0
+ "0000 " # 166: Entries[1].CurrentInsertIndex=0
+ "FFFF " # 168: Entries[1].MarkedInsertIndex=<None>
# Insertion action table.
- '022F' # 170: InsertionActionTable[0]=GlyphID 559
-) # 172: <end>
+ "022F" # 170: InsertionActionTable[0]=GlyphID 559
+) # 172: <end>
assert len(MORX_INSERTION_DATA) == 172, len(MORX_INSERTION_DATA)
MORX_INSERTION_XML = [
'<Version value="2"/>',
'<Reserved value="0"/>',
- '<!-- MorphChainCount=1 -->',
+ "<!-- MorphChainCount=1 -->",
'<MorphChain index="0">',
' <DefaultFlags value="0x00000001"/>',
- ' <!-- StructLength=164 -->',
- ' <!-- MorphFeatureCount=0 -->',
- ' <!-- MorphSubtableCount=1 -->',
+ " <!-- StructLength=164 -->",
+ " <!-- MorphFeatureCount=0 -->",
+ " <!-- MorphSubtableCount=1 -->",
' <MorphSubtable index="0">',
- ' <!-- StructLength=148 -->',
+ " <!-- StructLength=148 -->",
' <TextDirection value="Horizontal"/>',
' <ProcessingOrder value="LayoutOrder"/>',
- ' <!-- MorphType=5 -->',
+ " <!-- MorphType=5 -->",
' <SubFeatureFlags value="0x00000001"/>',
- ' <InsertionMorph>',
- ' <StateTable>',
- ' <!-- GlyphClassCount=6 -->',
+ " <InsertionMorph>",
+ " <StateTable>",
+ " <!-- GlyphClassCount=6 -->",
' <GlyphClass glyph="g.172" value="5"/>',
' <GlyphClass glyph="g.486" value="5"/>',
' <GlyphClass glyph="g.487" value="5"/>',
@@ -793,211 +780,205 @@ MORX_INSERTION_XML = [
' <State index="0">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="0"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="1">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="0"/>',
- ' </Transition>',
- ' </State>',
+ " </Transition>",
+ " </State>",
' <State index="2">',
' <Transition onGlyphClass="0">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="1">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="2">',
' <NewState value="2"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="3">',
' <NewState value="0"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="4">',
' <NewState value="2"/>',
- ' </Transition>',
+ " </Transition>",
' <Transition onGlyphClass="5">',
' <NewState value="0"/>',
' <Flags value="CurrentIsKashidaLike,CurrentInsertBefore"/>',
' <CurrentInsertionAction glyph="g.559"/>',
- ' </Transition>',
- ' </State>',
- ' </StateTable>',
- ' </InsertionMorph>',
- ' </MorphSubtable>',
- '</MorphChain>',
+ " </Transition>",
+ " </State>",
+ " </StateTable>",
+ " </InsertionMorph>",
+ " </MorphSubtable>",
+ "</MorphChain>",
]
class MORXNoncontextualGlyphSubstitutionTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- glyphs = ['.notdef'] + ['g.%d' % i for i in range (1, 140)]
- glyphs[11], glyphs[13] = 'parenleft', 'parenright'
- glyphs[135], glyphs[136] = 'parenleft.vertical', 'parenright.vertical'
+ glyphs = [".notdef"] + ["g.%d" % i for i in range(1, 140)]
+ glyphs[11], glyphs[13] = "parenleft", "parenright"
+ glyphs[135], glyphs[136] = "parenleft.vertical", "parenright.vertical"
cls.font = FakeFont(glyphs)
def test_decompile_toXML(self):
- table = newTable('morx')
+ table = newTable("morx")
table.decompile(MORX_NONCONTEXTUAL_DATA, self.font)
self.assertEqual(getXML(table.toXML), MORX_NONCONTEXTUAL_XML)
def test_compile_fromXML(self):
- table = newTable('morx')
+ table = newTable("morx")
for name, attrs, content in parseXML(MORX_NONCONTEXTUAL_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(MORX_NONCONTEXTUAL_DATA))
+ self.assertEqual(
+ hexStr(table.compile(self.font)), hexStr(MORX_NONCONTEXTUAL_DATA)
+ )
class MORXRearrangementTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.nodef', 'A', 'B', 'C'])
+ cls.font = FakeFont([".nodef", "A", "B", "C"])
def test_decompile_toXML(self):
- table = newTable('morx')
+ table = newTable("morx")
table.decompile(MORX_REARRANGEMENT_DATA, self.font)
self.assertEqual(getXML(table.toXML), MORX_REARRANGEMENT_XML)
def test_compile_fromXML(self):
- table = newTable('morx')
+ table = newTable("morx")
for name, attrs, content in parseXML(MORX_REARRANGEMENT_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(MORX_REARRANGEMENT_DATA))
+ self.assertEqual(
+ hexStr(table.compile(self.font)), hexStr(MORX_REARRANGEMENT_DATA)
+ )
class MORXContextualSubstitutionTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- g = ['.notdef'] + ['g.%d' % i for i in range (1, 910)]
- g[80] = 'C'
- g[50], g[52], g[201], g[202] = 'A', 'B', 'X', 'Y'
- g[600], g[601], g[602], g[900] = (
- 'A.swash', 'B.swash', 'X.swash', 'Y.swash')
+ g = [".notdef"] + ["g.%d" % i for i in range(1, 910)]
+ g[80] = "C"
+ g[50], g[52], g[201], g[202] = "A", "B", "X", "Y"
+ g[600], g[601], g[602], g[900] = ("A.swash", "B.swash", "X.swash", "Y.swash")
cls.font = FakeFont(g)
def test_decompile_toXML(self):
- table = newTable('morx')
+ table = newTable("morx")
table.decompile(MORX_CONTEXTUAL_DATA, self.font)
self.assertEqual(getXML(table.toXML), MORX_CONTEXTUAL_XML)
def test_compile_fromXML(self):
- table = newTable('morx')
+ table = newTable("morx")
for name, attrs, content in parseXML(MORX_CONTEXTUAL_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(MORX_CONTEXTUAL_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(MORX_CONTEXTUAL_DATA))
class MORXLigatureSubstitutionTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- g = ['.notdef'] + ['g.%d' % i for i in range (1, 1515)]
- g[20:29] = 'a b c d e f g h i'.split()
- g[1000:1008] = 'adf adg adh adi aef aeg aeh aei'.split()
- g[1008:1016] = 'bdf bdg bdh bdi bef beg beh bei'.split()
- g[1500:1507] = 'cdf cdg cdh cdi cef ceg ceh'.split()
- g[1511] = 'cei'
+ g = [".notdef"] + ["g.%d" % i for i in range(1, 1515)]
+ g[20:29] = "a b c d e f g h i".split()
+ g[1000:1008] = "adf adg adh adi aef aeg aeh aei".split()
+ g[1008:1016] = "bdf bdg bdh bdi bef beg beh bei".split()
+ g[1500:1507] = "cdf cdg cdh cdi cef ceg ceh".split()
+ g[1511] = "cei"
cls.font = FakeFont(g)
def test_decompile_toXML(self):
- table = newTable('morx')
+ table = newTable("morx")
table.decompile(MORX_LIGATURE_DATA, self.font)
self.assertEqual(getXML(table.toXML), MORX_LIGATURE_XML)
def test_compile_fromXML(self):
- table = newTable('morx')
+ table = newTable("morx")
for name, attrs, content in parseXML(MORX_LIGATURE_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(MORX_LIGATURE_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(MORX_LIGATURE_DATA))
class MORXGlyphInsertionTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.notdef'] + ['g.%d' % i for i in range (1, 910)])
+ cls.font = FakeFont([".notdef"] + ["g.%d" % i for i in range(1, 910)])
def test_decompile_toXML(self):
- table = newTable('morx')
+ table = newTable("morx")
table.decompile(MORX_INSERTION_DATA, self.font)
self.assertEqual(getXML(table.toXML), MORX_INSERTION_XML)
def test_compile_fromXML(self):
- table = newTable('morx')
+ table = newTable("morx")
for name, attrs, content in parseXML(MORX_INSERTION_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(MORX_INSERTION_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(MORX_INSERTION_DATA))
class MORXCoverageFlagsTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.notdef', 'A', 'B', 'C'])
-
- def checkFlags(self, flags, textDirection, processingOrder,
- checkCompile=True):
- data = bytesjoin([
- MORX_REARRANGEMENT_DATA[:28],
- bytechr(flags << 4),
- MORX_REARRANGEMENT_DATA[29:]])
+ cls.font = FakeFont([".notdef", "A", "B", "C"])
+
+ def checkFlags(self, flags, textDirection, processingOrder, checkCompile=True):
+ data = bytesjoin(
+ [
+ MORX_REARRANGEMENT_DATA[:28],
+ bytechr(flags << 4),
+ MORX_REARRANGEMENT_DATA[29:],
+ ]
+ )
xml = []
for line in MORX_REARRANGEMENT_XML:
- if line.startswith(' <TextDirection '):
+ if line.startswith(" <TextDirection "):
line = ' <TextDirection value="%s"/>' % textDirection
- elif line.startswith(' <ProcessingOrder '):
+ elif line.startswith(" <ProcessingOrder "):
line = ' <ProcessingOrder value="%s"/>' % processingOrder
xml.append(line)
- table1 = newTable('morx')
+ table1 = newTable("morx")
table1.decompile(data, self.font)
self.assertEqual(getXML(table1.toXML), xml)
if checkCompile:
- table2 = newTable('morx')
+ table2 = newTable("morx")
for name, attrs, content in parseXML(xml):
table2.fromXML(name, attrs, content, font=self.font)
self.assertEqual(hexStr(table2.compile(self.font)), hexStr(data))
@@ -1034,17 +1015,22 @@ class MORXCoverageFlagsTest(unittest.TestCase):
# Note that the lower 4 bits of the first byte are already
# part of the Reserved value. We test the full round-trip
# to encoding and decoding is quite hairy.
- data = bytesjoin([
- MORX_REARRANGEMENT_DATA[:28],
- bytechr(0x8A), bytechr(0xBC), bytechr(0xDE),
- MORX_REARRANGEMENT_DATA[31:]])
- table = newTable('morx')
+ data = bytesjoin(
+ [
+ MORX_REARRANGEMENT_DATA[:28],
+ bytechr(0x8A),
+ bytechr(0xBC),
+ bytechr(0xDE),
+ MORX_REARRANGEMENT_DATA[31:],
+ ]
+ )
+ table = newTable("morx")
table.decompile(data, self.font)
subtable = table.table.MorphChain[0].MorphSubtable[0]
self.assertEqual(subtable.Reserved, 0xABCDE)
xml = getXML(table.toXML)
self.assertIn(' <Reserved value="0xabcde"/>', xml)
- table2 = newTable('morx')
+ table2 = newTable("morx")
for name, attrs, content in parseXML(xml):
table2.fromXML(name, attrs, content, font=self.font)
self.assertEqual(hexStr(table2.compile(self.font)[28:31]), "8abcde")
@@ -1059,16 +1045,17 @@ class UnsupportedMorxLookupTest(unittest.TestCase):
self.assertRaisesRegex = self.assertRaisesRegexp
def test_unsupportedLookupType(self):
- data = bytesjoin([
- MORX_NONCONTEXTUAL_DATA[:67],
- bytechr(66),
- MORX_NONCONTEXTUAL_DATA[69:]])
- with self.assertRaisesRegex(AssertionError,
- r"unsupported 'morx' lookup type 66"):
- morx = newTable('morx')
- morx.decompile(data, FakeFont(['.notdef']))
+ data = bytesjoin(
+ [MORX_NONCONTEXTUAL_DATA[:67], bytechr(66), MORX_NONCONTEXTUAL_DATA[69:]]
+ )
+ with self.assertRaisesRegex(
+ AssertionError, r"unsupported 'morx' lookup type 66"
+ ):
+ morx = newTable("morx")
+ morx.decompile(data, FakeFont([".notdef"]))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_n_a_m_e_test.py b/Tests/ttLib/tables/_n_a_m_e_test.py
index 5e8a0c2b..6b3a0a70 100644
--- a/Tests/ttLib/tables/_n_a_m_e_test.py
+++ b/Tests/ttLib/tables/_n_a_m_e_test.py
@@ -8,556 +8,624 @@ import struct
import unittest
from fontTools.ttLib import TTFont, newTable
from fontTools.ttLib.tables._n_a_m_e import (
- table__n_a_m_e, NameRecord, nameRecordFormat, nameRecordSize, makeName, log)
+ table__n_a_m_e,
+ NameRecord,
+ nameRecordFormat,
+ nameRecordSize,
+ makeName,
+ log,
+)
def names(nameTable):
- result = [(n.nameID, n.platformID, n.platEncID, n.langID, n.string)
- for n in nameTable.names]
- result.sort()
- return result
+ result = [
+ (n.nameID, n.platformID, n.platEncID, n.langID, n.string)
+ for n in nameTable.names
+ ]
+ result.sort()
+ return result
class NameTableTest(unittest.TestCase):
-
- def test_getDebugName(self):
- table = table__n_a_m_e()
- table.names = [
- makeName("Bold", 258, 1, 0, 0), # Mac, MacRoman, English
- makeName("Gras", 258, 1, 0, 1), # Mac, MacRoman, French
- makeName("Fett", 258, 1, 0, 2), # Mac, MacRoman, German
- makeName("Sem Fracções", 292, 1, 0, 8) # Mac, MacRoman, Portuguese
- ]
- self.assertEqual("Bold", table.getDebugName(258))
- self.assertEqual("Sem Fracções", table.getDebugName(292))
- self.assertEqual(None, table.getDebugName(999))
-
- def test_setName(self):
- table = table__n_a_m_e()
- table.setName("Regular", 2, 1, 0, 0)
- table.setName("Version 1.000", 5, 3, 1, 0x409)
- table.setName("寬鬆", 276, 1, 2, 0x13)
- self.assertEqual("Regular", table.getName(2, 1, 0, 0).toUnicode())
- self.assertEqual("Version 1.000", table.getName(5, 3, 1, 0x409).toUnicode())
- self.assertEqual("寬鬆", table.getName(276, 1, 2, 0x13).toUnicode())
- self.assertTrue(len(table.names) == 3)
- table.setName("緊縮", 276, 1, 2, 0x13)
- self.assertEqual("緊縮", table.getName(276, 1, 2, 0x13).toUnicode())
- self.assertTrue(len(table.names) == 3)
- # passing bytes issues a warning
- with CapturingLogHandler(log, "WARNING") as captor:
- table.setName(b"abc", 0, 1, 0, 0)
- self.assertTrue(
- len([r for r in captor.records if "string is bytes" in r.msg]) == 1)
- # anything other than unicode or bytes raises an error
- 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 = []
- for string in ("Width", "Weight", "Custom"):
- nameIDs.append(table.addName(string))
-
- self.assertEqual(nameIDs[0], 256)
- self.assertEqual(nameIDs[1], 257)
- self.assertEqual(nameIDs[2], 258)
- self.assertEqual(len(table.names), 6)
- self.assertEqual(table.names[0].string, "Width")
- self.assertEqual(table.names[1].string, "Width")
- self.assertEqual(table.names[2].string, "Weight")
- self.assertEqual(table.names[3].string, "Weight")
- self.assertEqual(table.names[4].string, "Custom")
- self.assertEqual(table.names[5].string, "Custom")
-
- with self.assertRaises(ValueError):
- table.addName('Invalid nameID', minNameID=32767)
- 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).
- # In this case, we expect that the implementation just
- # encodes the name for the Windows platform; Apple platforms
- # have been able to decode Windows names since the early days
- # of OSX (~2001). However, Windows has no language code for
- # “Swiss German as used in Liechtenstein” (gsw-LI), so we
- # expect that the implementation populates the 'ltag' table
- # to represent that particular, rather exotic BCP47 code.
- font = FakeFont(glyphs=[".notdef", "A"])
- nameTable = font.tables['name'] = newTable("name")
- with CapturingLogHandler(log, "WARNING") as captor:
- widthID = nameTable.addMultilingualName({
- "en": "Width",
- "de-CH": "Breite",
- "gsw-LI": "Bräiti",
- }, ttFont=font, mac=False)
- self.assertEqual(widthID, 256)
- xHeightID = nameTable.addMultilingualName({
- "en": "X-Height",
- "gsw-LI": "X-Hööchi"
- }, ttFont=font, mac=False)
- self.assertEqual(xHeightID, 257)
- captor.assertRegex("cannot add Windows name in language gsw-LI")
- self.assertEqual(names(nameTable), [
- (256, 0, 4, 0, "Bräiti"),
- (256, 3, 1, 0x0409, "Width"),
- (256, 3, 1, 0x0807, "Breite"),
- (257, 0, 4, 0, "X-Hööchi"),
- (257, 3, 1, 0x0409, "X-Height"),
- ])
- self.assertEqual(set(font.tables.keys()), {"ltag", "name"})
- self.assertEqual(font["ltag"].tags, ["gsw-LI"])
-
- def test_addMultilingualName_legacyMacEncoding(self):
- # Windows has no language code for Latin; MacOS has a code;
- # and we actually can convert the name to the legacy MacRoman
- # encoding. In this case, we expect that the name gets encoded
- # as Macintosh name (platformID 1) with the corresponding Mac
- # language code (133); the 'ltag' table should not be used.
- font = FakeFont(glyphs=[".notdef", "A"])
- nameTable = font.tables['name'] = newTable("name")
- with CapturingLogHandler(log, "WARNING") as captor:
- nameTable.addMultilingualName({"la": "SPQR"},
- ttFont=font)
- captor.assertRegex("cannot add Windows name in language la")
- self.assertEqual(names(nameTable), [(256, 1, 0, 131, "SPQR")])
- self.assertNotIn("ltag", font.tables.keys())
-
- def test_addMultilingualName_legacyMacEncodingButUnencodableName(self):
- # Windows has no language code for Latin; MacOS has a code;
- # but we cannot encode the name into this encoding because
- # it contains characters that are not representable.
- # In this case, we expect that the name gets encoded as
- # Unicode name (platformID 0) with the language tag being
- # added to the 'ltag' table.
- font = FakeFont(glyphs=[".notdef", "A"])
- nameTable = font.tables['name'] = newTable("name")
- with CapturingLogHandler(log, "WARNING") as captor:
- nameTable.addMultilingualName({"la": "ⱾƤℚⱤ"},
- ttFont=font)
- captor.assertRegex("cannot add Windows name in language la")
- self.assertEqual(names(nameTable), [(256, 0, 4, 0, "ⱾƤℚⱤ")])
- self.assertIn("ltag", font.tables)
- self.assertEqual(font["ltag"].tags, ["la"])
-
- def test_addMultilingualName_legacyMacEncodingButNoCodec(self):
- # Windows has no language code for “Azeri written in the
- # Arabic script” (az-Arab); MacOS would have a code (50);
- # but we cannot encode the name into the legacy encoding
- # because we have no codec for MacArabic in fonttools.
- # In this case, we expect that the name gets encoded as
- # Unicode name (platformID 0) with the language tag being
- # added to the 'ltag' table.
- font = FakeFont(glyphs=[".notdef", "A"])
- nameTable = font.tables['name'] = newTable("name")
- with CapturingLogHandler(log, "WARNING") as captor:
- nameTable.addMultilingualName({"az-Arab": "آذربايجان ديلی"},
- ttFont=font)
- captor.assertRegex("cannot add Windows name in language az-Arab")
- self.assertEqual(names(nameTable), [(256, 0, 4, 0, "آذربايجان ديلی")])
- self.assertIn("ltag", font.tables)
- self.assertEqual(font["ltag"].tags, ["az-Arab"])
-
- def test_addMultilingualName_noTTFont(self):
- # If the ttFont argument is not passed, the implementation
- # should add whatever names it can, but it should not crash
- # just because it cannot build an ltag table.
- nameTable = newTable("name")
- with CapturingLogHandler(log, "WARNING") as captor:
- 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_addMultilingualName_name_inconsistencies(self):
- # Check what happens, when there are
- # inconsistencies in the name table
- table = table__n_a_m_e()
- table.setName('Weight', 270, 3, 1, 0x409)
- names = {'en': 'Weight', }
- nameID = table.addMultilingualName(names, minNameID=256)
- # Because there is an inconsistency in the names,
- # addMultilingualName adds a new name ID
- self.assertEqual(271, nameID)
-
- def test_decompile_badOffset(self):
- # https://github.com/fonttools/fonttools/issues/525
- table = table__n_a_m_e()
- badRecord = {
- "platformID": 1,
- "platEncID": 3,
- "langID": 7,
- "nameID": 1,
- "length": 3,
- "offset": 8765 # out of range
- }
- data = bytesjoin([
- struct.pack(tostr(">HHH"), 1, 1, 6 + nameRecordSize),
- sstruct.pack(nameRecordFormat, badRecord)])
- table.decompile(data, ttFont=None)
- self.assertEqual(table.names, [])
+ def test_getDebugName(self):
+ table = table__n_a_m_e()
+ table.names = [
+ makeName("Bold", 258, 1, 0, 0), # Mac, MacRoman, English
+ makeName("Gras", 258, 1, 0, 1), # Mac, MacRoman, French
+ makeName("Fett", 258, 1, 0, 2), # Mac, MacRoman, German
+ makeName("Sem Fracções", 292, 1, 0, 8), # Mac, MacRoman, Portuguese
+ ]
+ self.assertEqual("Bold", table.getDebugName(258))
+ self.assertEqual("Sem Fracções", table.getDebugName(292))
+ self.assertEqual(None, table.getDebugName(999))
+
+ def test_setName(self):
+ table = table__n_a_m_e()
+ table.setName("Regular", 2, 1, 0, 0)
+ table.setName("Version 1.000", 5, 3, 1, 0x409)
+ table.setName("寬鬆", 276, 1, 2, 0x13)
+ self.assertEqual("Regular", table.getName(2, 1, 0, 0).toUnicode())
+ self.assertEqual("Version 1.000", table.getName(5, 3, 1, 0x409).toUnicode())
+ self.assertEqual("寬鬆", table.getName(276, 1, 2, 0x13).toUnicode())
+ self.assertTrue(len(table.names) == 3)
+ table.setName("緊縮", 276, 1, 2, 0x13)
+ self.assertEqual("緊縮", table.getName(276, 1, 2, 0x13).toUnicode())
+ self.assertTrue(len(table.names) == 3)
+ # passing bytes issues a warning
+ with CapturingLogHandler(log, "WARNING") as captor:
+ table.setName(b"abc", 0, 1, 0, 0)
+ self.assertTrue(
+ len([r for r in captor.records if "string is bytes" in r.msg]) == 1
+ )
+ # anything other than unicode or bytes raises an error
+ 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_attributes(self):
+ table = table__n_a_m_e()
+ # Create an actual invalid NameRecord object
+ broken = makeName("Test", 25, 3, 1, 0x409)
+ delattr(broken, "platformID")
+ table.names = [
+ makeName("Test", 25, 3, 1, 0x409),
+ broken,
+ ]
+ # Sorting these two is impossible, expect an error to be raised
+ with self.assertRaises(TypeError):
+ table.names.sort()
+
+ def test_names_sort_encoding(self):
+ """
+ Confirm that encoding errors in name table strings do not prevent at
+ least sorting by other IDs
+ """
+ table = table__n_a_m_e()
+ table.names = [
+ makeName("Mac Unicode 寬 encodes ok", 25, 3, 0, 0x409),
+ makeName("Win Latin 寬 fails to encode", 25, 1, 0, 0),
+ ]
+ table.names.sort()
+ # Encoding errors or not, sort based on other IDs nonetheless
+ self.assertEqual(table.names[0].platformID, 1)
+ self.assertEqual(table.names[1].platformID, 3)
+
+ def test_addName(self):
+ table = table__n_a_m_e()
+ nameIDs = []
+ for string in ("Width", "Weight", "Custom"):
+ nameIDs.append(table.addName(string))
+
+ self.assertEqual(nameIDs[0], 256)
+ self.assertEqual(nameIDs[1], 257)
+ self.assertEqual(nameIDs[2], 258)
+ self.assertEqual(len(table.names), 6)
+ self.assertEqual(table.names[0].string, "Width")
+ self.assertEqual(table.names[1].string, "Width")
+ self.assertEqual(table.names[2].string, "Weight")
+ self.assertEqual(table.names[3].string, "Weight")
+ self.assertEqual(table.names[4].string, "Custom")
+ self.assertEqual(table.names[5].string, "Custom")
+
+ with self.assertRaises(ValueError):
+ table.addName("Invalid nameID", minNameID=32767)
+ 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).
+ # In this case, we expect that the implementation just
+ # encodes the name for the Windows platform; Apple platforms
+ # have been able to decode Windows names since the early days
+ # of OSX (~2001). However, Windows has no language code for
+ # “Swiss German as used in Liechtenstein” (gsw-LI), so we
+ # expect that the implementation populates the 'ltag' table
+ # to represent that particular, rather exotic BCP47 code.
+ font = FakeFont(glyphs=[".notdef", "A"])
+ nameTable = font.tables["name"] = newTable("name")
+ with CapturingLogHandler(log, "WARNING") as captor:
+ widthID = nameTable.addMultilingualName(
+ {
+ "en": "Width",
+ "de-CH": "Breite",
+ "gsw-LI": "Bräiti",
+ },
+ ttFont=font,
+ mac=False,
+ )
+ self.assertEqual(widthID, 256)
+ xHeightID = nameTable.addMultilingualName(
+ {"en": "X-Height", "gsw-LI": "X-Hööchi"}, ttFont=font, mac=False
+ )
+ self.assertEqual(xHeightID, 257)
+ captor.assertRegex("cannot add Windows name in language gsw-LI")
+ self.assertEqual(
+ names(nameTable),
+ [
+ (256, 0, 4, 0, "Bräiti"),
+ (256, 3, 1, 0x0409, "Width"),
+ (256, 3, 1, 0x0807, "Breite"),
+ (257, 0, 4, 0, "X-Hööchi"),
+ (257, 3, 1, 0x0409, "X-Height"),
+ ],
+ )
+ self.assertEqual(set(font.tables.keys()), {"ltag", "name"})
+ self.assertEqual(font["ltag"].tags, ["gsw-LI"])
+
+ def test_addMultilingualName_legacyMacEncoding(self):
+ # Windows has no language code for Latin; MacOS has a code;
+ # and we actually can convert the name to the legacy MacRoman
+ # encoding. In this case, we expect that the name gets encoded
+ # as Macintosh name (platformID 1) with the corresponding Mac
+ # language code (133); the 'ltag' table should not be used.
+ font = FakeFont(glyphs=[".notdef", "A"])
+ nameTable = font.tables["name"] = newTable("name")
+ with CapturingLogHandler(log, "WARNING") as captor:
+ nameTable.addMultilingualName({"la": "SPQR"}, ttFont=font)
+ captor.assertRegex("cannot add Windows name in language la")
+ self.assertEqual(names(nameTable), [(256, 1, 0, 131, "SPQR")])
+ self.assertNotIn("ltag", font.tables.keys())
+
+ def test_addMultilingualName_legacyMacEncodingButUnencodableName(self):
+ # Windows has no language code for Latin; MacOS has a code;
+ # but we cannot encode the name into this encoding because
+ # it contains characters that are not representable.
+ # In this case, we expect that the name gets encoded as
+ # Unicode name (platformID 0) with the language tag being
+ # added to the 'ltag' table.
+ font = FakeFont(glyphs=[".notdef", "A"])
+ nameTable = font.tables["name"] = newTable("name")
+ with CapturingLogHandler(log, "WARNING") as captor:
+ nameTable.addMultilingualName({"la": "ⱾƤℚⱤ"}, ttFont=font)
+ captor.assertRegex("cannot add Windows name in language la")
+ self.assertEqual(names(nameTable), [(256, 0, 4, 0, "ⱾƤℚⱤ")])
+ self.assertIn("ltag", font.tables)
+ self.assertEqual(font["ltag"].tags, ["la"])
+
+ def test_addMultilingualName_legacyMacEncodingButNoCodec(self):
+ # Windows has no language code for “Azeri written in the
+ # Arabic script” (az-Arab); MacOS would have a code (50);
+ # but we cannot encode the name into the legacy encoding
+ # because we have no codec for MacArabic in fonttools.
+ # In this case, we expect that the name gets encoded as
+ # Unicode name (platformID 0) with the language tag being
+ # added to the 'ltag' table.
+ font = FakeFont(glyphs=[".notdef", "A"])
+ nameTable = font.tables["name"] = newTable("name")
+ with CapturingLogHandler(log, "WARNING") as captor:
+ nameTable.addMultilingualName({"az-Arab": "آذربايجان ديلی"}, ttFont=font)
+ captor.assertRegex("cannot add Windows name in language az-Arab")
+ self.assertEqual(names(nameTable), [(256, 0, 4, 0, "آذربايجان ديلی")])
+ self.assertIn("ltag", font.tables)
+ self.assertEqual(font["ltag"].tags, ["az-Arab"])
+
+ def test_addMultilingualName_noTTFont(self):
+ # If the ttFont argument is not passed, the implementation
+ # should add whatever names it can, but it should not crash
+ # just because it cannot build an ltag table.
+ nameTable = newTable("name")
+ with CapturingLogHandler(log, "WARNING") as captor:
+ nameTable.addMultilingualName({"en": "A", "la": "ⱾƤℚⱤ"})
+ captor.assertRegex("cannot store language la into 'ltag' table")
+
+ def test_addMultilingualName_TTFont(self):
+ # if ttFont argument is passed, it should not WARN about not being able
+ # to create ltag table.
+ font = FakeFont(glyphs=[".notdef", "A"])
+ nameTable = newTable("name")
+ with CapturingLogHandler(log, "WARNING") as captor:
+ nameTable.addMultilingualName({"en": "A", "ar": "ع"}, ttFont=font)
+ self.assertFalse(captor.records)
+
+ 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_addMultilingualName_name_inconsistencies(self):
+ # Check what happens, when there are
+ # inconsistencies in the name table
+ table = table__n_a_m_e()
+ table.setName("Weight", 270, 3, 1, 0x409)
+ names = {
+ "en": "Weight",
+ }
+ nameID = table.addMultilingualName(names, minNameID=256)
+ # Because there is an inconsistency in the names,
+ # addMultilingualName adds a new name ID
+ self.assertEqual(271, nameID)
+
+ def test_decompile_badOffset(self):
+ # https://github.com/fonttools/fonttools/issues/525
+ table = table__n_a_m_e()
+ badRecord = {
+ "platformID": 1,
+ "platEncID": 3,
+ "langID": 7,
+ "nameID": 1,
+ "length": 3,
+ "offset": 8765, # out of range
+ }
+ data = bytesjoin(
+ [
+ struct.pack(tostr(">HHH"), 1, 1, 6 + nameRecordSize),
+ sstruct.pack(nameRecordFormat, badRecord),
+ ]
+ )
+ table.decompile(data, ttFont=None)
+ self.assertEqual(table.names, [])
class NameRecordTest(unittest.TestCase):
-
- def test_toUnicode_utf16be(self):
- name = makeName("Foo Bold", 111, 0, 2, 7)
- self.assertEqual("utf_16_be", name.getEncoding())
- self.assertEqual("Foo Bold", name.toUnicode())
-
- def test_toUnicode_macroman(self):
- name = makeName("Foo Italic", 222, 1, 0, 7) # MacRoman
- self.assertEqual("mac_roman", name.getEncoding())
- self.assertEqual("Foo Italic", name.toUnicode())
-
- 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"+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)
- xml = writer.file.getvalue().decode("utf_8").strip()
- return xml.split(writer.newlinestr.decode("utf_8"))[1:]
-
- def test_toXML_utf16be(self):
- name = makeName("Foo Bold", 111, 0, 2, 7)
- self.assertEqual([
- '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
- ' Foo Bold',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_toXML_utf16be_odd_length1(self):
- name = makeName(b"\0F\0o\0o\0", 111, 0, 2, 7)
- self.assertEqual([
- '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
- ' Foo',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_toXML_utf16be_odd_length2(self):
- name = makeName(b"\0Fooz", 111, 0, 2, 7)
- self.assertEqual([
- '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
- ' Fooz',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_toXML_utf16be_double_encoded(self):
- name = makeName(b"\0\0\0F\0\0\0o", 111, 0, 2, 7)
- self.assertEqual([
- '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
- ' Fo',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_toXML_macroman(self):
- name = makeName("Foo Italic", 222, 1, 0, 7) # MacRoman
- self.assertEqual([
- '<namerecord nameID="222" platformID="1" platEncID="0" langID="0x7" unicode="True">',
- ' Foo Italic',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_toXML_macroman_actual_utf16be(self):
- name = makeName("\0F\0o\0o", 222, 1, 0, 7)
- self.assertEqual([
- '<namerecord nameID="222" platformID="1" platEncID="0" langID="0x7" unicode="True">',
- ' Foo',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_toXML_unknownPlatEncID_nonASCII(self):
- name = makeName(b"B\x8arli", 333, 1, 9876, 7) # Unknown Mac encodingID
- self.assertEqual([
- '<namerecord nameID="333" platformID="1" platEncID="9876" langID="0x7" unicode="False">',
- ' B&#138;rli',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_toXML_unknownPlatEncID_ASCII(self):
- name = makeName(b"Barli", 333, 1, 9876, 7) # Unknown Mac encodingID
- self.assertEqual([
- '<namerecord nameID="333" platformID="1" platEncID="9876" langID="0x7" unicode="True">',
- ' Barli',
- '</namerecord>'
- ], self.toXML(name))
-
- def test_encoding_macroman_misc(self):
- name = makeName('', 123, 1, 0, 17) # Mac Turkish
- self.assertEqual(name.getEncoding(), "mac_turkish")
- name.langID = 37
- self.assertEqual(name.getEncoding(), "mac_romanian")
- name.langID = 45 # Other
- self.assertEqual(name.getEncoding(), "mac_roman")
-
- def test_extended_mac_encodings(self):
- name = makeName(b'\xfe', 123, 1, 1, 0) # Mac Japanese
- 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)
- self.assertEqual(name.getEncoding(), "ascii")
- self.assertEqual(name.getEncoding(None), None)
- self.assertEqual(name.getEncoding(default=None), None)
-
- def test_get_family_name(self):
- name = table__n_a_m_e()
- name.names = [
- makeName("Copyright", 0, 1, 0, 0),
- makeName("Family Name ID 1", 1, 1, 0, 0),
- makeName("SubFamily Name ID 2", 2, 1, 0, 0),
- makeName("Unique Name ID 3", 3, 1, 0, 0),
- makeName("Full Name ID 4", 4, 1, 0, 0),
- makeName("PS Name ID 6", 6, 1, 0, 0),
- makeName("Version Name ID 5", 5, 1, 0, 0),
- makeName("Trademark Name ID 7", 7, 1, 0, 0),
- ]
-
- result_value = name.getBestFamilyName()
- self.assertEqual("Family Name ID 1", result_value)
-
- expected_value = "Family Name ID 16"
- name.setName(expected_value, 16, 1, 0, 0)
- result_value = name.getBestFamilyName()
- self.assertEqual(expected_value, result_value)
-
- expected_value = "Family Name ID 21"
- name.setName(expected_value, 21, 1, 0, 0)
- result_value = name.getBestFamilyName()
- self.assertEqual(expected_value, result_value)
-
- def test_get_subfamily_name(self):
- name = table__n_a_m_e()
- name.names = [
- makeName("Copyright", 0, 1, 0, 0),
- makeName("Family Name ID 1", 1, 1, 0, 0),
- makeName("SubFamily Name ID 2", 2, 1, 0, 0),
- makeName("Unique Name ID 3", 3, 1, 0, 0),
- makeName("Full Name ID 4", 4, 1, 0, 0),
- makeName("PS Name ID 6", 6, 1, 0, 0),
- makeName("Version Name ID 5", 5, 1, 0, 0),
- makeName("Trademark Name ID 7", 7, 1, 0, 0),
- ]
-
- result_value = name.getBestSubFamilyName()
- self.assertEqual("SubFamily Name ID 2", result_value)
-
- expected_value = "Family Name ID 17"
- name.setName(expected_value, 17, 1, 0, 0)
- result_value = name.getBestSubFamilyName()
- self.assertEqual(expected_value, result_value)
-
- expected_value = "Family Name ID 22"
- name.setName(expected_value, 22, 1, 0, 0)
- result_value = name.getBestSubFamilyName()
- self.assertEqual(expected_value, result_value)
-
- def test_get_nice_full_name(self):
- name = table__n_a_m_e()
- name.names = [
- makeName("NID 1", 1, 1, 0, 0),
- makeName("NID 2", 2, 1, 0, 0),
- makeName("NID 4", 4, 1, 0, 0),
- makeName("NID 6", 6, 1, 0, 0),
- ]
-
- result_value = name.getBestFullName()
- self.assertEqual("NID 1 NID 2", result_value)
-
- expected_value = "NID 1 NID 2"
- # expection is still NID 1 NID 2,
- # because name ID 17 is missing
- name.setName("NID 16", 16, 1, 0, 0)
- result_value = name.getBestFullName()
- self.assertEqual(expected_value, result_value)
-
- name.setName('NID 17', 17, 1, 0, 0)
- result_value = name.getBestFullName()
- self.assertEqual("NID 16 NID 17", result_value)
-
- expected_value = "NID 16 NID 17"
- # expection is still NID 16 NID 17,
- # because name ID 21 is missing
- name.setName('NID 21', 21, 1, 0, 0)
- result_value = name.getBestFullName()
- self.assertEqual(expected_value, result_value)
-
- name.setName('NID 22', 22, 1, 0, 0)
- result_value = name.getBestFullName()
- self.assertEqual("NID 21 NID 22", result_value)
-
- for NID in [2, 16, 17, 21, 22]:
- name.removeNames(NID)
-
- result_value = name.getBestFullName()
- self.assertEqual("NID 4", result_value)
-
- name.setName('Regular', 2, 1, 0, 0)
- result_value = name.getBestFullName()
- self.assertEqual("NID 1", result_value)
+ def test_toUnicode_utf16be(self):
+ name = makeName("Foo Bold", 111, 0, 2, 7)
+ self.assertEqual("utf_16_be", name.getEncoding())
+ self.assertEqual("Foo Bold", name.toUnicode())
+
+ def test_toUnicode_macroman(self):
+ name = makeName("Foo Italic", 222, 1, 0, 7) # MacRoman
+ self.assertEqual("mac_roman", name.getEncoding())
+ self.assertEqual("Foo Italic", name.toUnicode())
+
+ 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" + 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)
+ xml = writer.file.getvalue().decode("utf_8").strip()
+ return xml.split(writer.newlinestr.decode("utf_8"))[1:]
+
+ def test_toXML_utf16be(self):
+ name = makeName("Foo Bold", 111, 0, 2, 7)
+ self.assertEqual(
+ [
+ '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
+ " Foo Bold",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_toXML_utf16be_odd_length1(self):
+ name = makeName(b"\0F\0o\0o\0", 111, 0, 2, 7)
+ self.assertEqual(
+ [
+ '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
+ " Foo",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_toXML_utf16be_odd_length2(self):
+ name = makeName(b"\0Fooz", 111, 0, 2, 7)
+ self.assertEqual(
+ [
+ '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
+ " Fooz",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_toXML_utf16be_double_encoded(self):
+ name = makeName(b"\0\0\0F\0\0\0o", 111, 0, 2, 7)
+ self.assertEqual(
+ [
+ '<namerecord nameID="111" platformID="0" platEncID="2" langID="0x7">',
+ " Fo",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_toXML_macroman(self):
+ name = makeName("Foo Italic", 222, 1, 0, 7) # MacRoman
+ self.assertEqual(
+ [
+ '<namerecord nameID="222" platformID="1" platEncID="0" langID="0x7" unicode="True">',
+ " Foo Italic",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_toXML_macroman_actual_utf16be(self):
+ name = makeName("\0F\0o\0o", 222, 1, 0, 7)
+ self.assertEqual(
+ [
+ '<namerecord nameID="222" platformID="1" platEncID="0" langID="0x7" unicode="True">',
+ " Foo",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_toXML_unknownPlatEncID_nonASCII(self):
+ name = makeName(b"B\x8arli", 333, 1, 9876, 7) # Unknown Mac encodingID
+ self.assertEqual(
+ [
+ '<namerecord nameID="333" platformID="1" platEncID="9876" langID="0x7" unicode="False">',
+ " B&#138;rli",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_toXML_unknownPlatEncID_ASCII(self):
+ name = makeName(b"Barli", 333, 1, 9876, 7) # Unknown Mac encodingID
+ self.assertEqual(
+ [
+ '<namerecord nameID="333" platformID="1" platEncID="9876" langID="0x7" unicode="True">',
+ " Barli",
+ "</namerecord>",
+ ],
+ self.toXML(name),
+ )
+
+ def test_encoding_macroman_misc(self):
+ name = makeName("", 123, 1, 0, 17) # Mac Turkish
+ self.assertEqual(name.getEncoding(), "mac_turkish")
+ name.langID = 37
+ self.assertEqual(name.getEncoding(), "mac_romanian")
+ name.langID = 45 # Other
+ self.assertEqual(name.getEncoding(), "mac_roman")
+
+ def test_extended_mac_encodings(self):
+ name = makeName(b"\xfe", 123, 1, 1, 0) # Mac Japanese
+ 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)
+ self.assertEqual(name.getEncoding(), "ascii")
+ self.assertEqual(name.getEncoding(None), None)
+ self.assertEqual(name.getEncoding(default=None), None)
+
+ def test_get_family_name(self):
+ name = table__n_a_m_e()
+ name.names = [
+ makeName("Copyright", 0, 1, 0, 0),
+ makeName("Family Name ID 1", 1, 1, 0, 0),
+ makeName("SubFamily Name ID 2", 2, 1, 0, 0),
+ makeName("Unique Name ID 3", 3, 1, 0, 0),
+ makeName("Full Name ID 4", 4, 1, 0, 0),
+ makeName("PS Name ID 6", 6, 1, 0, 0),
+ makeName("Version Name ID 5", 5, 1, 0, 0),
+ makeName("Trademark Name ID 7", 7, 1, 0, 0),
+ ]
+
+ result_value = name.getBestFamilyName()
+ self.assertEqual("Family Name ID 1", result_value)
+
+ expected_value = "Family Name ID 16"
+ name.setName(expected_value, 16, 1, 0, 0)
+ result_value = name.getBestFamilyName()
+ self.assertEqual(expected_value, result_value)
+
+ expected_value = "Family Name ID 21"
+ name.setName(expected_value, 21, 1, 0, 0)
+ result_value = name.getBestFamilyName()
+ self.assertEqual(expected_value, result_value)
+
+ def test_get_subfamily_name(self):
+ name = table__n_a_m_e()
+ name.names = [
+ makeName("Copyright", 0, 1, 0, 0),
+ makeName("Family Name ID 1", 1, 1, 0, 0),
+ makeName("SubFamily Name ID 2", 2, 1, 0, 0),
+ makeName("Unique Name ID 3", 3, 1, 0, 0),
+ makeName("Full Name ID 4", 4, 1, 0, 0),
+ makeName("PS Name ID 6", 6, 1, 0, 0),
+ makeName("Version Name ID 5", 5, 1, 0, 0),
+ makeName("Trademark Name ID 7", 7, 1, 0, 0),
+ ]
+
+ result_value = name.getBestSubFamilyName()
+ self.assertEqual("SubFamily Name ID 2", result_value)
+
+ expected_value = "Family Name ID 17"
+ name.setName(expected_value, 17, 1, 0, 0)
+ result_value = name.getBestSubFamilyName()
+ self.assertEqual(expected_value, result_value)
+
+ expected_value = "Family Name ID 22"
+ name.setName(expected_value, 22, 1, 0, 0)
+ result_value = name.getBestSubFamilyName()
+ self.assertEqual(expected_value, result_value)
+
+ def test_get_nice_full_name(self):
+ name = table__n_a_m_e()
+ name.names = [
+ makeName("NID 1", 1, 1, 0, 0),
+ makeName("NID 2", 2, 1, 0, 0),
+ makeName("NID 4", 4, 1, 0, 0),
+ makeName("NID 6", 6, 1, 0, 0),
+ ]
+
+ result_value = name.getBestFullName()
+ self.assertEqual("NID 1 NID 2", result_value)
+
+ expected_value = "NID 1 NID 2"
+ # expection is still NID 1 NID 2,
+ # because name ID 17 is missing
+ name.setName("NID 16", 16, 1, 0, 0)
+ result_value = name.getBestFullName()
+ self.assertEqual(expected_value, result_value)
+
+ name.setName("NID 17", 17, 1, 0, 0)
+ result_value = name.getBestFullName()
+ self.assertEqual("NID 16 NID 17", result_value)
+
+ expected_value = "NID 16 NID 17"
+ # expection is still NID 16 NID 17,
+ # because name ID 21 is missing
+ name.setName("NID 21", 21, 1, 0, 0)
+ result_value = name.getBestFullName()
+ self.assertEqual(expected_value, result_value)
+
+ name.setName("NID 22", 22, 1, 0, 0)
+ result_value = name.getBestFullName()
+ self.assertEqual("NID 21 NID 22", result_value)
+
+ for NID in [2, 16, 17, 21, 22]:
+ name.removeNames(NID)
+
+ result_value = name.getBestFullName()
+ self.assertEqual("NID 4", result_value)
+
+ name.setName("Regular", 2, 1, 0, 0)
+ result_value = name.getBestFullName()
+ self.assertEqual("NID 1", result_value)
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_o_p_b_d_test.py b/Tests/ttLib/tables/_o_p_b_d_test.py
index d62ada8b..24020e31 100644
--- a/Tests/ttLib/tables/_o_p_b_d_test.py
+++ b/Tests/ttLib/tables/_o_p_b_d_test.py
@@ -7,72 +7,72 @@ import unittest
# Example: Format 0 Optical Bounds Table
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6opbd.html
OPBD_FORMAT_0_DATA = deHexStr(
- '0001 0000 0000 ' # 0: Version=1.0, Format=0
- '0006 0004 0002 ' # 6: LookupFormat=6, UnitSize=4, NUnits=2
- '0008 0001 0000 ' # 12: SearchRange=8, EntrySelector=1, RangeShift=0
- '000A 001E ' # 18: Glyph=10(=C), OffsetOfOpticalBoundsDeltas=30
- '002B 0026 ' # 22: Glyph=43(=A), OffsetOfOpticalBoundsDeltas=38
- 'FFFF 0000 ' # 26: Glyph=<end>, OffsetOfOpticalBoundsDeltas=0
- 'FFCE 0005 0037 FFFB ' # 30: Bounds[C].Left=-50 .Top=5 .Right=55 .Bottom=-5
- 'FFF6 000F 0000 0000 ' # 38: Bounds[A].Left=-10 .Top=15 .Right=0 .Bottom=0
-) # 46: <end>
-assert(len(OPBD_FORMAT_0_DATA) == 46)
+ "0001 0000 0000 " # 0: Version=1.0, Format=0
+ "0006 0004 0002 " # 6: LookupFormat=6, UnitSize=4, NUnits=2
+ "0008 0001 0000 " # 12: SearchRange=8, EntrySelector=1, RangeShift=0
+ "000A 001E " # 18: Glyph=10(=C), OffsetOfOpticalBoundsDeltas=30
+ "002B 0026 " # 22: Glyph=43(=A), OffsetOfOpticalBoundsDeltas=38
+ "FFFF 0000 " # 26: Glyph=<end>, OffsetOfOpticalBoundsDeltas=0
+ "FFCE 0005 0037 FFFB " # 30: Bounds[C].Left=-50 .Top=5 .Right=55 .Bottom=-5
+ "FFF6 000F 0000 0000 " # 38: Bounds[A].Left=-10 .Top=15 .Right=0 .Bottom=0
+) # 46: <end>
+assert len(OPBD_FORMAT_0_DATA) == 46
OPBD_FORMAT_0_XML = [
'<Version value="0x00010000"/>',
'<OpticalBounds Format="0">',
- ' <OpticalBoundsDeltas>',
+ " <OpticalBoundsDeltas>",
' <Lookup glyph="A">',
' <Left value="-10"/>',
' <Top value="15"/>',
' <Right value="0"/>',
' <Bottom value="0"/>',
- ' </Lookup>',
+ " </Lookup>",
' <Lookup glyph="C">',
' <Left value="-50"/>',
' <Top value="5"/>',
' <Right value="55"/>',
' <Bottom value="-5"/>',
- ' </Lookup>',
- ' </OpticalBoundsDeltas>',
- '</OpticalBounds>',
+ " </Lookup>",
+ " </OpticalBoundsDeltas>",
+ "</OpticalBounds>",
]
# Example: Format 1 Optical Bounds Table
# https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6opbd.html
OPBD_FORMAT_1_DATA = deHexStr(
- '0001 0000 0001 ' # 0: Version=1.0, Format=1
- '0006 0004 0002 ' # 6: LookupFormat=6, UnitSize=4, NUnits=2
- '0008 0001 0000 ' # 12: SearchRange=8, EntrySelector=1, RangeShift=0
- '000A 001E ' # 18: Glyph=10(=C), OffsetOfOpticalBoundsPoints=30
- '002B 0026 ' # 22: Glyph=43(=A), OffsetOfOpticalBoundsPoints=38
- 'FFFF 0000 ' # 26: Glyph=<end>, OffsetOfOpticalBoundsPoints=0
- '0024 0025 0026 0027 ' # 30: Bounds[C].Left=36 .Top=37 .Right=38 .Bottom=39
- '0020 0029 FFFF FFFF ' # 38: Bounds[A].Left=32 .Top=41 .Right=-1 .Bottom=-1
-) # 46: <end>
-assert(len(OPBD_FORMAT_1_DATA) == 46)
+ "0001 0000 0001 " # 0: Version=1.0, Format=1
+ "0006 0004 0002 " # 6: LookupFormat=6, UnitSize=4, NUnits=2
+ "0008 0001 0000 " # 12: SearchRange=8, EntrySelector=1, RangeShift=0
+ "000A 001E " # 18: Glyph=10(=C), OffsetOfOpticalBoundsPoints=30
+ "002B 0026 " # 22: Glyph=43(=A), OffsetOfOpticalBoundsPoints=38
+ "FFFF 0000 " # 26: Glyph=<end>, OffsetOfOpticalBoundsPoints=0
+ "0024 0025 0026 0027 " # 30: Bounds[C].Left=36 .Top=37 .Right=38 .Bottom=39
+ "0020 0029 FFFF FFFF " # 38: Bounds[A].Left=32 .Top=41 .Right=-1 .Bottom=-1
+) # 46: <end>
+assert len(OPBD_FORMAT_1_DATA) == 46
OPBD_FORMAT_1_XML = [
'<Version value="0x00010000"/>',
'<OpticalBounds Format="1">',
- ' <OpticalBoundsPoints>',
+ " <OpticalBoundsPoints>",
' <Lookup glyph="A">',
' <Left value="32"/>',
' <Top value="41"/>',
' <Right value="-1"/>',
' <Bottom value="-1"/>',
- ' </Lookup>',
+ " </Lookup>",
' <Lookup glyph="C">',
' <Left value="36"/>',
' <Top value="37"/>',
' <Right value="38"/>',
' <Bottom value="39"/>',
- ' </Lookup>',
- ' </OpticalBoundsPoints>',
- '</OpticalBounds>',
+ " </Lookup>",
+ " </OpticalBoundsPoints>",
+ "</OpticalBounds>",
]
@@ -81,101 +81,99 @@ OPBD_FORMAT_1_XML = [
# was crashing when trying to decompile this table.
# https://github.com/fonttools/fonttools/issues/1031
OPBD_APPLE_CHANCERY_DATA = deHexStr(
- '0001 0000 0000 ' # 0: Version=1.0, Format=0
- '0004 0006 0011 ' # 6: LookupFormat=4, UnitSize=6, NUnits=17
- '0060 0004 0006 ' # 12: SearchRange=96, EntrySelector=4, RangeShift=6
- '017d 017d 0072 ' # 18: Seg[0].LastGlyph=381, FirstGlyph=381, Off=114(+6)
- '0183 0180 0074 ' # 24: Seg[1].LastGlyph=387, FirstGlyph=384, Off=116(+6)
- '0186 0185 007c ' # 30: Seg[2].LastGlyph=390, FirstGlyph=389, Off=124(+6)
- '018f 018b 0080 ' # 36: Seg[3].LastGlyph=399, FirstGlyph=395, Off=128(+6)
- '01a0 0196 008a ' # 42: Seg[4].LastGlyph=416, FirstGlyph=406, Off=138(+6)
- '01a5 01a3 00a0 ' # 48: Seg[5].LastGlyph=421, FirstGlyph=419, Off=160(+6)
- '01aa 01aa 00a6 ' # 54: Seg[6].LastGlyph=426, FirstGlyph=426, Off=166(+6)
- '01ac 01ac 00a8 ' # 60: Seg[7].LastGlyph=428, FirstGlyph=428, Off=168(+6)
- '01fb 01f1 00aa ' # 66: Seg[8].LastGlyph=507, FirstGlyph=497, Off=170(+6)
- '0214 0209 00c0 ' # 72: Seg[9].LastGlyph=532, FirstGlyph=521, Off=192(+6)
- '021d 0216 00d8 ' # 78: Seg[10].LastGlyph=541, FirstGlyph=534, Off=216(+6)
- '0222 0220 00e8 ' # 84: Seg[11].LastGlyph=546, FirstGlyph=544, Off=232(+6)
- '0227 0225 00ee ' # 90: Seg[12].LastGlyph=551, FirstGlyph=549, Off=238(+6)
- '0229 0229 00f4 ' # 96: Seg[13].LastGlyph=553, FirstGlyph=553, Off=244(+6)
- '023b 023b 00f6 ' # 102: Seg[14].LastGlyph=571, FirstGlyph=571, Off=246(+6)
- '023e 023e 00f8 ' # 108: Seg[15].LastGlyph=574, FirstGlyph=574, Off=248(+6)
- 'ffff ffff 00fa ' # 114: Seg[16]=<end>
- '0100 0108 0110 0118 0120 0128 0130 0138 0140 0148 0150 0158 '
- '0160 0168 0170 0178 0180 0188 0190 0198 01a0 01a8 01b0 01b8 '
- '01c0 01c8 01d0 01d8 01e0 01e8 01f0 01f8 0200 0208 0210 0218 '
- '0220 0228 0230 0238 0240 0248 0250 0258 0260 0268 0270 0278 '
- '0280 0288 0290 0298 02a0 02a8 02b0 02b8 02c0 02c8 02d0 02d8 '
- '02e0 02e8 02f0 02f8 0300 0308 0310 0318 fd98 0000 0000 0000 '
- 'fdbc 0000 0000 0000 fdbc 0000 0000 0000 fdbf 0000 0000 0000 '
- 'fdbc 0000 0000 0000 fd98 0000 0000 0000 fda9 0000 0000 0000 '
- 'fd98 0000 0000 0000 fd98 0000 0000 0000 fd98 0000 0000 0000 '
- '0000 0000 0205 0000 0000 0000 0205 0000 0000 0000 02a4 0000 '
- '0000 0000 027e 0000 0000 0000 02f4 0000 0000 0000 02a4 0000 '
- '0000 0000 0365 0000 0000 0000 0291 0000 0000 0000 0291 0000 '
- '0000 0000 026a 0000 0000 0000 02b8 0000 0000 0000 02cb 0000 '
- '0000 0000 02a4 0000 0000 0000 01a9 0000 0000 0000 0244 0000 '
- '0000 0000 02a4 0000 0000 0000 02cb 0000 0000 0000 0244 0000 '
- '0000 0000 0307 0000 0000 0000 0307 0000 0000 0000 037f 0000 '
- '0000 0000 0307 0000 0000 0000 0307 0000 0000 0000 0307 0000 '
- '0000 0000 0307 0000 0000 0000 0307 0000 0000 0000 03e3 0000 '
- '0000 0000 030c 0000 0000 0000 0307 0000 fe30 0000 0000 0000 '
- 'fe7e 0000 0000 0000 fe91 0000 0000 0000 fe6a 0000 0000 0000 '
- 'fe6a 0000 0000 0000 fecb 0000 0000 0000 fe6a 0000 0000 0000 '
- 'fe7e 0000 0000 0000 fea4 0000 0000 0000 fe7e 0000 0000 0000 '
- 'fe44 0000 0000 0000 fea4 0000 0000 0000 feb8 0000 0000 0000 '
- 'fe7e 0000 0000 0000 fe5e 0000 0000 0000 fe37 0000 0000 0000 '
- 'fe37 0000 0000 0000 fcbd 0000 0000 0000 fd84 0000 0000 0000 '
- 'fd98 0000 0000 0000 fd82 0000 0000 0000 fcbd 0000 0000 0000 '
- 'fd84 0000 0000 0000 fcbd 0000 0000 0000 fcbd 0000 0000 0000 '
- 'fe72 0000 0000 0000 ff9d 0000 0000 0000 0000 0000 032f 0000 '
- '0000 0000 03ba 0000 '
+ "0001 0000 0000 " # 0: Version=1.0, Format=0
+ "0004 0006 0011 " # 6: LookupFormat=4, UnitSize=6, NUnits=17
+ "0060 0004 0006 " # 12: SearchRange=96, EntrySelector=4, RangeShift=6
+ "017d 017d 0072 " # 18: Seg[0].LastGlyph=381, FirstGlyph=381, Off=114(+6)
+ "0183 0180 0074 " # 24: Seg[1].LastGlyph=387, FirstGlyph=384, Off=116(+6)
+ "0186 0185 007c " # 30: Seg[2].LastGlyph=390, FirstGlyph=389, Off=124(+6)
+ "018f 018b 0080 " # 36: Seg[3].LastGlyph=399, FirstGlyph=395, Off=128(+6)
+ "01a0 0196 008a " # 42: Seg[4].LastGlyph=416, FirstGlyph=406, Off=138(+6)
+ "01a5 01a3 00a0 " # 48: Seg[5].LastGlyph=421, FirstGlyph=419, Off=160(+6)
+ "01aa 01aa 00a6 " # 54: Seg[6].LastGlyph=426, FirstGlyph=426, Off=166(+6)
+ "01ac 01ac 00a8 " # 60: Seg[7].LastGlyph=428, FirstGlyph=428, Off=168(+6)
+ "01fb 01f1 00aa " # 66: Seg[8].LastGlyph=507, FirstGlyph=497, Off=170(+6)
+ "0214 0209 00c0 " # 72: Seg[9].LastGlyph=532, FirstGlyph=521, Off=192(+6)
+ "021d 0216 00d8 " # 78: Seg[10].LastGlyph=541, FirstGlyph=534, Off=216(+6)
+ "0222 0220 00e8 " # 84: Seg[11].LastGlyph=546, FirstGlyph=544, Off=232(+6)
+ "0227 0225 00ee " # 90: Seg[12].LastGlyph=551, FirstGlyph=549, Off=238(+6)
+ "0229 0229 00f4 " # 96: Seg[13].LastGlyph=553, FirstGlyph=553, Off=244(+6)
+ "023b 023b 00f6 " # 102: Seg[14].LastGlyph=571, FirstGlyph=571, Off=246(+6)
+ "023e 023e 00f8 " # 108: Seg[15].LastGlyph=574, FirstGlyph=574, Off=248(+6)
+ "ffff ffff 00fa " # 114: Seg[16]=<end>
+ "0100 0108 0110 0118 0120 0128 0130 0138 0140 0148 0150 0158 "
+ "0160 0168 0170 0178 0180 0188 0190 0198 01a0 01a8 01b0 01b8 "
+ "01c0 01c8 01d0 01d8 01e0 01e8 01f0 01f8 0200 0208 0210 0218 "
+ "0220 0228 0230 0238 0240 0248 0250 0258 0260 0268 0270 0278 "
+ "0280 0288 0290 0298 02a0 02a8 02b0 02b8 02c0 02c8 02d0 02d8 "
+ "02e0 02e8 02f0 02f8 0300 0308 0310 0318 fd98 0000 0000 0000 "
+ "fdbc 0000 0000 0000 fdbc 0000 0000 0000 fdbf 0000 0000 0000 "
+ "fdbc 0000 0000 0000 fd98 0000 0000 0000 fda9 0000 0000 0000 "
+ "fd98 0000 0000 0000 fd98 0000 0000 0000 fd98 0000 0000 0000 "
+ "0000 0000 0205 0000 0000 0000 0205 0000 0000 0000 02a4 0000 "
+ "0000 0000 027e 0000 0000 0000 02f4 0000 0000 0000 02a4 0000 "
+ "0000 0000 0365 0000 0000 0000 0291 0000 0000 0000 0291 0000 "
+ "0000 0000 026a 0000 0000 0000 02b8 0000 0000 0000 02cb 0000 "
+ "0000 0000 02a4 0000 0000 0000 01a9 0000 0000 0000 0244 0000 "
+ "0000 0000 02a4 0000 0000 0000 02cb 0000 0000 0000 0244 0000 "
+ "0000 0000 0307 0000 0000 0000 0307 0000 0000 0000 037f 0000 "
+ "0000 0000 0307 0000 0000 0000 0307 0000 0000 0000 0307 0000 "
+ "0000 0000 0307 0000 0000 0000 0307 0000 0000 0000 03e3 0000 "
+ "0000 0000 030c 0000 0000 0000 0307 0000 fe30 0000 0000 0000 "
+ "fe7e 0000 0000 0000 fe91 0000 0000 0000 fe6a 0000 0000 0000 "
+ "fe6a 0000 0000 0000 fecb 0000 0000 0000 fe6a 0000 0000 0000 "
+ "fe7e 0000 0000 0000 fea4 0000 0000 0000 fe7e 0000 0000 0000 "
+ "fe44 0000 0000 0000 fea4 0000 0000 0000 feb8 0000 0000 0000 "
+ "fe7e 0000 0000 0000 fe5e 0000 0000 0000 fe37 0000 0000 0000 "
+ "fe37 0000 0000 0000 fcbd 0000 0000 0000 fd84 0000 0000 0000 "
+ "fd98 0000 0000 0000 fd82 0000 0000 0000 fcbd 0000 0000 0000 "
+ "fd84 0000 0000 0000 fcbd 0000 0000 0000 fcbd 0000 0000 0000 "
+ "fe72 0000 0000 0000 ff9d 0000 0000 0000 0000 0000 032f 0000 "
+ "0000 0000 03ba 0000 "
)
assert len(OPBD_APPLE_CHANCERY_DATA) == 800
class OPBDTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- glyphs = ['.notdef'] + ['X.alt%d' for g in range(1, 50)]
- glyphs[10] = 'C'
- glyphs[43] = 'A'
+ glyphs = [".notdef"] + ["X.alt%d" for g in range(1, 50)]
+ glyphs[10] = "C"
+ glyphs[43] = "A"
cls.font = FakeFont(glyphs)
def test_decompile_toXML_format0(self):
- table = newTable('opbd')
+ table = newTable("opbd")
table.decompile(OPBD_FORMAT_0_DATA, self.font)
self.assertEqual(getXML(table.toXML), OPBD_FORMAT_0_XML)
def test_compile_fromXML_format0(self):
- table = newTable('opbd')
+ table = newTable("opbd")
for name, attrs, content in parseXML(OPBD_FORMAT_0_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(OPBD_FORMAT_0_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(OPBD_FORMAT_0_DATA))
def test_decompile_toXML_format1(self):
- table = newTable('opbd')
+ table = newTable("opbd")
table.decompile(OPBD_FORMAT_1_DATA, self.font)
self.assertEqual(getXML(table.toXML), OPBD_FORMAT_1_XML)
def test_compile_fromXML_format1(self):
- table = newTable('opbd')
+ table = newTable("opbd")
for name, attrs, content in parseXML(OPBD_FORMAT_1_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(OPBD_FORMAT_1_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(OPBD_FORMAT_1_DATA))
def test_decompile_AppleChancery(self):
# Make sure we do not crash when decompiling the 'opbd' table of
# AppleChancery.ttf. https://github.com/fonttools/fonttools/issues/1031
- table = newTable('opbd')
+ table = newTable("opbd")
table.decompile(OPBD_APPLE_CHANCERY_DATA, self.font)
self.assertIn('<OpticalBounds Format="0">', getXML(table.toXML))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_p_r_o_p_test.py b/Tests/ttLib/tables/_p_r_o_p_test.py
index 63c2924b..42f9815b 100644
--- a/Tests/ttLib/tables/_p_r_o_p_test.py
+++ b/Tests/ttLib/tables/_p_r_o_p_test.py
@@ -5,78 +5,76 @@ import unittest
PROP_FORMAT_0_DATA = deHexStr(
- '0001 0000 0000 ' # 0: Version=1.0, Format=0
- '0005 ' # 6: DefaultProperties=European number terminator
-) # 8: <end>
-assert(len(PROP_FORMAT_0_DATA) == 8)
+ "0001 0000 0000 " # 0: Version=1.0, Format=0
+ "0005 " # 6: DefaultProperties=European number terminator
+) # 8: <end>
+assert len(PROP_FORMAT_0_DATA) == 8
PROP_FORMAT_0_XML = [
'<Version value="1.0"/>',
'<GlyphProperties Format="0">',
' <DefaultProperties value="5"/>',
- '</GlyphProperties>',
+ "</GlyphProperties>",
]
PROP_FORMAT_1_DATA = deHexStr(
- '0003 0000 0001 ' # 0: Version=3.0, Format=1
- '0000 ' # 6: DefaultProperties=left-to-right; non-whitespace
- '0008 0003 0004 ' # 8: LookupFormat=8, FirstGlyph=3, GlyphCount=4
- '000B ' # 14: Properties[C]=other neutral
- '000A ' # 16: Properties[D]=whitespace
- '600B ' # 18: Properties[E]=other neutral; hanging punct
- '0005 ' # 20: Properties[F]=European number terminator
-) # 22: <end>
-assert(len(PROP_FORMAT_1_DATA) == 22)
+ "0003 0000 0001 " # 0: Version=3.0, Format=1
+ "0000 " # 6: DefaultProperties=left-to-right; non-whitespace
+ "0008 0003 0004 " # 8: LookupFormat=8, FirstGlyph=3, GlyphCount=4
+ "000B " # 14: Properties[C]=other neutral
+ "000A " # 16: Properties[D]=whitespace
+ "600B " # 18: Properties[E]=other neutral; hanging punct
+ "0005 " # 20: Properties[F]=European number terminator
+) # 22: <end>
+assert len(PROP_FORMAT_1_DATA) == 22
PROP_FORMAT_1_XML = [
'<Version value="3.0"/>',
'<GlyphProperties Format="1">',
' <DefaultProperties value="0"/>',
- ' <Properties>',
+ " <Properties>",
' <Lookup glyph="C" value="11"/>',
' <Lookup glyph="D" value="10"/>',
' <Lookup glyph="E" value="24587"/>',
' <Lookup glyph="F" value="5"/>',
- ' </Properties>',
- '</GlyphProperties>',
+ " </Properties>",
+ "</GlyphProperties>",
]
class PROPTest(unittest.TestCase):
-
@classmethod
def setUpClass(cls):
cls.maxDiff = None
- cls.font = FakeFont(['.notdef', 'A', 'B', 'C', 'D', 'E', 'F', 'G'])
+ cls.font = FakeFont([".notdef", "A", "B", "C", "D", "E", "F", "G"])
def test_decompile_toXML_format0(self):
- table = newTable('prop')
+ table = newTable("prop")
table.decompile(PROP_FORMAT_0_DATA, self.font)
self.assertEqual(getXML(table.toXML), PROP_FORMAT_0_XML)
def test_compile_fromXML_format0(self):
- table = newTable('prop')
+ table = newTable("prop")
for name, attrs, content in parseXML(PROP_FORMAT_0_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(PROP_FORMAT_0_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(PROP_FORMAT_0_DATA))
def test_decompile_toXML_format1(self):
- table = newTable('prop')
+ table = newTable("prop")
table.decompile(PROP_FORMAT_1_DATA, self.font)
self.assertEqual(getXML(table.toXML), PROP_FORMAT_1_XML)
def test_compile_fromXML_format1(self):
- table = newTable('prop')
+ table = newTable("prop")
for name, attrs, content in parseXML(PROP_FORMAT_1_XML):
table.fromXML(name, attrs, content, font=self.font)
- self.assertEqual(hexStr(table.compile(self.font)),
- hexStr(PROP_FORMAT_1_DATA))
+ self.assertEqual(hexStr(table.compile(self.font)), hexStr(PROP_FORMAT_1_DATA))
-if __name__ == '__main__':
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_t_r_a_k_test.py b/Tests/ttLib/tables/_t_r_a_k_test.py
index 2ea6cf59..ec178377 100644
--- a/Tests/ttLib/tables/_t_r_a_k_test.py
+++ b/Tests/ttLib/tables/_t_r_a_k_test.py
@@ -8,332 +8,336 @@ import unittest
# /Library/Fonts/Osaka.ttf from OSX has trak table with both horiz and vertData
OSAKA_TRAK_TABLE_DATA = deHexStr(
- '00 01 00 00 00 00 00 0c 00 40 00 00 00 03 00 02 00 00 00 2c ff ff '
- '00 00 01 06 00 34 00 00 00 00 01 07 00 38 00 01 00 00 01 08 00 3c '
- '00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 00 00 00 0c 00 0c 00 03 '
- '00 02 00 00 00 60 ff ff 00 00 01 09 00 68 00 00 00 00 01 0a 00 6c '
- '00 01 00 00 01 0b 00 70 00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 '
- '00 00 00 0c 00 0c')
-
-# decompiled horizData and vertData entries from Osaka.ttf
+ "00 01 00 00 00 00 00 0c 00 40 00 00 00 03 00 02 00 00 00 2c ff ff "
+ "00 00 01 06 00 34 00 00 00 00 01 07 00 38 00 01 00 00 01 08 00 3c "
+ "00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 00 00 00 0c 00 0c 00 03 "
+ "00 02 00 00 00 60 ff ff 00 00 01 09 00 68 00 00 00 00 01 0a 00 6c "
+ "00 01 00 00 01 0b 00 70 00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 "
+ "00 00 00 0c 00 0c"
+)
+
+# decompiled horizData and vertData entries from Osaka.ttf
OSAKA_HORIZ_TRACK_ENTRIES = {
- -1.0: TrackTableEntry({24.0: -12, 12.0: -12}, nameIndex=262),
- 0.0: TrackTableEntry({24.0: 0, 12.0: 0}, nameIndex=263),
- 1.0: TrackTableEntry({24.0: 12, 12.0: 12}, nameIndex=264)
- }
+ -1.0: TrackTableEntry({24.0: -12, 12.0: -12}, nameIndex=262),
+ 0.0: TrackTableEntry({24.0: 0, 12.0: 0}, nameIndex=263),
+ 1.0: TrackTableEntry({24.0: 12, 12.0: 12}, nameIndex=264),
+}
OSAKA_VERT_TRACK_ENTRIES = {
- -1.0: TrackTableEntry({24.0: -12, 12.0: -12}, nameIndex=265),
- 0.0: TrackTableEntry({24.0: 0, 12.0: 0}, nameIndex=266),
- 1.0: TrackTableEntry({24.0: 12, 12.0: 12}, nameIndex=267)
- }
+ -1.0: TrackTableEntry({24.0: -12, 12.0: -12}, nameIndex=265),
+ 0.0: TrackTableEntry({24.0: 0, 12.0: 0}, nameIndex=266),
+ 1.0: TrackTableEntry({24.0: 12, 12.0: 12}, nameIndex=267),
+}
OSAKA_TRAK_TABLE_XML = [
- '<version value="1.0"/>',
- '<format value="0"/>',
- '<horizData>',
- ' <!-- nTracks=3, nSizes=2 -->',
- ' <trackEntry value="-1.0" nameIndex="262">',
- ' <!-- Tight -->',
- ' <track size="12.0" value="-12"/>',
- ' <track size="24.0" value="-12"/>',
- ' </trackEntry>',
- ' <trackEntry value="0.0" nameIndex="263">',
- ' <!-- Normal -->',
- ' <track size="12.0" value="0"/>',
- ' <track size="24.0" value="0"/>',
- ' </trackEntry>',
- ' <trackEntry value="1.0" nameIndex="264">',
- ' <!-- Loose -->',
- ' <track size="12.0" value="12"/>',
- ' <track size="24.0" value="12"/>',
- ' </trackEntry>',
- '</horizData>',
- '<vertData>',
- ' <!-- nTracks=3, nSizes=2 -->',
- ' <trackEntry value="-1.0" nameIndex="265">',
- ' <!-- Tight -->',
- ' <track size="12.0" value="-12"/>',
- ' <track size="24.0" value="-12"/>',
- ' </trackEntry>',
- ' <trackEntry value="0.0" nameIndex="266">',
- ' <!-- Normal -->',
- ' <track size="12.0" value="0"/>',
- ' <track size="24.0" value="0"/>',
- ' </trackEntry>',
- ' <trackEntry value="1.0" nameIndex="267">',
- ' <!-- Loose -->',
- ' <track size="12.0" value="12"/>',
- ' <track size="24.0" value="12"/>',
- ' </trackEntry>',
- '</vertData>',
+ '<version value="1.0"/>',
+ '<format value="0"/>',
+ "<horizData>",
+ " <!-- nTracks=3, nSizes=2 -->",
+ ' <trackEntry value="-1.0" nameIndex="262">',
+ " <!-- Tight -->",
+ ' <track size="12.0" value="-12"/>',
+ ' <track size="24.0" value="-12"/>',
+ " </trackEntry>",
+ ' <trackEntry value="0.0" nameIndex="263">',
+ " <!-- Normal -->",
+ ' <track size="12.0" value="0"/>',
+ ' <track size="24.0" value="0"/>',
+ " </trackEntry>",
+ ' <trackEntry value="1.0" nameIndex="264">',
+ " <!-- Loose -->",
+ ' <track size="12.0" value="12"/>',
+ ' <track size="24.0" value="12"/>',
+ " </trackEntry>",
+ "</horizData>",
+ "<vertData>",
+ " <!-- nTracks=3, nSizes=2 -->",
+ ' <trackEntry value="-1.0" nameIndex="265">',
+ " <!-- Tight -->",
+ ' <track size="12.0" value="-12"/>',
+ ' <track size="24.0" value="-12"/>',
+ " </trackEntry>",
+ ' <trackEntry value="0.0" nameIndex="266">',
+ " <!-- Normal -->",
+ ' <track size="12.0" value="0"/>',
+ ' <track size="24.0" value="0"/>',
+ " </trackEntry>",
+ ' <trackEntry value="1.0" nameIndex="267">',
+ " <!-- Loose -->",
+ ' <track size="12.0" value="12"/>',
+ ' <track size="24.0" value="12"/>',
+ " </trackEntry>",
+ "</vertData>",
]
# made-up table containing only vertData (no horizData)
OSAKA_VERT_ONLY_TRAK_TABLE_DATA = deHexStr(
- '00 01 00 00 00 00 00 00 00 0c 00 00 00 03 00 02 00 00 00 2c ff ff '
- '00 00 01 09 00 34 00 00 00 00 01 0a 00 38 00 01 00 00 01 0b 00 3c '
- '00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 00 00 00 0c 00 0c')
+ "00 01 00 00 00 00 00 00 00 0c 00 00 00 03 00 02 00 00 00 2c ff ff "
+ "00 00 01 09 00 34 00 00 00 00 01 0a 00 38 00 01 00 00 01 0b 00 3c "
+ "00 0c 00 00 00 18 00 00 ff f4 ff f4 00 00 00 00 00 0c 00 0c"
+)
OSAKA_VERT_ONLY_TRAK_TABLE_XML = [
- '<version value="1.0"/>',
- '<format value="0"/>',
- '<horizData>',
- ' <!-- nTracks=0, nSizes=0 -->',
- '</horizData>',
- '<vertData>',
- ' <!-- nTracks=3, nSizes=2 -->',
- ' <trackEntry value="-1.0" nameIndex="265">',
- ' <!-- Tight -->',
- ' <track size="12.0" value="-12"/>',
- ' <track size="24.0" value="-12"/>',
- ' </trackEntry>',
- ' <trackEntry value="0.0" nameIndex="266">',
- ' <!-- Normal -->',
- ' <track size="12.0" value="0"/>',
- ' <track size="24.0" value="0"/>',
- ' </trackEntry>',
- ' <trackEntry value="1.0" nameIndex="267">',
- ' <!-- Loose -->',
- ' <track size="12.0" value="12"/>',
- ' <track size="24.0" value="12"/>',
- ' </trackEntry>',
- '</vertData>',
+ '<version value="1.0"/>',
+ '<format value="0"/>',
+ "<horizData>",
+ " <!-- nTracks=0, nSizes=0 -->",
+ "</horizData>",
+ "<vertData>",
+ " <!-- nTracks=3, nSizes=2 -->",
+ ' <trackEntry value="-1.0" nameIndex="265">',
+ " <!-- Tight -->",
+ ' <track size="12.0" value="-12"/>',
+ ' <track size="24.0" value="-12"/>',
+ " </trackEntry>",
+ ' <trackEntry value="0.0" nameIndex="266">',
+ " <!-- Normal -->",
+ ' <track size="12.0" value="0"/>',
+ ' <track size="24.0" value="0"/>',
+ " </trackEntry>",
+ ' <trackEntry value="1.0" nameIndex="267">',
+ " <!-- Loose -->",
+ ' <track size="12.0" value="12"/>',
+ ' <track size="24.0" value="12"/>',
+ " </trackEntry>",
+ "</vertData>",
]
# also /Library/Fonts/Skia.ttf contains a trak table with horizData
SKIA_TRAK_TABLE_DATA = deHexStr(
- '00 01 00 00 00 00 00 0c 00 00 00 00 00 03 00 05 00 00 00 2c ff ff '
- '00 00 01 13 00 40 00 00 00 00 01 2f 00 4a 00 01 00 00 01 14 00 54 '
- '00 09 00 00 00 0a 00 00 00 0c 00 00 00 12 00 00 00 13 00 00 ff f6 '
- 'ff e2 ff c4 ff c1 ff c1 00 0f 00 00 ff fb ff e7 ff e7 00 8c 00 82 '
- '00 7d 00 73 00 73')
+ "00 01 00 00 00 00 00 0c 00 00 00 00 00 03 00 05 00 00 00 2c ff ff "
+ "00 00 01 13 00 40 00 00 00 00 01 2f 00 4a 00 01 00 00 01 14 00 54 "
+ "00 09 00 00 00 0a 00 00 00 0c 00 00 00 12 00 00 00 13 00 00 ff f6 "
+ "ff e2 ff c4 ff c1 ff c1 00 0f 00 00 ff fb ff e7 ff e7 00 8c 00 82 "
+ "00 7d 00 73 00 73"
+)
SKIA_TRACK_ENTRIES = {
- -1.0: TrackTableEntry(
- {9.0: -10, 10.0: -30, 19.0: -63, 12.0: -60, 18.0: -63}, nameIndex=275),
- 0.0: TrackTableEntry(
- {9.0: 15, 10.0: 0, 19.0: -25, 12.0: -5, 18.0: -25}, nameIndex=303),
- 1.0: TrackTableEntry(
- {9.0: 140, 10.0: 130, 19.0: 115, 12.0: 125, 18.0: 115}, nameIndex=276)
- }
+ -1.0: TrackTableEntry(
+ {9.0: -10, 10.0: -30, 19.0: -63, 12.0: -60, 18.0: -63}, nameIndex=275
+ ),
+ 0.0: TrackTableEntry(
+ {9.0: 15, 10.0: 0, 19.0: -25, 12.0: -5, 18.0: -25}, nameIndex=303
+ ),
+ 1.0: TrackTableEntry(
+ {9.0: 140, 10.0: 130, 19.0: 115, 12.0: 125, 18.0: 115}, nameIndex=276
+ ),
+}
SKIA_TRAK_TABLE_XML = [
- '<version value="1.0"/>',
- '<format value="0"/>',
- '<horizData>',
- ' <!-- nTracks=3, nSizes=5 -->',
- ' <trackEntry value="-1.0" nameIndex="275">',
- ' <!-- Tight -->',
- ' <track size="9.0" value="-10"/>',
- ' <track size="10.0" value="-30"/>',
- ' <track size="12.0" value="-60"/>',
- ' <track size="18.0" value="-63"/>',
- ' <track size="19.0" value="-63"/>',
- ' </trackEntry>',
- ' <trackEntry value="0.0" nameIndex="303">',
- ' <!-- Normal -->',
- ' <track size="9.0" value="15"/>',
- ' <track size="10.0" value="0"/>',
- ' <track size="12.0" value="-5"/>',
- ' <track size="18.0" value="-25"/>',
- ' <track size="19.0" value="-25"/>',
- ' </trackEntry>',
- ' <trackEntry value="1.0" nameIndex="276">',
- ' <!-- Loose -->',
- ' <track size="9.0" value="140"/>',
- ' <track size="10.0" value="130"/>',
- ' <track size="12.0" value="125"/>',
- ' <track size="18.0" value="115"/>',
- ' <track size="19.0" value="115"/>',
- ' </trackEntry>',
- '</horizData>',
- '<vertData>',
- ' <!-- nTracks=0, nSizes=0 -->',
- '</vertData>',
+ '<version value="1.0"/>',
+ '<format value="0"/>',
+ "<horizData>",
+ " <!-- nTracks=3, nSizes=5 -->",
+ ' <trackEntry value="-1.0" nameIndex="275">',
+ " <!-- Tight -->",
+ ' <track size="9.0" value="-10"/>',
+ ' <track size="10.0" value="-30"/>',
+ ' <track size="12.0" value="-60"/>',
+ ' <track size="18.0" value="-63"/>',
+ ' <track size="19.0" value="-63"/>',
+ " </trackEntry>",
+ ' <trackEntry value="0.0" nameIndex="303">',
+ " <!-- Normal -->",
+ ' <track size="9.0" value="15"/>',
+ ' <track size="10.0" value="0"/>',
+ ' <track size="12.0" value="-5"/>',
+ ' <track size="18.0" value="-25"/>',
+ ' <track size="19.0" value="-25"/>',
+ " </trackEntry>",
+ ' <trackEntry value="1.0" nameIndex="276">',
+ " <!-- Loose -->",
+ ' <track size="9.0" value="140"/>',
+ ' <track size="10.0" value="130"/>',
+ ' <track size="12.0" value="125"/>',
+ ' <track size="18.0" value="115"/>',
+ ' <track size="19.0" value="115"/>',
+ " </trackEntry>",
+ "</horizData>",
+ "<vertData>",
+ " <!-- nTracks=0, nSizes=0 -->",
+ "</vertData>",
]
class TrackingTableTest(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 setUp(self):
- table = table__t_r_a_k()
- table.version = 1.0
- table.format = 0
- self.font = {'trak': table}
-
- def test_compile_horiz(self):
- table = self.font['trak']
- table.horizData = TrackData(SKIA_TRACK_ENTRIES)
- trakData = table.compile(self.font)
- self.assertEqual(trakData, SKIA_TRAK_TABLE_DATA)
-
- def test_compile_vert(self):
- table = self.font['trak']
- table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
- trakData = table.compile(self.font)
- self.assertEqual(trakData, OSAKA_VERT_ONLY_TRAK_TABLE_DATA)
-
- def test_compile_horiz_and_vert(self):
- table = self.font['trak']
- table.horizData = TrackData(OSAKA_HORIZ_TRACK_ENTRIES)
- table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
- trakData = table.compile(self.font)
- self.assertEqual(trakData, OSAKA_TRAK_TABLE_DATA)
-
- def test_compile_longword_aligned(self):
- table = self.font['trak']
- # without padding, this 'horizData' would end up 46 byte long
- table.horizData = TrackData({
- 0.0: TrackTableEntry(nameIndex=256, values={12.0: 0, 24.0: 0, 36.0: 0})
- })
- table.vertData = TrackData({
- 0.0: TrackTableEntry(nameIndex=257, values={12.0: 0, 24.0: 0, 36.0: 0})
- })
- trakData = table.compile(self.font)
- self.assertTrue(table.vertOffset % 4 == 0)
-
- def test_compile_sizes_mismatch(self):
- table = self.font['trak']
- table.horizData = TrackData({
- -1.0: TrackTableEntry(nameIndex=256, values={9.0: -10, 10.0: -30}),
- 0.0: TrackTableEntry(nameIndex=257, values={8.0: 20, 12.0: 0})
- })
- with self.assertRaisesRegex(TTLibError, 'entries must specify the same sizes'):
- table.compile(self.font)
-
- def test_decompile_horiz(self):
- table = self.font['trak']
- table.decompile(SKIA_TRAK_TABLE_DATA, self.font)
- self.assertEqual(table.horizData, SKIA_TRACK_ENTRIES)
- self.assertEqual(table.vertData, TrackData())
-
- def test_decompile_vert(self):
- table = self.font['trak']
- table.decompile(OSAKA_VERT_ONLY_TRAK_TABLE_DATA, self.font)
- self.assertEqual(table.horizData, TrackData())
- self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
-
- def test_decompile_horiz_and_vert(self):
- table = self.font['trak']
- table.decompile(OSAKA_TRAK_TABLE_DATA, self.font)
- self.assertEqual(table.horizData, OSAKA_HORIZ_TRACK_ENTRIES)
- self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
-
- def test_roundtrip_decompile_compile(self):
- for trakData in (
- OSAKA_TRAK_TABLE_DATA,
- OSAKA_VERT_ONLY_TRAK_TABLE_DATA,
- SKIA_TRAK_TABLE_DATA):
- table = table__t_r_a_k()
- table.decompile(trakData, ttFont=None)
- newTrakData = table.compile(ttFont=None)
- self.assertEqual(trakData, newTrakData)
-
- def test_fromXML_horiz(self):
- table = self.font['trak']
- for name, attrs, content in parseXML(SKIA_TRAK_TABLE_XML):
- table.fromXML(name, attrs, content, self.font)
- self.assertEqual(table.version, 1.0)
- self.assertEqual(table.format, 0)
- self.assertEqual(table.horizData, SKIA_TRACK_ENTRIES)
- self.assertEqual(table.vertData, TrackData())
-
- def test_fromXML_horiz_and_vert(self):
- table = self.font['trak']
- for name, attrs, content in parseXML(OSAKA_TRAK_TABLE_XML):
- table.fromXML(name, attrs, content, self.font)
- self.assertEqual(table.version, 1.0)
- self.assertEqual(table.format, 0)
- self.assertEqual(table.horizData, OSAKA_HORIZ_TRACK_ENTRIES)
- self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
-
- def test_fromXML_vert(self):
- table = self.font['trak']
- for name, attrs, content in parseXML(OSAKA_VERT_ONLY_TRAK_TABLE_XML):
- table.fromXML(name, attrs, content, self.font)
- self.assertEqual(table.version, 1.0)
- self.assertEqual(table.format, 0)
- self.assertEqual(table.horizData, TrackData())
- self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
-
- def test_toXML_horiz(self):
- table = self.font['trak']
- table.horizData = TrackData(SKIA_TRACK_ENTRIES)
- add_name(self.font, 'Tight', nameID=275)
- add_name(self.font, 'Normal', nameID=303)
- add_name(self.font, 'Loose', nameID=276)
- self.assertEqual(
- SKIA_TRAK_TABLE_XML,
- getXML(table.toXML, self.font))
-
- def test_toXML_horiz_and_vert(self):
- table = self.font['trak']
- table.horizData = TrackData(OSAKA_HORIZ_TRACK_ENTRIES)
- table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
- add_name(self.font, 'Tight', nameID=262)
- add_name(self.font, 'Normal', nameID=263)
- add_name(self.font, 'Loose', nameID=264)
- add_name(self.font, 'Tight', nameID=265)
- add_name(self.font, 'Normal', nameID=266)
- add_name(self.font, 'Loose', nameID=267)
- self.assertEqual(
- OSAKA_TRAK_TABLE_XML,
- getXML(table.toXML, self.font))
-
- def test_toXML_vert(self):
- table = self.font['trak']
- table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
- add_name(self.font, 'Tight', nameID=265)
- add_name(self.font, 'Normal', nameID=266)
- add_name(self.font, 'Loose', nameID=267)
- self.assertEqual(
- OSAKA_VERT_ONLY_TRAK_TABLE_XML,
- getXML(table.toXML, self.font))
-
- def test_roundtrip_fromXML_toXML(self):
- font = {}
- add_name(font, 'Tight', nameID=275)
- add_name(font, 'Normal', nameID=303)
- add_name(font, 'Loose', nameID=276)
- add_name(font, 'Tight', nameID=262)
- add_name(font, 'Normal', nameID=263)
- add_name(font, 'Loose', nameID=264)
- add_name(font, 'Tight', nameID=265)
- add_name(font, 'Normal', nameID=266)
- add_name(font, 'Loose', nameID=267)
- for input_xml in (
- SKIA_TRAK_TABLE_XML,
- OSAKA_TRAK_TABLE_XML,
- OSAKA_VERT_ONLY_TRAK_TABLE_XML):
- table = table__t_r_a_k()
- font['trak'] = table
- for name, attrs, content in parseXML(input_xml):
- table.fromXML(name, attrs, content, font)
- output_xml = getXML(table.toXML, font)
- self.assertEqual(input_xml, output_xml)
+ 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 setUp(self):
+ table = table__t_r_a_k()
+ table.version = 1.0
+ table.format = 0
+ self.font = {"trak": table}
+
+ def test_compile_horiz(self):
+ table = self.font["trak"]
+ table.horizData = TrackData(SKIA_TRACK_ENTRIES)
+ trakData = table.compile(self.font)
+ self.assertEqual(trakData, SKIA_TRAK_TABLE_DATA)
+
+ def test_compile_vert(self):
+ table = self.font["trak"]
+ table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
+ trakData = table.compile(self.font)
+ self.assertEqual(trakData, OSAKA_VERT_ONLY_TRAK_TABLE_DATA)
+
+ def test_compile_horiz_and_vert(self):
+ table = self.font["trak"]
+ table.horizData = TrackData(OSAKA_HORIZ_TRACK_ENTRIES)
+ table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
+ trakData = table.compile(self.font)
+ self.assertEqual(trakData, OSAKA_TRAK_TABLE_DATA)
+
+ def test_compile_longword_aligned(self):
+ table = self.font["trak"]
+ # without padding, this 'horizData' would end up 46 byte long
+ table.horizData = TrackData(
+ {0.0: TrackTableEntry(nameIndex=256, values={12.0: 0, 24.0: 0, 36.0: 0})}
+ )
+ table.vertData = TrackData(
+ {0.0: TrackTableEntry(nameIndex=257, values={12.0: 0, 24.0: 0, 36.0: 0})}
+ )
+ trakData = table.compile(self.font)
+ self.assertTrue(table.vertOffset % 4 == 0)
+
+ def test_compile_sizes_mismatch(self):
+ table = self.font["trak"]
+ table.horizData = TrackData(
+ {
+ -1.0: TrackTableEntry(nameIndex=256, values={9.0: -10, 10.0: -30}),
+ 0.0: TrackTableEntry(nameIndex=257, values={8.0: 20, 12.0: 0}),
+ }
+ )
+ with self.assertRaisesRegex(TTLibError, "entries must specify the same sizes"):
+ table.compile(self.font)
+
+ def test_decompile_horiz(self):
+ table = self.font["trak"]
+ table.decompile(SKIA_TRAK_TABLE_DATA, self.font)
+ self.assertEqual(table.horizData, SKIA_TRACK_ENTRIES)
+ self.assertEqual(table.vertData, TrackData())
+
+ def test_decompile_vert(self):
+ table = self.font["trak"]
+ table.decompile(OSAKA_VERT_ONLY_TRAK_TABLE_DATA, self.font)
+ self.assertEqual(table.horizData, TrackData())
+ self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
+
+ def test_decompile_horiz_and_vert(self):
+ table = self.font["trak"]
+ table.decompile(OSAKA_TRAK_TABLE_DATA, self.font)
+ self.assertEqual(table.horizData, OSAKA_HORIZ_TRACK_ENTRIES)
+ self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
+
+ def test_roundtrip_decompile_compile(self):
+ for trakData in (
+ OSAKA_TRAK_TABLE_DATA,
+ OSAKA_VERT_ONLY_TRAK_TABLE_DATA,
+ SKIA_TRAK_TABLE_DATA,
+ ):
+ table = table__t_r_a_k()
+ table.decompile(trakData, ttFont=None)
+ newTrakData = table.compile(ttFont=None)
+ self.assertEqual(trakData, newTrakData)
+
+ def test_fromXML_horiz(self):
+ table = self.font["trak"]
+ for name, attrs, content in parseXML(SKIA_TRAK_TABLE_XML):
+ table.fromXML(name, attrs, content, self.font)
+ self.assertEqual(table.version, 1.0)
+ self.assertEqual(table.format, 0)
+ self.assertEqual(table.horizData, SKIA_TRACK_ENTRIES)
+ self.assertEqual(table.vertData, TrackData())
+
+ def test_fromXML_horiz_and_vert(self):
+ table = self.font["trak"]
+ for name, attrs, content in parseXML(OSAKA_TRAK_TABLE_XML):
+ table.fromXML(name, attrs, content, self.font)
+ self.assertEqual(table.version, 1.0)
+ self.assertEqual(table.format, 0)
+ self.assertEqual(table.horizData, OSAKA_HORIZ_TRACK_ENTRIES)
+ self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
+
+ def test_fromXML_vert(self):
+ table = self.font["trak"]
+ for name, attrs, content in parseXML(OSAKA_VERT_ONLY_TRAK_TABLE_XML):
+ table.fromXML(name, attrs, content, self.font)
+ self.assertEqual(table.version, 1.0)
+ self.assertEqual(table.format, 0)
+ self.assertEqual(table.horizData, TrackData())
+ self.assertEqual(table.vertData, OSAKA_VERT_TRACK_ENTRIES)
+
+ def test_toXML_horiz(self):
+ table = self.font["trak"]
+ table.horizData = TrackData(SKIA_TRACK_ENTRIES)
+ add_name(self.font, "Tight", nameID=275)
+ add_name(self.font, "Normal", nameID=303)
+ add_name(self.font, "Loose", nameID=276)
+ self.assertEqual(SKIA_TRAK_TABLE_XML, getXML(table.toXML, self.font))
+
+ def test_toXML_horiz_and_vert(self):
+ table = self.font["trak"]
+ table.horizData = TrackData(OSAKA_HORIZ_TRACK_ENTRIES)
+ table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
+ add_name(self.font, "Tight", nameID=262)
+ add_name(self.font, "Normal", nameID=263)
+ add_name(self.font, "Loose", nameID=264)
+ add_name(self.font, "Tight", nameID=265)
+ add_name(self.font, "Normal", nameID=266)
+ add_name(self.font, "Loose", nameID=267)
+ self.assertEqual(OSAKA_TRAK_TABLE_XML, getXML(table.toXML, self.font))
+
+ def test_toXML_vert(self):
+ table = self.font["trak"]
+ table.vertData = TrackData(OSAKA_VERT_TRACK_ENTRIES)
+ add_name(self.font, "Tight", nameID=265)
+ add_name(self.font, "Normal", nameID=266)
+ add_name(self.font, "Loose", nameID=267)
+ self.assertEqual(OSAKA_VERT_ONLY_TRAK_TABLE_XML, getXML(table.toXML, self.font))
+
+ def test_roundtrip_fromXML_toXML(self):
+ font = {}
+ add_name(font, "Tight", nameID=275)
+ add_name(font, "Normal", nameID=303)
+ add_name(font, "Loose", nameID=276)
+ add_name(font, "Tight", nameID=262)
+ add_name(font, "Normal", nameID=263)
+ add_name(font, "Loose", nameID=264)
+ add_name(font, "Tight", nameID=265)
+ add_name(font, "Normal", nameID=266)
+ add_name(font, "Loose", nameID=267)
+ for input_xml in (
+ SKIA_TRAK_TABLE_XML,
+ OSAKA_TRAK_TABLE_XML,
+ OSAKA_VERT_ONLY_TRAK_TABLE_XML,
+ ):
+ table = table__t_r_a_k()
+ font["trak"] = table
+ for name, attrs, content in parseXML(input_xml):
+ table.fromXML(name, attrs, content, font)
+ output_xml = getXML(table.toXML, font)
+ self.assertEqual(input_xml, output_xml)
def add_name(font, string, nameID):
- nameTable = font.get("name")
- if nameTable is None:
- nameTable = font["name"] = table__n_a_m_e()
- nameTable.names = []
- namerec = NameRecord()
- namerec.nameID = nameID
- namerec.string = string.encode('mac_roman')
- namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
- nameTable.names.append(namerec)
+ nameTable = font.get("name")
+ if nameTable is None:
+ nameTable = font["name"] = table__n_a_m_e()
+ nameTable.names = []
+ namerec = NameRecord()
+ namerec.nameID = nameID
+ namerec.string = string.encode("mac_roman")
+ namerec.platformID, namerec.platEncID, namerec.langID = (1, 0, 0)
+ nameTable.names.append(namerec)
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_v_h_e_a_test.py b/Tests/ttLib/tables/_v_h_e_a_test.py
index c6018632..698bd3b7 100644
--- a/Tests/ttLib/tables/_v_h_e_a_test.py
+++ b/Tests/ttLib/tables/_v_h_e_a_test.py
@@ -8,53 +8,53 @@ import unittest
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
VHEA_DATA_VERSION_11 = deHexStr(
- '0001 1000 ' # 1.1 version
- '01F4 ' # 500 ascent
- 'FE0C ' # -500 descent
- '0000 ' # 0 lineGap
- '0BB8 ' # 3000 advanceHeightMax
- 'FC16 ' # -1002 minTopSideBearing
- 'FD5B ' # -677 minBottomSideBearing
- '0B70 ' # 2928 yMaxExtent
- '0000 ' # 0 caretSlopeRise
- '0001 ' # 1 caretSlopeRun
- '0000 ' # 0 caretOffset
- '0000 ' # 0 reserved1
- '0000 ' # 0 reserved2
- '0000 ' # 0 reserved3
- '0000 ' # 0 reserved4
- '0000 ' # 0 metricDataFormat
- '000C ' # 12 numberOfVMetrics
+ "0001 1000 " # 1.1 version
+ "01F4 " # 500 ascent
+ "FE0C " # -500 descent
+ "0000 " # 0 lineGap
+ "0BB8 " # 3000 advanceHeightMax
+ "FC16 " # -1002 minTopSideBearing
+ "FD5B " # -677 minBottomSideBearing
+ "0B70 " # 2928 yMaxExtent
+ "0000 " # 0 caretSlopeRise
+ "0001 " # 1 caretSlopeRun
+ "0000 " # 0 caretOffset
+ "0000 " # 0 reserved1
+ "0000 " # 0 reserved2
+ "0000 " # 0 reserved3
+ "0000 " # 0 reserved4
+ "0000 " # 0 metricDataFormat
+ "000C " # 12 numberOfVMetrics
)
-VHEA_DATA_VERSION_10 = deHexStr('00010000') + VHEA_DATA_VERSION_11[4:]
+VHEA_DATA_VERSION_10 = deHexStr("00010000") + VHEA_DATA_VERSION_11[4:]
VHEA_VERSION_11_AS_DICT = {
- 'tableTag': 'vhea',
- 'tableVersion': 0x00011000,
- 'ascent': 500,
- 'descent': -500,
- 'lineGap': 0,
- 'advanceHeightMax': 3000,
- 'minTopSideBearing': -1002,
- 'minBottomSideBearing': -677,
- 'yMaxExtent': 2928,
- 'caretSlopeRise': 0,
- 'caretSlopeRun': 1,
- 'caretOffset': 0,
- 'reserved1': 0,
- 'reserved2': 0,
- 'reserved3': 0,
- 'reserved4': 0,
- 'metricDataFormat': 0,
- 'numberOfVMetrics': 12,
+ "tableTag": "vhea",
+ "tableVersion": 0x00011000,
+ "ascent": 500,
+ "descent": -500,
+ "lineGap": 0,
+ "advanceHeightMax": 3000,
+ "minTopSideBearing": -1002,
+ "minBottomSideBearing": -677,
+ "yMaxExtent": 2928,
+ "caretSlopeRise": 0,
+ "caretSlopeRun": 1,
+ "caretOffset": 0,
+ "reserved1": 0,
+ "reserved2": 0,
+ "reserved3": 0,
+ "reserved4": 0,
+ "metricDataFormat": 0,
+ "numberOfVMetrics": 12,
}
VHEA_VERSION_10_AS_DICT = dict(VHEA_VERSION_11_AS_DICT)
-VHEA_VERSION_10_AS_DICT['tableVersion'] = 0x00010000
+VHEA_VERSION_10_AS_DICT["tableVersion"] = 0x00010000
VHEA_XML_VERSION_11 = [
'<tableVersion value="0x00011000"/>',
@@ -90,9 +90,8 @@ VHEA_XML_VERSION_10_AS_FLOAT = [
class VheaCompileOrToXMLTest(unittest.TestCase):
-
def setUp(self):
- vhea = newTable('vhea')
+ vhea = newTable("vhea")
vhea.tableVersion = 0x00010000
vhea.ascent = 500
vhea.descent = -500
@@ -107,140 +106,156 @@ class VheaCompileOrToXMLTest(unittest.TestCase):
vhea.metricDataFormat = 0
vhea.numberOfVMetrics = 12
vhea.reserved1 = vhea.reserved2 = vhea.reserved3 = vhea.reserved4 = 0
- self.font = TTFont(sfntVersion='OTTO')
- self.font['vhea'] = vhea
+ self.font = TTFont(sfntVersion="OTTO")
+ self.font["vhea"] = vhea
def test_compile_caretOffset_as_reserved0(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
del vhea.caretOffset
vhea.reserved0 = 0
self.assertEqual(VHEA_DATA_VERSION_10, vhea.compile(self.font))
def test_compile_version_10(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.tableVersion = 0x00010000
self.assertEqual(VHEA_DATA_VERSION_10, vhea.compile(self.font))
def test_compile_version_10_as_float(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.tableVersion = 1.0
with CapturingLogHandler(log, "WARNING") as captor:
self.assertEqual(VHEA_DATA_VERSION_10, vhea.compile(self.font))
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
def test_compile_version_11(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.tableVersion = 0x00011000
self.assertEqual(VHEA_DATA_VERSION_11, vhea.compile(self.font))
def test_compile_version_11_as_float(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.tableVersion = 1.0625
with CapturingLogHandler(log, "WARNING") as captor:
self.assertEqual(VHEA_DATA_VERSION_11, vhea.compile(self.font))
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
def test_toXML_caretOffset_as_reserved0(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
del vhea.caretOffset
vhea.reserved0 = 0
self.assertEqual(getXML(vhea.toXML), VHEA_XML_VERSION_10)
def test_toXML_version_10(self):
- vhea = self.font['vhea']
- self.font['vhea'].tableVersion = 0x00010000
+ vhea = self.font["vhea"]
+ self.font["vhea"].tableVersion = 0x00010000
self.assertEqual(getXML(vhea.toXML), VHEA_XML_VERSION_10)
def test_toXML_version_10_as_float(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.tableVersion = 1.0
with CapturingLogHandler(log, "WARNING") as captor:
self.assertEqual(getXML(vhea.toXML), VHEA_XML_VERSION_10)
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
def test_toXML_version_11(self):
- vhea = self.font['vhea']
- self.font['vhea'].tableVersion = 0x00011000
+ vhea = self.font["vhea"]
+ self.font["vhea"].tableVersion = 0x00011000
self.assertEqual(getXML(vhea.toXML), VHEA_XML_VERSION_11)
def test_toXML_version_11_as_float(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.tableVersion = 1.0625
with CapturingLogHandler(log, "WARNING") as captor:
self.assertEqual(getXML(vhea.toXML), VHEA_XML_VERSION_11)
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
class VheaDecompileOrFromXMLTest(unittest.TestCase):
-
def setUp(self):
- vhea = newTable('vhea')
- self.font = TTFont(sfntVersion='OTTO')
- self.font['vhea'] = vhea
+ vhea = newTable("vhea")
+ self.font = TTFont(sfntVersion="OTTO")
+ self.font["vhea"] = vhea
def test_decompile_version_10(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.decompile(VHEA_DATA_VERSION_10, self.font)
for key in vhea.__dict__:
self.assertEqual(getattr(vhea, key), VHEA_VERSION_10_AS_DICT[key])
def test_decompile_version_11(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
vhea.decompile(VHEA_DATA_VERSION_11, self.font)
for key in vhea.__dict__:
self.assertEqual(getattr(vhea, key), VHEA_VERSION_11_AS_DICT[key])
def test_fromXML_version_10(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
for name, attrs, content in parseXML(VHEA_XML_VERSION_10):
vhea.fromXML(name, attrs, content, self.font)
for key in vhea.__dict__:
self.assertEqual(getattr(vhea, key), VHEA_VERSION_10_AS_DICT[key])
def test_fromXML_version_10_as_float(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
with CapturingLogHandler(log, "WARNING") as captor:
for name, attrs, content in parseXML(VHEA_XML_VERSION_10_AS_FLOAT):
vhea.fromXML(name, attrs, content, self.font)
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
for key in vhea.__dict__:
self.assertEqual(getattr(vhea, key), VHEA_VERSION_10_AS_DICT[key])
def test_fromXML_version_11(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
for name, attrs, content in parseXML(VHEA_XML_VERSION_11):
vhea.fromXML(name, attrs, content, self.font)
for key in vhea.__dict__:
self.assertEqual(getattr(vhea, key), VHEA_VERSION_11_AS_DICT[key])
def test_fromXML_version_11_as_float(self):
- vhea = self.font['vhea']
+ vhea = self.font["vhea"]
with CapturingLogHandler(log, "WARNING") as captor:
for name, attrs, content in parseXML(VHEA_XML_VERSION_11_AS_FLOAT):
vhea.fromXML(name, attrs, content, self.font)
self.assertTrue(
- len([r for r in captor.records
- if "Table version value is a float" in r.msg]) == 1)
+ len(
+ [r for r in captor.records if "Table version value is a float" in r.msg]
+ )
+ == 1
+ )
for key in vhea.__dict__:
self.assertEqual(getattr(vhea, key), VHEA_VERSION_11_AS_DICT[key])
class VheaRecalcTest(unittest.TestCase):
-
def test_recalc_TTF(self):
font = TTFont()
- font.importXML(os.path.join(DATA_DIR, '_v_h_e_a_recalc_TTF.ttx'))
- vhea = font['vhea']
+ font.importXML(os.path.join(DATA_DIR, "_v_h_e_a_recalc_TTF.ttx"))
+ vhea = font["vhea"]
vhea.recalc(font)
self.assertEqual(vhea.advanceHeightMax, 900)
self.assertEqual(vhea.minTopSideBearing, 200)
@@ -249,8 +264,8 @@ class VheaRecalcTest(unittest.TestCase):
def test_recalc_OTF(self):
font = TTFont()
- font.importXML(os.path.join(DATA_DIR, '_v_h_e_a_recalc_OTF.ttx'))
- vhea = font['vhea']
+ font.importXML(os.path.join(DATA_DIR, "_v_h_e_a_recalc_OTF.ttx"))
+ vhea = font["vhea"]
vhea.recalc(font)
self.assertEqual(vhea.advanceHeightMax, 900)
self.assertEqual(vhea.minTopSideBearing, 200)
@@ -259,8 +274,8 @@ class VheaRecalcTest(unittest.TestCase):
def test_recalc_empty(self):
font = TTFont()
- font.importXML(os.path.join(DATA_DIR, '_v_h_e_a_recalc_empty.ttx'))
- vhea = font['vhea']
+ font.importXML(os.path.join(DATA_DIR, "_v_h_e_a_recalc_empty.ttx"))
+ vhea = font["vhea"]
vhea.recalc(font)
self.assertEqual(vhea.advanceHeightMax, 900)
self.assertEqual(vhea.minTopSideBearing, 0)
@@ -270,4 +285,5 @@ class VheaRecalcTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/_v_m_t_x_test.py b/Tests/ttLib/tables/_v_m_t_x_test.py
index 5ea2d245..9b8fcb7a 100644
--- a/Tests/ttLib/tables/_v_m_t_x_test.py
+++ b/Tests/ttLib/tables/_v_m_t_x_test.py
@@ -4,7 +4,6 @@ import unittest
class VmtxTableTest(_h_m_t_x_test.HmtxTableTest):
-
@classmethod
def setUpClass(cls):
cls.tableClass = table__v_m_t_x
@@ -13,4 +12,5 @@ class VmtxTableTest(_h_m_t_x_test.HmtxTableTest):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/data/COLRv1-clip-boxes-cff.ttx b/Tests/ttLib/tables/data/COLRv1-clip-boxes-cff.ttx
new file mode 100644
index 00000000..05172cae
--- /dev/null
+++ b/Tests/ttLib/tables/data/COLRv1-clip-boxes-cff.ttx
@@ -0,0 +1,1213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.37">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name=".null"/>
+ <GlyphID id="2" name="upem_box_glyph"/>
+ <GlyphID id="3" name="cross_glyph"/>
+ <GlyphID id="4" name="one"/>
+ <GlyphID id="5" name="zero"/>
+ <GlyphID id="6" name="scale_0.5_1.5_center_500.0_500.0"/>
+ <GlyphID id="7" name="scale_1.5_1.5_center_500.0_500.0"/>
+ <GlyphID id="8" name="scale_0.5_1.5_center_0_0"/>
+ <GlyphID id="9" name="scale_1.5_1.5_center_0_0"/>
+ <GlyphID id="10" name="scale_0.5_1.5_center_1000_1000"/>
+ <GlyphID id="11" name="scale_1.5_1.5_center_1000_1000"/>
+ <GlyphID id="12" name="rotate_10_center_0_0"/>
+ <GlyphID id="13" name="rotate_-10_center_1000_1000"/>
+ <GlyphID id="14" name="rotate_25_center_500.0_500.0"/>
+ <GlyphID id="15" name="rotate_-15_center_500.0_500.0"/>
+ <GlyphID id="16" name="skew_25_0_center_0_0"/>
+ <GlyphID id="17" name="skew_25_0_center_500.0_500.0"/>
+ <GlyphID id="18" name="skew_0_15_center_0_0"/>
+ <GlyphID id="19" name="skew_0_15_center_500.0_500.0"/>
+ <GlyphID id="20" name="skew_-10_20_center_500.0_500.0"/>
+ <GlyphID id="21" name="skew_-10_20_center_1000_1000"/>
+ <GlyphID id="22" name="transform_matrix_1_0_0_1_125_125"/>
+ <GlyphID id="23" name="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <GlyphID id="24" name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <GlyphID id="25" name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <GlyphID id="26" name="translate_0_0"/>
+ <GlyphID id="27" name="translate_0_100"/>
+ <GlyphID id="28" name="translate_0_-100"/>
+ <GlyphID id="29" name="translate_100_0"/>
+ <GlyphID id="30" name="translate_-100_0"/>
+ <GlyphID id="31" name="translate_200_200"/>
+ <GlyphID id="32" name="translate_-200_-200"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x4b7ffe68"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 10 15:01:34 2023"/>
+ <modified value="Fri Mar 10 15:01:34 2023"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="950"/>
+ <descent value="-250"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="1000"/>
+ <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>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="33"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="988"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="0"/>
+ <ySubscriptYSize value="0"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="0"/>
+ <ySuperscriptXSize value="0"/>
+ <ySuperscriptYSize value="0"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="0"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="0"/>
+ <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 00000000"/>
+ <ulUnicodeRange2 value="00000010 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000100 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="????"/>
+ <fsSelection value="00000000 10000000"/>
+ <usFirstCharIndex value="65535"/>
+ <usLastCharIndex value="65535"/>
+ <sTypoAscender value="950"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="950"/>
+ <usWinDescent value="250"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="0"/>
+ <sCapHeight value="0"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ COLRv1 Static Test Glyphs
+ </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">
+ COLRv1 Static Test Glyphs 2023-03-10T15:01:34.955294
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ COLRv1 Static Test Glyphs Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 2023-03-10T15:01:34.955294
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ COLRv1StaticTestGlyphs-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ COLRv1 Static Test Glyphs
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ COLRv1 Static Test Glyphs 2023-03-10T15:01:34.955294
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ COLRv1 Static Test Glyphs Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ 2023-03-10T15:01:34.955294
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ COLRv1StaticTestGlyphs-Regular
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ </cmap_format_4>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="88" language="0" nGroups="6">
+ <map code="0xf0300" name="scale_0.5_1.5_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0301" name="scale_1.5_1.5_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0302" name="scale_0.5_1.5_center_0_0"/><!-- ???? -->
+ <map code="0xf0303" name="scale_1.5_1.5_center_0_0"/><!-- ???? -->
+ <map code="0xf0304" name="scale_0.5_1.5_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0305" name="scale_1.5_1.5_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0600" name="rotate_10_center_0_0"/><!-- ???? -->
+ <map code="0xf0601" name="rotate_-10_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0602" name="rotate_25_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0603" name="rotate_-15_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0700" name="skew_25_0_center_0_0"/><!-- ???? -->
+ <map code="0xf0701" name="skew_25_0_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0702" name="skew_0_15_center_0_0"/><!-- ???? -->
+ <map code="0xf0703" name="skew_0_15_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0704" name="skew_-10_20_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0705" name="skew_-10_20_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0800" name="transform_matrix_1_0_0_1_125_125"/><!-- ???? -->
+ <map code="0xf0801" name="transform_matrix_1.5_0_0_1.5_0_0"/><!-- ???? -->
+ <map code="0xf0802" name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/><!-- ???? -->
+ <map code="0xf0803" name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/><!-- ???? -->
+ <map code="0xf0900" name="translate_0_0"/><!-- ???? -->
+ <map code="0xf0901" name="translate_0_100"/><!-- ???? -->
+ <map code="0xf0902" name="translate_0_-100"/><!-- ???? -->
+ <map code="0xf0903" name="translate_100_0"/><!-- ???? -->
+ <map code="0xf0904" name="translate_-100_0"/><!-- ???? -->
+ <map code="0xf0905" name="translate_200_200"/><!-- ???? -->
+ <map code="0xf0906" name="translate_-200_-200"/><!-- ???? -->
+ <map code="0xfe001" name=".null"/><!-- ???? -->
+ <map code="0xfe002" name="upem_box_glyph"/><!-- ???? -->
+ <map code="0xfe003" name="cross_glyph"/><!-- ???? -->
+ <map code="0xfe004" name="one"/><!-- ???? -->
+ <map code="0xfe005" name="zero"/><!-- ???? -->
+ </cmap_format_12>
+ </cmap>
+
+ <post>
+ <formatType value="3.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <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="COLRv1StaticTestGlyphs-Regular">
+ <FullName value="COLRv1StaticTestGlyphs-Regular"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-100"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="0 0 1000 1000"/>
+ <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="0"/>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ 600 endchar
+ </CharString>
+ <CharString name=".null">
+ 0 endchar
+ </CharString>
+ <CharString name="cross_glyph">
+ 1000 475 525 rmoveto
+ 225 50 -225 225 -50 -225 -225 -50 225 -225 50 vlineto
+ endchar
+ </CharString>
+ <CharString name="one">
+ 1000 296 543 rmoveto
+ -293 -37 247 vlineto
+ -75 -31 0 37 106 40 rlineto
+ endchar
+ </CharString>
+ <CharString name="rotate_-10_center_1000_1000">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="rotate_-15_center_500.0_500.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="rotate_10_center_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="rotate_25_center_500.0_500.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="scale_0.5_1.5_center_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="scale_0.5_1.5_center_1000_1000">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="scale_0.5_1.5_center_500.0_500.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="scale_1.5_1.5_center_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="scale_1.5_1.5_center_1000_1000">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="scale_1.5_1.5_center_500.0_500.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="skew_-10_20_center_1000_1000">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="skew_-10_20_center_500.0_500.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="skew_0_15_center_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="skew_0_15_center_500.0_500.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="skew_25_0_center_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="skew_25_0_center_500.0_500.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="transform_matrix_1.5_0_0_1.5_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="transform_matrix_1_0_0_1_125_125">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="translate_-100_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="translate_-200_-200">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="translate_0_-100">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="translate_0_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="translate_0_100">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="translate_100_0">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="translate_200_200">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="upem_box_glyph">
+ 1000 0 hmoveto
+ 1000 1000 -1000 vlineto
+ endchar
+ </CharString>
+ <CharString name="zero">
+ 1000 357 374 rmoveto
+ -47 -8 -34 -17 -19 vhcurveto
+ -19 -16 -23 -9 -28 hhcurveto
+ -28 -22 9 19 -17 hvcurveto
+ -17 19 -8 34 47 vvcurveto
+ 45 vlineto
+ 47 8 33 17 19 vhcurveto
+ 18 17 22 9 28 hhcurveto
+ 28 23 -9 -18 16 hvcurveto
+ 17 -19 8 -33 -47 vvcurveto
+ -37 6 rmoveto
+ 33 -5 23 -9 14 vhcurveto
+ 13 -10 -13 7 -18 hhcurveto
+ -18 -13 -7 -13 -10 hvcurveto
+ -9 -14 -5 -23 -33 vvcurveto
+ -57 vlineto
+ -32 5 -24 10 -14 vhcurveto
+ -14 9 14 -8 17 hhcurveto
+ 18 14 8 14 9 hvcurveto
+ 9 14 5 24 32 vvcurveto
+ endchar
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF>
+
+ <COLR>
+ <Version value="1"/>
+ <!-- BaseGlyphRecordCount=0 -->
+ <!-- LayerRecordCount=0 -->
+ <BaseGlyphList>
+ <!-- BaseGlyphCount=27 -->
+ <BaseGlyphPaintRecord index="0">
+ <BaseGlyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="1">
+ <BaseGlyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="2">
+ <BaseGlyph value="scale_0.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="16"><!-- PaintScale -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="3">
+ <BaseGlyph value="scale_1.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="20"><!-- PaintScaleUniform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="4">
+ <BaseGlyph value="scale_0.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="5">
+ <BaseGlyph value="scale_1.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="6">
+ <BaseGlyph value="rotate_10_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="24"><!-- PaintRotate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="10.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="7">
+ <BaseGlyph value="rotate_-10_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-10.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="8">
+ <BaseGlyph value="rotate_25_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="25.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="9">
+ <BaseGlyph value="rotate_-15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="10">
+ <BaseGlyph value="skew_25_0_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="11">
+ <BaseGlyph value="skew_25_0_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="12">
+ <BaseGlyph value="skew_0_15_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="13">
+ <BaseGlyph value="skew_0_15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="14">
+ <BaseGlyph value="skew_-10_20_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="15">
+ <BaseGlyph value="skew_-10_20_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="16">
+ <BaseGlyph value="transform_matrix_1_0_0_1_125_125"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.0"/>
+ <dx value="125.0"/>
+ <dy value="125.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="17">
+ <BaseGlyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.5"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.5"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="18">
+ <BaseGlyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="0.9659"/>
+ <yx value="0.2588"/>
+ <xy value="-0.2588"/>
+ <yy value="0.9659"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="19">
+ <BaseGlyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.6"/>
+ <yy value="1.0"/>
+ <dx value="-300.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="20">
+ <BaseGlyph value="translate_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="21">
+ <BaseGlyph value="translate_0_100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="22">
+ <BaseGlyph value="translate_0_-100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="-100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="23">
+ <BaseGlyph value="translate_100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="24">
+ <BaseGlyph value="translate_-100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="25">
+ <BaseGlyph value="translate_200_200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="200"/>
+ <dy value="200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="26">
+ <BaseGlyph value="translate_-200_-200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-200"/>
+ <dy value="-200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ </BaseGlyphList>
+ </COLR>
+
+ <CPAL>
+ <version value="1"/>
+ <numPaletteEntries value="7"/>
+ <palette index="0">
+ <color index="0" value="#FF0000FF"/>
+ <color index="1" value="#FFA500FF"/>
+ <color index="2" value="#FFFF00FF"/>
+ <color index="3" value="#008000FF"/>
+ <color index="4" value="#0000FFFF"/>
+ <color index="5" value="#4B0082FF"/>
+ <color index="6" value="#EE82EEFF"/>
+ </palette>
+ <palette index="1" type="2">
+ <color index="0" value="#2A294AFF"/>
+ <color index="1" value="#244163FF"/>
+ <color index="2" value="#1B6388FF"/>
+ <color index="3" value="#157DA3FF"/>
+ <color index="4" value="#0E9AC2FF"/>
+ <color index="5" value="#05BEE8FF"/>
+ <color index="6" value="#00D4FFFF"/>
+ </palette>
+ <palette index="2" type="1">
+ <color index="0" value="#FC7118FF"/>
+ <color index="1" value="#FB8115FF"/>
+ <color index="2" value="#FA9511FF"/>
+ <color index="3" value="#FAA80DFF"/>
+ <color index="4" value="#F9BE09FF"/>
+ <color index="5" value="#F8D304FF"/>
+ <color index="6" value="#F8E700FF"/>
+ </palette>
+ </CPAL>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="0"/>
+ <mtx name=".null" width="0" lsb="0"/>
+ <mtx name="cross_glyph" width="1000" lsb="250"/>
+ <mtx name="one" width="1000" lsb="184"/>
+ <mtx name="rotate_-10_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="rotate_-15_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="rotate_10_center_0_0" width="1000" lsb="0"/>
+ <mtx name="rotate_25_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="scale_0.5_1.5_center_0_0" width="1000" lsb="0"/>
+ <mtx name="scale_0.5_1.5_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="scale_0.5_1.5_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="scale_1.5_1.5_center_0_0" width="1000" lsb="0"/>
+ <mtx name="scale_1.5_1.5_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="scale_1.5_1.5_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="skew_-10_20_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="skew_-10_20_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="skew_0_15_center_0_0" width="1000" lsb="0"/>
+ <mtx name="skew_0_15_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="skew_25_0_center_0_0" width="1000" lsb="0"/>
+ <mtx name="skew_25_0_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_1.5_0_0_1.5_0_0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_1_0_0_1_125_125" width="1000" lsb="0"/>
+ <mtx name="translate_-100_0" width="1000" lsb="0"/>
+ <mtx name="translate_-200_-200" width="1000" lsb="0"/>
+ <mtx name="translate_0_-100" width="1000" lsb="0"/>
+ <mtx name="translate_0_0" width="1000" lsb="0"/>
+ <mtx name="translate_0_100" width="1000" lsb="0"/>
+ <mtx name="translate_100_0" width="1000" lsb="0"/>
+ <mtx name="translate_200_200" width="1000" lsb="0"/>
+ <mtx name="upem_box_glyph" width="1000" lsb="0"/>
+ <mtx name="zero" width="1000" lsb="173"/>
+ </hmtx>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/data/COLRv1-clip-boxes-glyf.ttx b/Tests/ttLib/tables/data/COLRv1-clip-boxes-glyf.ttx
new file mode 100644
index 00000000..2f1c14c4
--- /dev/null
+++ b/Tests/ttLib/tables/data/COLRv1-clip-boxes-glyf.ttx
@@ -0,0 +1,1414 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.37">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name=".null"/>
+ <GlyphID id="2" name="upem_box_glyph"/>
+ <GlyphID id="3" name="cross_glyph"/>
+ <GlyphID id="4" name="one"/>
+ <GlyphID id="5" name="zero"/>
+ <GlyphID id="6" name="scale_0.5_1.5_center_500.0_500.0"/>
+ <GlyphID id="7" name="scale_1.5_1.5_center_500.0_500.0"/>
+ <GlyphID id="8" name="scale_0.5_1.5_center_0_0"/>
+ <GlyphID id="9" name="scale_1.5_1.5_center_0_0"/>
+ <GlyphID id="10" name="scale_0.5_1.5_center_1000_1000"/>
+ <GlyphID id="11" name="scale_1.5_1.5_center_1000_1000"/>
+ <GlyphID id="12" name="rotate_10_center_0_0"/>
+ <GlyphID id="13" name="rotate_-10_center_1000_1000"/>
+ <GlyphID id="14" name="rotate_25_center_500.0_500.0"/>
+ <GlyphID id="15" name="rotate_-15_center_500.0_500.0"/>
+ <GlyphID id="16" name="skew_25_0_center_0_0"/>
+ <GlyphID id="17" name="skew_25_0_center_500.0_500.0"/>
+ <GlyphID id="18" name="skew_0_15_center_0_0"/>
+ <GlyphID id="19" name="skew_0_15_center_500.0_500.0"/>
+ <GlyphID id="20" name="skew_-10_20_center_500.0_500.0"/>
+ <GlyphID id="21" name="skew_-10_20_center_1000_1000"/>
+ <GlyphID id="22" name="transform_matrix_1_0_0_1_125_125"/>
+ <GlyphID id="23" name="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <GlyphID id="24" name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <GlyphID id="25" name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <GlyphID id="26" name="translate_0_0"/>
+ <GlyphID id="27" name="translate_0_100"/>
+ <GlyphID id="28" name="translate_0_-100"/>
+ <GlyphID id="29" name="translate_100_0"/>
+ <GlyphID id="30" name="translate_-100_0"/>
+ <GlyphID id="31" name="translate_200_200"/>
+ <GlyphID id="32" name="translate_-200_-200"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x5a54e94d"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Mar 10 15:07:35 2023"/>
+ <modified value="Fri Mar 10 15:07:35 2023"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="950"/>
+ <descent value="-250"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="1000"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="1000"/>
+ <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="33"/>
+ <maxPoints value="28"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="2"/>
+ <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="988"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000000"/>
+ <ySubscriptXSize value="0"/>
+ <ySubscriptYSize value="0"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="0"/>
+ <ySuperscriptXSize value="0"/>
+ <ySuperscriptYSize value="0"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="0"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="0"/>
+ <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 00000000"/>
+ <ulUnicodeRange2 value="00000010 00000000 00000000 00000000"/>
+ <ulUnicodeRange3 value="00000100 00000000 00000000 00000000"/>
+ <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+ <achVendID value="????"/>
+ <fsSelection value="00000000 10000000"/>
+ <usFirstCharIndex value="65535"/>
+ <usLastCharIndex value="65535"/>
+ <sTypoAscender value="950"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="950"/>
+ <usWinDescent value="250"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="0"/>
+ <sCapHeight value="0"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="0"/>
+ <mtx name=".null" width="0" lsb="0"/>
+ <mtx name="cross_glyph" width="1000" lsb="250"/>
+ <mtx name="one" width="1000" lsb="184"/>
+ <mtx name="rotate_-10_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="rotate_-15_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="rotate_10_center_0_0" width="1000" lsb="0"/>
+ <mtx name="rotate_25_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="scale_0.5_1.5_center_0_0" width="1000" lsb="0"/>
+ <mtx name="scale_0.5_1.5_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="scale_0.5_1.5_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="scale_1.5_1.5_center_0_0" width="1000" lsb="0"/>
+ <mtx name="scale_1.5_1.5_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="scale_1.5_1.5_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="skew_-10_20_center_1000_1000" width="1000" lsb="0"/>
+ <mtx name="skew_-10_20_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="skew_0_15_center_0_0" width="1000" lsb="0"/>
+ <mtx name="skew_0_15_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="skew_25_0_center_0_0" width="1000" lsb="0"/>
+ <mtx name="skew_25_0_center_500.0_500.0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_1.5_0_0_1.5_0_0" width="1000" lsb="0"/>
+ <mtx name="transform_matrix_1_0_0_1_125_125" width="1000" lsb="0"/>
+ <mtx name="translate_-100_0" width="1000" lsb="0"/>
+ <mtx name="translate_-200_-200" width="1000" lsb="0"/>
+ <mtx name="translate_0_-100" width="1000" lsb="0"/>
+ <mtx name="translate_0_0" width="1000" lsb="0"/>
+ <mtx name="translate_0_100" width="1000" lsb="0"/>
+ <mtx name="translate_100_0" width="1000" lsb="0"/>
+ <mtx name="translate_200_200" width="1000" lsb="0"/>
+ <mtx name="upem_box_glyph" width="1000" lsb="0"/>
+ <mtx name="zero" width="1000" lsb="173"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ </cmap_format_4>
+ <cmap_format_12 platformID="3" platEncID="10" format="12" reserved="0" length="88" language="0" nGroups="6">
+ <map code="0xf0300" name="scale_0.5_1.5_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0301" name="scale_1.5_1.5_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0302" name="scale_0.5_1.5_center_0_0"/><!-- ???? -->
+ <map code="0xf0303" name="scale_1.5_1.5_center_0_0"/><!-- ???? -->
+ <map code="0xf0304" name="scale_0.5_1.5_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0305" name="scale_1.5_1.5_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0600" name="rotate_10_center_0_0"/><!-- ???? -->
+ <map code="0xf0601" name="rotate_-10_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0602" name="rotate_25_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0603" name="rotate_-15_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0700" name="skew_25_0_center_0_0"/><!-- ???? -->
+ <map code="0xf0701" name="skew_25_0_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0702" name="skew_0_15_center_0_0"/><!-- ???? -->
+ <map code="0xf0703" name="skew_0_15_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0704" name="skew_-10_20_center_500.0_500.0"/><!-- ???? -->
+ <map code="0xf0705" name="skew_-10_20_center_1000_1000"/><!-- ???? -->
+ <map code="0xf0800" name="transform_matrix_1_0_0_1_125_125"/><!-- ???? -->
+ <map code="0xf0801" name="transform_matrix_1.5_0_0_1.5_0_0"/><!-- ???? -->
+ <map code="0xf0802" name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/><!-- ???? -->
+ <map code="0xf0803" name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/><!-- ???? -->
+ <map code="0xf0900" name="translate_0_0"/><!-- ???? -->
+ <map code="0xf0901" name="translate_0_100"/><!-- ???? -->
+ <map code="0xf0902" name="translate_0_-100"/><!-- ???? -->
+ <map code="0xf0903" name="translate_100_0"/><!-- ???? -->
+ <map code="0xf0904" name="translate_-100_0"/><!-- ???? -->
+ <map code="0xf0905" name="translate_200_200"/><!-- ???? -->
+ <map code="0xf0906" name="translate_-200_-200"/><!-- ???? -->
+ <map code="0xfe001" name=".null"/><!-- ???? -->
+ <map code="0xfe002" name="upem_box_glyph"/><!-- ???? -->
+ <map code="0xfe003" name="cross_glyph"/><!-- ???? -->
+ <map code="0xfe004" name="one"/><!-- ???? -->
+ <map code="0xfe005" name="zero"/><!-- ???? -->
+ </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"/><!-- contains no outline data -->
+
+ <TTGlyph name=".null"/><!-- contains no outline data -->
+
+ <TTGlyph name="cross_glyph" xMin="250" yMin="250" xMax="750" yMax="750">
+ <contour>
+ <pt x="475" y="525" on="1"/>
+ <pt x="475" y="750" on="1"/>
+ <pt x="525" y="750" on="1"/>
+ <pt x="525" y="525" on="1"/>
+ <pt x="750" y="525" on="1"/>
+ <pt x="750" y="475" on="1"/>
+ <pt x="525" y="475" on="1"/>
+ <pt x="525" y="250" on="1"/>
+ <pt x="475" y="250" on="1"/>
+ <pt x="475" y="475" on="1"/>
+ <pt x="250" y="475" on="1"/>
+ <pt x="250" y="525" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="one" xMin="184" yMin="250" xMax="296" yMax="543">
+ <contour>
+ <pt x="296" y="543" on="1"/>
+ <pt x="296" y="250" on="1"/>
+ <pt x="259" y="250" on="1"/>
+ <pt x="259" y="497" on="1"/>
+ <pt x="184" y="466" on="1"/>
+ <pt x="184" y="503" on="1"/>
+ <pt x="290" y="543" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="rotate_-10_center_1000_1000" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="rotate_-15_center_500.0_500.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="rotate_10_center_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="rotate_25_center_500.0_500.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="scale_0.5_1.5_center_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="scale_0.5_1.5_center_1000_1000" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="scale_0.5_1.5_center_500.0_500.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="scale_1.5_1.5_center_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="scale_1.5_1.5_center_1000_1000" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="scale_1.5_1.5_center_500.0_500.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="skew_-10_20_center_1000_1000" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="skew_-10_20_center_500.0_500.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="skew_0_15_center_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="skew_0_15_center_500.0_500.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="skew_25_0_center_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="skew_25_0_center_500.0_500.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="transform_matrix_1.5_0_0_1.5_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="transform_matrix_1_0_0_1_125_125" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="translate_-100_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="translate_-200_-200" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="translate_0_-100" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="translate_0_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="translate_0_100" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="translate_100_0" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="translate_200_200" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="upem_box_glyph" xMin="0" yMin="0" xMax="1000" yMax="1000">
+ <contour>
+ <pt x="0" y="0" on="1"/>
+ <pt x="0" y="1000" on="1"/>
+ <pt x="1000" y="1000" on="1"/>
+ <pt x="1000" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="zero" xMin="173" yMin="246" xMax="357" yMax="545">
+ <contour>
+ <pt x="357" y="374" on="1"/>
+ <pt x="357" y="303" on="0"/>
+ <pt x="308" y="246" on="0"/>
+ <pt x="265" y="246" on="1"/>
+ <pt x="223" y="246" on="0"/>
+ <pt x="173" y="303" on="0"/>
+ <pt x="173" y="374" on="1"/>
+ <pt x="173" y="419" on="1"/>
+ <pt x="173" y="490" on="0"/>
+ <pt x="223" y="545" on="0"/>
+ <pt x="265" y="545" on="1"/>
+ <pt x="307" y="545" on="0"/>
+ <pt x="357" y="490" on="0"/>
+ <pt x="357" y="419" on="1"/>
+ </contour>
+ <contour>
+ <pt x="320" y="425" on="1"/>
+ <pt x="320" y="474" on="0"/>
+ <pt x="292" y="515" on="0"/>
+ <pt x="265" y="515" on="1"/>
+ <pt x="238" y="515" on="0"/>
+ <pt x="210" y="474" on="0"/>
+ <pt x="210" y="425" on="1"/>
+ <pt x="210" y="368" on="1"/>
+ <pt x="210" y="320" on="0"/>
+ <pt x="239" y="276" on="0"/>
+ <pt x="265" y="276" on="1"/>
+ <pt x="292" y="276" on="0"/>
+ <pt x="320" y="320" on="0"/>
+ <pt x="320" y="368" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ COLRv1 Static Test Glyphs
+ </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">
+ COLRv1 Static Test Glyphs 2023-03-10T15:07:35.658876
+ </namerecord>
+ <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ COLRv1 Static Test Glyphs Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ 2023-03-10T15:07:35.658876
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ COLRv1StaticTestGlyphs-Regular
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ COLRv1 Static Test Glyphs
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ COLRv1 Static Test Glyphs 2023-03-10T15:07:35.658876
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ COLRv1 Static Test Glyphs Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ 2023-03-10T15:07:35.658876
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ COLRv1StaticTestGlyphs-Regular
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <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="upem_box_glyph"/>
+ <psName name="cross_glyph"/>
+ <psName name="scale_0.5_1.5_center_500.0_500.0"/>
+ <psName name="scale_1.5_1.5_center_500.0_500.0"/>
+ <psName name="scale_0.5_1.5_center_0_0"/>
+ <psName name="scale_1.5_1.5_center_0_0"/>
+ <psName name="scale_0.5_1.5_center_1000_1000"/>
+ <psName name="scale_1.5_1.5_center_1000_1000"/>
+ <psName name="rotate_10_center_0_0"/>
+ <psName name="rotate_-10_center_1000_1000"/>
+ <psName name="rotate_25_center_500.0_500.0"/>
+ <psName name="rotate_-15_center_500.0_500.0"/>
+ <psName name="skew_25_0_center_0_0"/>
+ <psName name="skew_25_0_center_500.0_500.0"/>
+ <psName name="skew_0_15_center_0_0"/>
+ <psName name="skew_0_15_center_500.0_500.0"/>
+ <psName name="skew_-10_20_center_500.0_500.0"/>
+ <psName name="skew_-10_20_center_1000_1000"/>
+ <psName name="transform_matrix_1_0_0_1_125_125"/>
+ <psName name="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <psName name="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <psName name="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <psName name="translate_0_0"/>
+ <psName name="translate_0_100"/>
+ <psName name="translate_0_-100"/>
+ <psName name="translate_100_0"/>
+ <psName name="translate_-100_0"/>
+ <psName name="translate_200_200"/>
+ <psName name="translate_-200_-200"/>
+ </extraNames>
+ </post>
+
+ <COLR>
+ <Version value="1"/>
+ <!-- BaseGlyphRecordCount=0 -->
+ <!-- LayerRecordCount=0 -->
+ <BaseGlyphList>
+ <!-- BaseGlyphCount=27 -->
+ <BaseGlyphPaintRecord index="0">
+ <BaseGlyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="1">
+ <BaseGlyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="2">
+ <BaseGlyph value="scale_0.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="16"><!-- PaintScale -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="3">
+ <BaseGlyph value="scale_1.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="20"><!-- PaintScaleUniform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="4">
+ <BaseGlyph value="scale_0.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="5">
+ <BaseGlyph value="scale_1.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="6">
+ <BaseGlyph value="rotate_10_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="24"><!-- PaintRotate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="10.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="7">
+ <BaseGlyph value="rotate_-10_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-10.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="8">
+ <BaseGlyph value="rotate_25_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="25.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="9">
+ <BaseGlyph value="rotate_-15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="10">
+ <BaseGlyph value="skew_25_0_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="11">
+ <BaseGlyph value="skew_25_0_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="12">
+ <BaseGlyph value="skew_0_15_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="13">
+ <BaseGlyph value="skew_0_15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="14">
+ <BaseGlyph value="skew_-10_20_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="15">
+ <BaseGlyph value="skew_-10_20_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="16">
+ <BaseGlyph value="transform_matrix_1_0_0_1_125_125"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.0"/>
+ <dx value="125.0"/>
+ <dy value="125.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="17">
+ <BaseGlyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.5"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.5"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="18">
+ <BaseGlyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="0.9659"/>
+ <yx value="0.2588"/>
+ <xy value="-0.2588"/>
+ <yy value="0.9659"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="19">
+ <BaseGlyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.6"/>
+ <yy value="1.0"/>
+ <dx value="-300.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="20">
+ <BaseGlyph value="translate_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="21">
+ <BaseGlyph value="translate_0_100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="22">
+ <BaseGlyph value="translate_0_-100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="-100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="23">
+ <BaseGlyph value="translate_100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="24">
+ <BaseGlyph value="translate_-100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="25">
+ <BaseGlyph value="translate_200_200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="200"/>
+ <dy value="200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="26">
+ <BaseGlyph value="translate_-200_-200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-200"/>
+ <dy value="-200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ </BaseGlyphList>
+ </COLR>
+
+ <CPAL>
+ <version value="1"/>
+ <numPaletteEntries value="7"/>
+ <palette index="0">
+ <color index="0" value="#FF0000FF"/>
+ <color index="1" value="#FFA500FF"/>
+ <color index="2" value="#FFFF00FF"/>
+ <color index="3" value="#008000FF"/>
+ <color index="4" value="#0000FFFF"/>
+ <color index="5" value="#4B0082FF"/>
+ <color index="6" value="#EE82EEFF"/>
+ </palette>
+ <palette index="1" type="2">
+ <color index="0" value="#2A294AFF"/>
+ <color index="1" value="#244163FF"/>
+ <color index="2" value="#1B6388FF"/>
+ <color index="3" value="#157DA3FF"/>
+ <color index="4" value="#0E9AC2FF"/>
+ <color index="5" value="#05BEE8FF"/>
+ <color index="6" value="#00D4FFFF"/>
+ </palette>
+ <palette index="2" type="1">
+ <color index="0" value="#FC7118FF"/>
+ <color index="1" value="#FB8115FF"/>
+ <color index="2" value="#FA9511FF"/>
+ <color index="3" value="#FAA80DFF"/>
+ <color index="4" value="#F9BE09FF"/>
+ <color index="5" value="#F8D304FF"/>
+ <color index="6" value="#F8E700FF"/>
+ </palette>
+ </CPAL>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/data/COLRv1-clip-boxes-q1-expected.ttx b/Tests/ttLib/tables/data/COLRv1-clip-boxes-q1-expected.ttx
new file mode 100644
index 00000000..c3b8ef62
--- /dev/null
+++ b/Tests/ttLib/tables/data/COLRv1-clip-boxes-q1-expected.ttx
@@ -0,0 +1,919 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <COLR>
+ <Version value="1"/>
+ <BaseGlyphList>
+ <!-- BaseGlyphCount=27 -->
+ <BaseGlyphPaintRecord index="0">
+ <BaseGlyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="1">
+ <BaseGlyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="2">
+ <BaseGlyph value="scale_0.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="16"><!-- PaintScale -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="3">
+ <BaseGlyph value="scale_1.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="20"><!-- PaintScaleUniform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="4">
+ <BaseGlyph value="scale_0.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="5">
+ <BaseGlyph value="scale_1.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="6">
+ <BaseGlyph value="rotate_10_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="24"><!-- PaintRotate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="10.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="7">
+ <BaseGlyph value="rotate_-10_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-10.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="8">
+ <BaseGlyph value="rotate_25_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="25.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="9">
+ <BaseGlyph value="rotate_-15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="10">
+ <BaseGlyph value="skew_25_0_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="11">
+ <BaseGlyph value="skew_25_0_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="12">
+ <BaseGlyph value="skew_0_15_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="13">
+ <BaseGlyph value="skew_0_15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="14">
+ <BaseGlyph value="skew_-10_20_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="15">
+ <BaseGlyph value="skew_-10_20_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="16">
+ <BaseGlyph value="transform_matrix_1_0_0_1_125_125"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.0"/>
+ <dx value="125.0"/>
+ <dy value="125.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="17">
+ <BaseGlyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.5"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.5"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="18">
+ <BaseGlyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="0.9659"/>
+ <yx value="0.2588"/>
+ <xy value="-0.2588"/>
+ <yy value="0.9659"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="19">
+ <BaseGlyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.6"/>
+ <yy value="1.0"/>
+ <dx value="-300.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="20">
+ <BaseGlyph value="translate_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="21">
+ <BaseGlyph value="translate_0_100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="22">
+ <BaseGlyph value="translate_0_-100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="-100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="23">
+ <BaseGlyph value="translate_100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="24">
+ <BaseGlyph value="translate_-100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="25">
+ <BaseGlyph value="translate_200_200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="200"/>
+ <dy value="200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="26">
+ <BaseGlyph value="translate_-200_-200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-200"/>
+ <dy value="-200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ </BaseGlyphList>
+ <ClipList Format="1">
+ <Clip>
+ <Glyph value="rotate_-10_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="170"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="845"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="rotate_-15_center_500.0_500.0"/>
+ <Glyph value="rotate_25_center_500.0_500.0"/>
+ <Glyph value="translate_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="rotate_10_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="155"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="830"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="125"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="1125"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="-125"/>
+ <xMax value="875"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="125"/>
+ <xMax value="750"/>
+ <yMax value="875"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_0_0"/>
+ <Glyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="1125"/>
+ <yMax value="1125"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="-125"/>
+ <yMin value="-125"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="125"/>
+ <yMin value="125"/>
+ <xMax value="875"/>
+ <yMax value="875"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_-10_20_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="157"/>
+ <yMin value="58"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_-10_20_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="245"/>
+ <yMin value="240"/>
+ <xMax value="755"/>
+ <yMax value="760"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_0_15_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="891"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_0_15_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="243"/>
+ <xMax value="750"/>
+ <yMax value="757"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_25_0_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="5"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_25_0_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="238"/>
+ <yMin value="250"/>
+ <xMax value="762"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="105"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="861"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <ClipBox Format="1">
+ <xMin value="235"/>
+ <yMin value="250"/>
+ <xMax value="766"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="transform_matrix_1_0_0_1_125_125"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="875"/>
+ <yMax value="875"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_-100_0"/>
+ <ClipBox Format="1">
+ <xMin value="150"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_-200_-200"/>
+ <ClipBox Format="1">
+ <xMin value="50"/>
+ <yMin value="50"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_0_-100"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="150"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_0_100"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="850"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_100_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="850"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_200_200"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="950"/>
+ <yMax value="950"/>
+ </ClipBox>
+ </Clip>
+ </ClipList>
+ </COLR>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/data/COLRv1-clip-boxes-q10-expected.ttx b/Tests/ttLib/tables/data/COLRv1-clip-boxes-q10-expected.ttx
new file mode 100644
index 00000000..f6b66f5c
--- /dev/null
+++ b/Tests/ttLib/tables/data/COLRv1-clip-boxes-q10-expected.ttx
@@ -0,0 +1,911 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <COLR>
+ <Version value="1"/>
+ <BaseGlyphList>
+ <!-- BaseGlyphCount=27 -->
+ <BaseGlyphPaintRecord index="0">
+ <BaseGlyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="1">
+ <BaseGlyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="2">
+ <BaseGlyph value="scale_0.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="16"><!-- PaintScale -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="3">
+ <BaseGlyph value="scale_1.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="20"><!-- PaintScaleUniform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="4">
+ <BaseGlyph value="scale_0.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="5">
+ <BaseGlyph value="scale_1.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="6">
+ <BaseGlyph value="rotate_10_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="24"><!-- PaintRotate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="10.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="7">
+ <BaseGlyph value="rotate_-10_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-10.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="8">
+ <BaseGlyph value="rotate_25_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="25.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="9">
+ <BaseGlyph value="rotate_-15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="10">
+ <BaseGlyph value="skew_25_0_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="11">
+ <BaseGlyph value="skew_25_0_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="12">
+ <BaseGlyph value="skew_0_15_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="13">
+ <BaseGlyph value="skew_0_15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="14">
+ <BaseGlyph value="skew_-10_20_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="15">
+ <BaseGlyph value="skew_-10_20_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="16">
+ <BaseGlyph value="transform_matrix_1_0_0_1_125_125"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.0"/>
+ <dx value="125.0"/>
+ <dy value="125.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="17">
+ <BaseGlyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.5"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.5"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="18">
+ <BaseGlyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="0.9659"/>
+ <yx value="0.2588"/>
+ <xy value="-0.2588"/>
+ <yy value="0.9659"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="19">
+ <BaseGlyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.6"/>
+ <yy value="1.0"/>
+ <dx value="-300.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="20">
+ <BaseGlyph value="translate_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="21">
+ <BaseGlyph value="translate_0_100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="22">
+ <BaseGlyph value="translate_0_-100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="-100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="23">
+ <BaseGlyph value="translate_100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="24">
+ <BaseGlyph value="translate_-100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="25">
+ <BaseGlyph value="translate_200_200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="200"/>
+ <dy value="200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="26">
+ <BaseGlyph value="translate_-200_-200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-200"/>
+ <dy value="-200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ </BaseGlyphList>
+ <ClipList Format="1">
+ <Clip>
+ <Glyph value="rotate_-10_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="170"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="850"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="rotate_-15_center_500.0_500.0"/>
+ <Glyph value="rotate_25_center_500.0_500.0"/>
+ <Glyph value="translate_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="rotate_10_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="150"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="830"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="120"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="1130"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="-130"/>
+ <xMax value="880"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="120"/>
+ <xMax value="750"/>
+ <yMax value="880"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_0_0"/>
+ <Glyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="1130"/>
+ <yMax value="1130"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="-130"/>
+ <yMin value="-130"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="120"/>
+ <yMin value="120"/>
+ <xMax value="880"/>
+ <yMax value="880"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_-10_20_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="150"/>
+ <yMin value="50"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_-10_20_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="240"/>
+ <yMin value="240"/>
+ <xMax value="760"/>
+ <yMax value="760"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_0_15_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="900"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_0_15_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="240"/>
+ <xMax value="750"/>
+ <yMax value="760"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_25_0_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="0"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_25_0_center_500.0_500.0"/>
+ <Glyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <ClipBox Format="1">
+ <xMin value="230"/>
+ <yMin value="250"/>
+ <xMax value="770"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="100"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="870"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="transform_matrix_1_0_0_1_125_125"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="880"/>
+ <yMax value="880"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_-100_0"/>
+ <ClipBox Format="1">
+ <xMin value="150"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_-200_-200"/>
+ <ClipBox Format="1">
+ <xMin value="50"/>
+ <yMin value="50"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_0_-100"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="150"/>
+ <xMax value="750"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_0_100"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="750"/>
+ <yMax value="850"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_100_0"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="850"/>
+ <yMax value="750"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_200_200"/>
+ <ClipBox Format="1">
+ <xMin value="250"/>
+ <yMin value="250"/>
+ <xMax value="950"/>
+ <yMax value="950"/>
+ </ClipBox>
+ </Clip>
+ </ClipList>
+ </COLR>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/data/COLRv1-clip-boxes-q100-expected.ttx b/Tests/ttLib/tables/data/COLRv1-clip-boxes-q100-expected.ttx
new file mode 100644
index 00000000..f4fee78a
--- /dev/null
+++ b/Tests/ttLib/tables/data/COLRv1-clip-boxes-q100-expected.ttx
@@ -0,0 +1,863 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <COLR>
+ <Version value="1"/>
+ <BaseGlyphList>
+ <!-- BaseGlyphCount=27 -->
+ <BaseGlyphPaintRecord index="0">
+ <BaseGlyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="1">
+ <BaseGlyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="2">
+ <BaseGlyph value="scale_0.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="16"><!-- PaintScale -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="3">
+ <BaseGlyph value="scale_1.5_1.5_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="20"><!-- PaintScaleUniform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="4">
+ <BaseGlyph value="scale_0.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="18"><!-- PaintScaleAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scaleX value="0.5"/>
+ <scaleY value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="5">
+ <BaseGlyph value="scale_1.5_1.5_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="22"><!-- PaintScaleUniformAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <scale value="1.5"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="6">
+ <BaseGlyph value="rotate_10_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="24"><!-- PaintRotate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="10.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="7">
+ <BaseGlyph value="rotate_-10_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-10.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="8">
+ <BaseGlyph value="rotate_25_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="25.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="9">
+ <BaseGlyph value="rotate_-15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="26"><!-- PaintRotateAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <angle value="-15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="10">
+ <BaseGlyph value="skew_25_0_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="11">
+ <BaseGlyph value="skew_25_0_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="25.0"/>
+ <ySkewAngle value="0.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="12">
+ <BaseGlyph value="skew_0_15_center_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="28"><!-- PaintSkew -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="13">
+ <BaseGlyph value="skew_0_15_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="0.0"/>
+ <ySkewAngle value="15.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="14">
+ <BaseGlyph value="skew_-10_20_center_500.0_500.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="500"/>
+ <centerY value="500"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="15">
+ <BaseGlyph value="skew_-10_20_center_1000_1000"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="30"><!-- PaintSkewAroundCenter -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <xSkewAngle value="-10.0"/>
+ <ySkewAngle value="20.0"/>
+ <centerX value="1000"/>
+ <centerY value="1000"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="16">
+ <BaseGlyph value="transform_matrix_1_0_0_1_125_125"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.0"/>
+ <dx value="125.0"/>
+ <dy value="125.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="17">
+ <BaseGlyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.5"/>
+ <yx value="0.0"/>
+ <xy value="0.0"/>
+ <yy value="1.5"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="18">
+ <BaseGlyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="0.9659"/>
+ <yx value="0.2588"/>
+ <xy value="-0.2588"/>
+ <yy value="0.9659"/>
+ <dx value="0.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="19">
+ <BaseGlyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="12"><!-- PaintTransform -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <Transform>
+ <xx value="1.0"/>
+ <yx value="0.0"/>
+ <xy value="0.6"/>
+ <yy value="1.0"/>
+ <dx value="-300.0"/>
+ <dy value="0.0"/>
+ </Transform>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="20">
+ <BaseGlyph value="translate_0_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="21">
+ <BaseGlyph value="translate_0_100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="22">
+ <BaseGlyph value="translate_0_-100"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="0"/>
+ <dy value="-100"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="23">
+ <BaseGlyph value="translate_100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="24">
+ <BaseGlyph value="translate_-100_0"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-100"/>
+ <dy value="0"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="25">
+ <BaseGlyph value="translate_200_200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="200"/>
+ <dy value="200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ <BaseGlyphPaintRecord index="26">
+ <BaseGlyph value="translate_-200_-200"/>
+ <Paint Format="32"><!-- PaintComposite -->
+ <SourcePaint Format="14"><!-- PaintTranslate -->
+ <Paint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="1"/>
+ <Alpha value="0.7"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </Paint>
+ <dx value="-200"/>
+ <dy value="-200"/>
+ </SourcePaint>
+ <CompositeMode value="dest_over"/>
+ <BackdropPaint Format="10"><!-- PaintGlyph -->
+ <Paint Format="2"><!-- PaintSolid -->
+ <PaletteIndex value="4"/>
+ <Alpha value="0.5"/>
+ </Paint>
+ <Glyph value="cross_glyph"/>
+ </BackdropPaint>
+ </Paint>
+ </BaseGlyphPaintRecord>
+ </BaseGlyphList>
+ <ClipList Format="1">
+ <Clip>
+ <Glyph value="rotate_-10_center_1000_1000"/>
+ <Glyph value="rotate_10_center_0_0"/>
+ <Glyph value="transform_matrix_0.9659_0.2588_-0.2588_0.9659_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="100"/>
+ <yMin value="200"/>
+ <xMax value="800"/>
+ <yMax value="900"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="rotate_-15_center_500.0_500.0"/>
+ <Glyph value="rotate_25_center_500.0_500.0"/>
+ <Glyph value="skew_-10_20_center_500.0_500.0"/>
+ <Glyph value="skew_0_15_center_500.0_500.0"/>
+ <Glyph value="skew_25_0_center_500.0_500.0"/>
+ <Glyph value="transform_matrix_1.0_0.0_0.6_1.0_-300.0_0.0"/>
+ <Glyph value="translate_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="200"/>
+ <xMax value="800"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="100"/>
+ <yMin value="200"/>
+ <xMax value="800"/>
+ <yMax value="1200"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="-200"/>
+ <xMax value="900"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_0.5_1.5_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="100"/>
+ <xMax value="800"/>
+ <yMax value="900"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_0_0"/>
+ <Glyph value="transform_matrix_1.5_0_0_1.5_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="200"/>
+ <xMax value="1200"/>
+ <yMax value="1200"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="-200"/>
+ <yMin value="-200"/>
+ <xMax value="800"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="scale_1.5_1.5_center_500.0_500.0"/>
+ <ClipBox Format="1">
+ <xMin value="100"/>
+ <yMin value="100"/>
+ <xMax value="900"/>
+ <yMax value="900"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_-10_20_center_1000_1000"/>
+ <ClipBox Format="1">
+ <xMin value="100"/>
+ <yMin value="0"/>
+ <xMax value="800"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_0_15_center_0_0"/>
+ <Glyph value="translate_0_100"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="200"/>
+ <xMax value="800"/>
+ <yMax value="900"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="skew_25_0_center_0_0"/>
+ <ClipBox Format="1">
+ <xMin value="0"/>
+ <yMin value="200"/>
+ <xMax value="800"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="transform_matrix_1_0_0_1_125_125"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="200"/>
+ <xMax value="900"/>
+ <yMax value="900"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_-100_0"/>
+ <ClipBox Format="1">
+ <xMin value="100"/>
+ <yMin value="200"/>
+ <xMax value="800"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_-200_-200"/>
+ <ClipBox Format="1">
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="800"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_0_-100"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="100"/>
+ <xMax value="800"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_100_0"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="200"/>
+ <xMax value="900"/>
+ <yMax value="800"/>
+ </ClipBox>
+ </Clip>
+ <Clip>
+ <Glyph value="translate_200_200"/>
+ <ClipBox Format="1">
+ <xMin value="200"/>
+ <yMin value="200"/>
+ <xMax value="1000"/>
+ <yMax value="1000"/>
+ </ClipBox>
+ </Clip>
+ </ClipList>
+ </COLR>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/data/NotoSans-VF-cubic.subset.ttf b/Tests/ttLib/tables/data/NotoSans-VF-cubic.subset.ttf
new file mode 100644
index 00000000..604d4281
--- /dev/null
+++ b/Tests/ttLib/tables/data/NotoSans-VF-cubic.subset.ttf
Binary files differ
diff --git a/Tests/ttLib/tables/data/_g_l_y_f_instructions.ttx b/Tests/ttLib/tables/data/_g_l_y_f_instructions.ttx
new file mode 100644
index 00000000..d090a25f
--- /dev/null
+++ b/Tests/ttLib/tables/data/_g_l_y_f_instructions.ttx
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="NULL"/>
+ <GlyphID id="2" name="nonmarkingreturn"/>
+ <GlyphID id="3" name="A"/>
+ <GlyphID id="4" name="B"/>
+ </GlyphOrder>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="5"/>
+ <maxPoints value="4"/>
+ <maxContours value="1"/>
+ <maxCompositePoints value="4"/>
+ <maxCompositeContours value="1"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="10"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="512"/>
+ <maxSizeOfInstructions value="371"/>
+ <maxComponentElements value="1"/>
+ <maxComponentDepth value="1"/>
+ </maxp>
+
+ <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="100" yMin="0" xMax="477" yMax="700">
+ <contour>
+ <pt x="100" y="700" on="1"/>
+ <pt x="477" y="700" on="1"/>
+ <pt x="477" y="0" on="1"/>
+ <pt x="100" y="0" on="1"/>
+ </contour>
+ <instructions>
+ <assembly>
+ SVTCA[0] /* SetFPVectorToAxis */
+ PUSHW[ ] /* 1 value pushed */
+ 3
+ MDAP[1] /* MoveDirectAbsPt */
+ IUP[0] /* InterpolateUntPts */
+ IUP[1] /* InterpolateUntPts */
+ </assembly>
+ </instructions>
+ </TTGlyph>
+
+ <TTGlyph name="B" xMin="100" yMin="0" xMax="477" yMax="700">
+ <component glyphName="A" x="0" y="0" flags="0x204"/>
+ <instructions>
+ <assembly>
+ SVTCA[0] /* SetFPVectorToAxis */
+ PUSHW[ ] /* 1 value pushed */
+ 1
+ MDAP[1] /* MoveDirectAbsPt */
+ IUP[0] /* InterpolateUntPts */
+ IUP[1] /* InterpolateUntPts */
+ </assembly>
+ </instructions>
+ </TTGlyph>
+
+ <TTGlyph name="NULL"/><!-- contains no outline data -->
+
+ <TTGlyph name="nonmarkingreturn"/><!-- contains no outline data -->
+
+ </glyf>
+
+</ttFont>
diff --git a/Tests/ttLib/tables/otBase_test.py b/Tests/ttLib/tables/otBase_test.py
index ce0416e4..27efcba9 100644
--- a/Tests/ttLib/tables/otBase_test.py
+++ b/Tests/ttLib/tables/otBase_test.py
@@ -26,8 +26,7 @@ class OTTableReaderTest(unittest.TestCase):
def test_readUShortArray(self):
reader = OTTableReader(deHexStr("DE AD BE EF CA FE"))
- self.assertEqual(list(reader.readUShortArray(3)),
- [0xDEAD, 0xBEEF, 0xCAFE])
+ self.assertEqual(list(reader.readUShortArray(3)), [0xDEAD, 0xBEEF, 0xCAFE])
self.assertEqual(reader.pos, 6)
def test_readUInt24(self):
@@ -91,4 +90,5 @@ class OTTableWriterTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/otConverters_test.py b/Tests/ttLib/tables/otConverters_test.py
index 1aff03bd..94b62a1f 100644
--- a/Tests/ttLib/tables/otConverters_test.py
+++ b/Tests/ttLib/tables/otConverters_test.py
@@ -22,8 +22,10 @@ class Char64Test(unittest.TestCase):
data = self.converter.read(reader, self.font, {})
self.assertEqual(data, "Hello � world")
self.assertEqual(reader.pos, 64)
- self.assertIn('replaced non-ASCII characters in "Hello � world"',
- [r.msg for r in captor.records])
+ self.assertIn(
+ 'replaced non-ASCII characters in "Hello � world"',
+ [r.msg for r in captor.records],
+ )
def test_write(self):
writer = OTTableWriter()
@@ -35,16 +37,20 @@ class Char64Test(unittest.TestCase):
with CapturingLogHandler(otConverters.log, "WARNING") as captor:
self.converter.write(writer, self.font, {}, "Hello ☃")
self.assertEqual(writer.getData(), b"Hello ?" + 57 * b"\0")
- self.assertIn('replacing non-ASCII characters in "Hello ☃"',
- [r.msg for r in captor.records])
+ self.assertIn(
+ 'replacing non-ASCII characters in "Hello ☃"',
+ [r.msg for r in captor.records],
+ )
def test_write_truncated(self):
writer = OTTableWriter()
with CapturingLogHandler(otConverters.log, "WARNING") as captor:
self.converter.write(writer, self.font, {}, "A" * 80)
self.assertEqual(writer.getData(), b"A" * 64)
- self.assertIn('truncating overlong "' + "A" * 80 + '" to 64 bytes',
- [r.msg for r in captor.records])
+ self.assertIn(
+ 'truncating overlong "' + "A" * 80 + '" to 64 bytes',
+ [r.msg for r in captor.records],
+ )
def test_xmlRead(self):
value = self.converter.xmlRead({"value": "Foo"}, [], self.font)
@@ -52,20 +58,23 @@ class Char64Test(unittest.TestCase):
def test_xmlWrite(self):
writer = makeXMLWriter()
- self.converter.xmlWrite(writer, self.font, "Hello world", "Element",
- [("attr", "v")])
+ self.converter.xmlWrite(
+ writer, self.font, "Hello world", "Element", [("attr", "v")]
+ )
xml = writer.file.getvalue().decode("utf-8").rstrip()
self.assertEqual(xml, '<Element attr="v" value="Hello world"/>')
class GlyphIDTest(unittest.TestCase):
font = FakeFont(".notdef A B C".split())
- converter = otConverters.GlyphID('GlyphID', 0, None, None)
+ converter = otConverters.GlyphID("GlyphID", 0, None, None)
def test_readArray(self):
reader = OTTableReader(deHexStr("0002 0001 DEAD 0002"))
- self.assertEqual(self.converter.readArray(reader, self.font, {}, 4),
- ["B", "A", "glyph57005", "B"])
+ self.assertEqual(
+ self.converter.readArray(reader, self.font, {}, 4),
+ ["B", "A", "glyph57005", "B"],
+ )
self.assertEqual(reader.pos, 8)
def test_read(self):
@@ -81,7 +90,7 @@ class GlyphIDTest(unittest.TestCase):
class LongTest(unittest.TestCase):
font = FakeFont([])
- converter = otConverters.Long('Long', 0, None, None)
+ converter = otConverters.Long("Long", 0, None, None)
def test_read(self):
reader = OTTableReader(deHexStr("FF0000EE"))
@@ -105,12 +114,12 @@ class LongTest(unittest.TestCase):
class NameIDTest(unittest.TestCase):
- converter = otConverters.NameID('NameID', 0, None, None)
+ converter = otConverters.NameID("NameID", 0, None, None)
def makeFont(self):
- nameTable = newTable('name')
- nameTable.setName(u"Demibold Condensed", 0x123, 3, 0, 0x409)
- nameTable.setName(u"Copyright 2018", 0, 3, 0, 0x409)
+ nameTable = newTable("name")
+ nameTable.setName("Demibold Condensed", 0x123, 3, 0, 0x409)
+ nameTable.setName("Copyright 2018", 0, 3, 0, 0x409)
return {"name": nameTable}
def test_read(self):
@@ -125,33 +134,36 @@ class NameIDTest(unittest.TestCase):
def test_xmlWrite(self):
writer = makeXMLWriter()
- self.converter.xmlWrite(writer, self.makeFont(), 291,
- "FooNameID", [("attr", "val")])
+ self.converter.xmlWrite(
+ writer, self.makeFont(), 291, "FooNameID", [("attr", "val")]
+ )
xml = writer.file.getvalue().decode("utf-8").rstrip()
self.assertEqual(
- xml,
- '<FooNameID attr="val" value="291"/> <!-- Demibold Condensed -->')
+ xml, '<FooNameID attr="val" value="291"/> <!-- Demibold Condensed -->'
+ )
def test_xmlWrite_missingID(self):
writer = makeXMLWriter()
with CapturingLogHandler(otConverters.log, "WARNING") as captor:
- self.converter.xmlWrite(writer, self.makeFont(), 666,
- "Entity", [("attrib", "val")])
- self.assertIn("name id 666 missing from name table",
- [r.msg for r in captor.records])
+ self.converter.xmlWrite(
+ writer, self.makeFont(), 666, "Entity", [("attrib", "val")]
+ )
+ self.assertIn(
+ "name id 666 missing from name table", [r.msg for r in captor.records]
+ )
xml = writer.file.getvalue().decode("utf-8").rstrip()
self.assertEqual(
xml,
- '<Entity attrib="val"'
- ' value="666"/> <!-- missing from name table -->')
+ '<Entity attrib="val"' ' value="666"/> <!-- missing from name table -->',
+ )
def test_xmlWrite_NULL(self):
writer = makeXMLWriter()
- self.converter.xmlWrite(writer, self.makeFont(), 0,
- "FooNameID", [("attr", "val")])
+ self.converter.xmlWrite(
+ writer, self.makeFont(), 0, "FooNameID", [("attr", "val")]
+ )
xml = writer.file.getvalue().decode("utf-8").rstrip()
- self.assertEqual(
- xml, '<FooNameID attr="val" value="0"/>')
+ self.assertEqual(xml, '<FooNameID attr="val" value="0"/>')
class UInt8Test(unittest.TestCase):
@@ -181,8 +193,9 @@ class UInt8Test(unittest.TestCase):
class AATLookupTest(unittest.TestCase):
font = FakeFont(".notdef A B C D E F G H A.alt B.alt".split())
- converter = otConverters.AATLookup("AATLookup", 0, None,
- tableClass=otConverters.GlyphID)
+ converter = otConverters.AATLookup(
+ "AATLookup", 0, None, tableClass=otConverters.GlyphID
+ )
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
@@ -193,170 +206,228 @@ class AATLookupTest(unittest.TestCase):
def test_readFormat0(self):
reader = OTTableReader(deHexStr("0000 0000 0001 0002 0000 7D00 0001"))
- self.assertEqual(self.converter.read(reader, self.font, None), {
- ".notdef": ".notdef",
- "A": "A",
- "B": "B",
- "C": ".notdef",
- "D": "glyph32000",
- "E": "A"
- })
+ self.assertEqual(
+ self.converter.read(reader, self.font, None),
+ {
+ ".notdef": ".notdef",
+ "A": "A",
+ "B": "B",
+ "C": ".notdef",
+ "D": "glyph32000",
+ "E": "A",
+ },
+ )
def test_readFormat2(self):
- reader = OTTableReader(deHexStr(
- "0002 0006 0002 000C 0001 0006 "
- "0002 0001 0003 " # glyph A..B: map to C
- "0007 0005 0008 " # glyph E..G: map to H
- "FFFF FFFF FFFF")) # end of search table
- self.assertEqual(self.converter.read(reader, self.font, None), {
- "A": "C",
- "B": "C",
- "E": "H",
- "F": "H",
- "G": "H",
- })
+ reader = OTTableReader(
+ deHexStr(
+ "0002 0006 0002 000C 0001 0006 "
+ "0002 0001 0003 " # glyph A..B: map to C
+ "0007 0005 0008 " # glyph E..G: map to H
+ "FFFF FFFF FFFF"
+ )
+ ) # end of search table
+ self.assertEqual(
+ self.converter.read(reader, self.font, None),
+ {
+ "A": "C",
+ "B": "C",
+ "E": "H",
+ "F": "H",
+ "G": "H",
+ },
+ )
def test_readFormat4(self):
- reader = OTTableReader(deHexStr(
- "0004 0006 0003 000C 0001 0006 "
- "0002 0001 001E " # glyph 1..2: mapping at offset 0x1E
- "0005 0004 001E " # glyph 4..5: mapping at offset 0x1E
- "FFFF FFFF FFFF " # end of search table
- "0007 0008")) # offset 0x18: glyphs [7, 8] = [G, H]
- self.assertEqual(self.converter.read(reader, self.font, None), {
- "A": "G",
- "B": "H",
- "D": "G",
- "E": "H",
- })
+ reader = OTTableReader(
+ deHexStr(
+ "0004 0006 0003 000C 0001 0006 "
+ "0002 0001 001E " # glyph 1..2: mapping at offset 0x1E
+ "0005 0004 001E " # glyph 4..5: mapping at offset 0x1E
+ "FFFF FFFF FFFF " # end of search table
+ "0007 0008"
+ )
+ ) # offset 0x18: glyphs [7, 8] = [G, H]
+ self.assertEqual(
+ self.converter.read(reader, self.font, None),
+ {
+ "A": "G",
+ "B": "H",
+ "D": "G",
+ "E": "H",
+ },
+ )
def test_readFormat6(self):
- reader = OTTableReader(deHexStr(
- "0006 0004 0002 0008 0001 0004 "
- "0003 0001 " # C --> A
- "0005 0002 " # E --> B
- "FFFF FFFF")) # end of search table
- self.assertEqual(self.converter.read(reader, self.font, None), {
- "C": "A",
- "E": "B",
- })
+ reader = OTTableReader(
+ deHexStr(
+ "0006 0004 0002 0008 0001 0004 "
+ "0003 0001 " # C --> A
+ "0005 0002 " # E --> B
+ "FFFF FFFF"
+ )
+ ) # end of search table
+ self.assertEqual(
+ self.converter.read(reader, self.font, None),
+ {
+ "C": "A",
+ "E": "B",
+ },
+ )
def test_readFormat8(self):
- reader = OTTableReader(deHexStr(
- "0008 "
- "0003 0003 " # first: C, count: 3
- "0007 0001 0002")) # [G, A, B]
- self.assertEqual(self.converter.read(reader, self.font, None), {
- "C": "G",
- "D": "A",
- "E": "B",
- })
+ reader = OTTableReader(
+ deHexStr("0008 " "0003 0003 " "0007 0001 0002") # first: C, count: 3
+ ) # [G, A, B]
+ self.assertEqual(
+ self.converter.read(reader, self.font, None),
+ {
+ "C": "G",
+ "D": "A",
+ "E": "B",
+ },
+ )
def test_readUnknownFormat(self):
reader = OTTableReader(deHexStr("0009"))
self.assertRaisesRegex(
AssertionError,
"unsupported lookup format: 9",
- self.converter.read, reader, self.font, None)
+ self.converter.read,
+ reader,
+ self.font,
+ None,
+ )
def test_writeFormat0(self):
writer = OTTableWriter()
font = FakeFont(".notdef A B C".split())
- self.converter.write(writer, font, {}, {
- ".notdef": ".notdef",
- "A": "C",
- "B": "C",
- "C": "A"
- })
+ self.converter.write(
+ writer, font, {}, {".notdef": ".notdef", "A": "C", "B": "C", "C": "A"}
+ )
self.assertEqual(writer.getData(), deHexStr("0000 0000 0003 0003 0001"))
def test_writeFormat2(self):
writer = OTTableWriter()
font = FakeFont(".notdef A B C D E F G H".split())
- self.converter.write(writer, font, {}, {
- "B": "C",
- "C": "C",
- "D": "C",
- "E": "C",
- "G": "A",
- "H": "A",
- })
- self.assertEqual(writer.getData(), deHexStr(
- "0002 " # format=2
- "0006 " # binSrchHeader.unitSize=6
- "0002 " # binSrchHeader.nUnits=2
- "000C " # binSrchHeader.searchRange=12
- "0001 " # binSrchHeader.entrySelector=1
- "0000 " # binSrchHeader.rangeShift=0
- "0005 0002 0003 " # segments[0].lastGlyph=E, firstGlyph=B, value=C
- "0008 0007 0001 " # segments[1].lastGlyph=H, firstGlyph=G, value=A
- "FFFF FFFF 0000 " # segments[2]=<END>
- ))
+ self.converter.write(
+ writer,
+ font,
+ {},
+ {
+ "B": "C",
+ "C": "C",
+ "D": "C",
+ "E": "C",
+ "G": "A",
+ "H": "A",
+ },
+ )
+ self.assertEqual(
+ writer.getData(),
+ deHexStr(
+ "0002 " # format=2
+ "0006 " # binSrchHeader.unitSize=6
+ "0002 " # binSrchHeader.nUnits=2
+ "000C " # binSrchHeader.searchRange=12
+ "0001 " # binSrchHeader.entrySelector=1
+ "0000 " # binSrchHeader.rangeShift=0
+ "0005 0002 0003 " # segments[0].lastGlyph=E, firstGlyph=B, value=C
+ "0008 0007 0001 " # segments[1].lastGlyph=H, firstGlyph=G, value=A
+ "FFFF FFFF 0000 " # segments[2]=<END>
+ ),
+ )
def test_writeFormat6(self):
writer = OTTableWriter()
font = FakeFont(".notdef A B C D E".split())
- self.converter.write(writer, font, {}, {
- "A": "C",
- "C": "B",
- "D": "D",
- "E": "E",
- })
- self.assertEqual(writer.getData(), deHexStr(
- "0006 " # format=6
- "0004 " # binSrchHeader.unitSize=4
- "0004 " # binSrchHeader.nUnits=4
- "0010 " # binSrchHeader.searchRange=16
- "0002 " # binSrchHeader.entrySelector=2
- "0000 " # binSrchHeader.rangeShift=0
- "0001 0003 " # entries[0].glyph=A, .value=C
- "0003 0002 " # entries[1].glyph=C, .value=B
- "0004 0004 " # entries[2].glyph=D, .value=D
- "0005 0005 " # entries[3].glyph=E, .value=E
- "FFFF 0000 " # entries[4]=<END>
- ))
+ self.converter.write(
+ writer,
+ font,
+ {},
+ {
+ "A": "C",
+ "C": "B",
+ "D": "D",
+ "E": "E",
+ },
+ )
+ self.assertEqual(
+ writer.getData(),
+ deHexStr(
+ "0006 " # format=6
+ "0004 " # binSrchHeader.unitSize=4
+ "0004 " # binSrchHeader.nUnits=4
+ "0010 " # binSrchHeader.searchRange=16
+ "0002 " # binSrchHeader.entrySelector=2
+ "0000 " # binSrchHeader.rangeShift=0
+ "0001 0003 " # entries[0].glyph=A, .value=C
+ "0003 0002 " # entries[1].glyph=C, .value=B
+ "0004 0004 " # entries[2].glyph=D, .value=D
+ "0005 0005 " # entries[3].glyph=E, .value=E
+ "FFFF 0000 " # entries[4]=<END>
+ ),
+ )
def test_writeFormat8(self):
writer = OTTableWriter()
font = FakeFont(".notdef A B C D E F G H".split())
- self.converter.write(writer, font, {}, {
- "B": "B",
- "C": "A",
- "D": "B",
- "E": "C",
- "F": "B",
- "G": "A",
- })
- self.assertEqual(writer.getData(), deHexStr(
- "0008 " # format=8
- "0002 " # firstGlyph=B
- "0006 " # glyphCount=6
- "0002 0001 0002 0003 0002 0001" # valueArray=[B, A, B, C, B, A]
- ))
+ self.converter.write(
+ writer,
+ font,
+ {},
+ {
+ "B": "B",
+ "C": "A",
+ "D": "B",
+ "E": "C",
+ "F": "B",
+ "G": "A",
+ },
+ )
+ self.assertEqual(
+ writer.getData(),
+ deHexStr(
+ "0008 " # format=8
+ "0002 " # firstGlyph=B
+ "0006 " # glyphCount=6
+ "0002 0001 0002 0003 0002 0001" # valueArray=[B, A, B, C, B, A]
+ ),
+ )
def test_xmlRead(self):
- value = self.converter.xmlRead({}, [
- ("Lookup", {"glyph": "A", "value": "A.alt"}, []),
- ("Lookup", {"glyph": "B", "value": "B.alt"}, []),
- ], self.font)
+ value = self.converter.xmlRead(
+ {},
+ [
+ ("Lookup", {"glyph": "A", "value": "A.alt"}, []),
+ ("Lookup", {"glyph": "B", "value": "B.alt"}, []),
+ ],
+ self.font,
+ )
self.assertEqual(value, {"A": "A.alt", "B": "B.alt"})
def test_xmlWrite(self):
writer = makeXMLWriter()
- self.converter.xmlWrite(writer, self.font,
- value={"A": "A.alt", "B": "B.alt"},
- name="Foo", attrs=[("attr", "val")])
+ self.converter.xmlWrite(
+ writer,
+ self.font,
+ value={"A": "A.alt", "B": "B.alt"},
+ name="Foo",
+ attrs=[("attr", "val")],
+ )
xml = writer.file.getvalue().decode("utf-8").splitlines()
- self.assertEqual(xml, [
- '<Foo attr="val">',
- ' <Lookup glyph="A" value="A.alt"/>',
- ' <Lookup glyph="B" value="B.alt"/>',
- '</Foo>',
- ])
+ self.assertEqual(
+ xml,
+ [
+ '<Foo attr="val">',
+ ' <Lookup glyph="A" value="A.alt"/>',
+ ' <Lookup glyph="B" value="B.alt"/>',
+ "</Foo>",
+ ],
+ )
class LazyListTest(unittest.TestCase):
-
def test_slice(self):
ll = otConverters._LazyList([10, 11, 12, 13])
sl = ll[:]
@@ -426,4 +497,5 @@ class LazyListTest(unittest.TestCase):
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/otTables_test.py b/Tests/ttLib/tables/otTables_test.py
index 3f74b7a9..db33e4c6 100644
--- a/Tests/ttLib/tables/otTables_test.py
+++ b/Tests/ttLib/tables/otTables_test.py
@@ -21,10 +21,7 @@ class SingleSubstTest(unittest.TestCase):
def test_postRead_format1(self):
table = otTables.SingleSubst()
table.Format = 1
- rawTable = {
- "Coverage": makeCoverage(["A", "B", "C"]),
- "DeltaGlyphID": 5
- }
+ rawTable = {"Coverage": makeCoverage(["A", "B", "C"]), "DeltaGlyphID": 5}
table.postRead(rawTable, self.font)
self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"})
@@ -34,7 +31,7 @@ class SingleSubstTest(unittest.TestCase):
rawTable = {
"Coverage": makeCoverage(["A", "B", "C"]),
"GlyphCount": 3,
- "Substitute": ["c", "b", "a"]
+ "Substitute": ["c", "b", "a"],
}
table.postRead(rawTable, self.font)
self.assertEqual(table.mapping, {"A": "c", "B": "b", "C": "a"})
@@ -74,18 +71,22 @@ class SingleSubstTest(unittest.TestCase):
table = otTables.SingleSubst()
table.mapping = {"A": "a", "B": "b", "C": "c"}
table.toXML2(writer, self.font)
- self.assertEqual(writer.file.getvalue().splitlines()[1:], [
- '<Substitution in="A" out="a"/>',
- '<Substitution in="B" out="b"/>',
- '<Substitution in="C" out="c"/>',
- ])
+ self.assertEqual(
+ writer.file.getvalue().splitlines()[1:],
+ [
+ '<Substitution in="A" out="a"/>',
+ '<Substitution in="B" out="b"/>',
+ '<Substitution in="C" out="c"/>',
+ ],
+ )
def test_fromXML(self):
table = otTables.SingleSubst()
for name, attrs, content in parseXML(
- '<Substitution in="A" out="a"/>'
- '<Substitution in="B" out="b"/>'
- '<Substitution in="C" out="c"/>'):
+ '<Substitution in="A" out="a"/>'
+ '<Substitution in="B" out="b"/>'
+ '<Substitution in="C" out="c"/>'
+ ):
table.fromXML(name, attrs, content, self.font)
self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"})
@@ -101,16 +102,10 @@ class MultipleSubstTest(unittest.TestCase):
table.Format = 1
rawTable = {
"Coverage": makeCoverage(["c_t", "f_f_i"]),
- "Sequence": [
- makeSequence(["c", "t"]),
- makeSequence(["f", "f", "i"])
- ]
+ "Sequence": [makeSequence(["c", "t"]), makeSequence(["f", "f", "i"])],
}
table.postRead(rawTable, self.font)
- self.assertEqual(table.mapping, {
- "c_t": ["c", "t"],
- "f_f_i": ["f", "f", "i"]
- })
+ self.assertEqual(table.mapping, {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]})
def test_postRead_formatUnknown(self):
table = otTables.MultipleSubst()
@@ -129,60 +124,63 @@ class MultipleSubstTest(unittest.TestCase):
table = otTables.MultipleSubst()
table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]}
table.toXML2(writer, self.font)
- self.assertEqual(writer.file.getvalue().splitlines()[1:], [
- '<Substitution in="c_t" out="c,t"/>',
- '<Substitution in="f_f_i" out="f,f,i"/>',
- ])
+ self.assertEqual(
+ writer.file.getvalue().splitlines()[1:],
+ [
+ '<Substitution in="c_t" out="c,t"/>',
+ '<Substitution in="f_f_i" out="f,f,i"/>',
+ ],
+ )
def test_fromXML(self):
table = otTables.MultipleSubst()
for name, attrs, content in parseXML(
- '<Substitution in="c_t" out="c,t"/>'
- '<Substitution in="f_f_i" out="f,f,i"/>'):
+ '<Substitution in="c_t" out="c,t"/>'
+ '<Substitution in="f_f_i" out="f,f,i"/>'
+ ):
table.fromXML(name, attrs, content, self.font)
- self.assertEqual(table.mapping,
- {'c_t': ['c', 't'], 'f_f_i': ['f', 'f', 'i']})
+ self.assertEqual(table.mapping, {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]})
def test_fromXML_oldFormat(self):
table = otTables.MultipleSubst()
for name, attrs, content in parseXML(
- '<Coverage>'
- ' <Glyph value="c_t"/>'
- ' <Glyph value="f_f_i"/>'
- '</Coverage>'
- '<Sequence index="0">'
- ' <Substitute index="0" value="c"/>'
- ' <Substitute index="1" value="t"/>'
- '</Sequence>'
- '<Sequence index="1">'
- ' <Substitute index="0" value="f"/>'
- ' <Substitute index="1" value="f"/>'
- ' <Substitute index="2" value="i"/>'
- '</Sequence>'):
+ "<Coverage>"
+ ' <Glyph value="c_t"/>'
+ ' <Glyph value="f_f_i"/>'
+ "</Coverage>"
+ '<Sequence index="0">'
+ ' <Substitute index="0" value="c"/>'
+ ' <Substitute index="1" value="t"/>'
+ "</Sequence>"
+ '<Sequence index="1">'
+ ' <Substitute index="0" value="f"/>'
+ ' <Substitute index="1" value="f"/>'
+ ' <Substitute index="2" value="i"/>'
+ "</Sequence>"
+ ):
table.fromXML(name, attrs, content, self.font)
- self.assertEqual(table.mapping,
- {'c_t': ['c', 't'], 'f_f_i': ['f', 'f', 'i']})
+ self.assertEqual(table.mapping, {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]})
def test_fromXML_oldFormat_bug385(self):
# https://github.com/fonttools/fonttools/issues/385
table = otTables.MultipleSubst()
table.Format = 1
for name, attrs, content in parseXML(
- '<Coverage>'
- ' <Glyph value="o"/>'
- ' <Glyph value="l"/>'
- '</Coverage>'
- '<Sequence>'
- ' <Substitute value="o"/>'
- ' <Substitute value="l"/>'
- ' <Substitute value="o"/>'
- '</Sequence>'
- '<Sequence>'
- ' <Substitute value="o"/>'
- '</Sequence>'):
+ "<Coverage>"
+ ' <Glyph value="o"/>'
+ ' <Glyph value="l"/>'
+ "</Coverage>"
+ "<Sequence>"
+ ' <Substitute value="o"/>'
+ ' <Substitute value="l"/>'
+ ' <Substitute value="o"/>'
+ "</Sequence>"
+ "<Sequence>"
+ ' <Substitute value="o"/>'
+ "</Sequence>"
+ ):
table.fromXML(name, attrs, content, self.font)
- self.assertEqual(table.mapping,
- {'o': ['o', 'l', 'o'], 'l': ['o']})
+ self.assertEqual(table.mapping, {"o": ["o", "l", "o"], "l": ["o"]})
class LigatureSubstTest(unittest.TestCase):
@@ -210,7 +208,7 @@ class LigatureSubstTest(unittest.TestCase):
ligs_f.Ligature = self.makeLigatures("ffi ff fi")
rawTable = {
"Coverage": makeCoverage(["c", "f"]),
- "LigatureSet": [ligs_c, ligs_f]
+ "LigatureSet": [ligs_c, ligs_f],
}
table.postRead(rawTable, self.font)
self.assertEqual(set(table.ligatures.keys()), {"c", "f"})
@@ -235,7 +233,7 @@ class LigatureSubstTest(unittest.TestCase):
table = otTables.LigatureSubst()
table.ligatures = {
"c": self.makeLigatures("ct"),
- "f": self.makeLigatures("ffi ff fi")
+ "f": self.makeLigatures("ffi ff fi"),
}
rawTable = table.preWrite(self.font)
self.assertEqual(table.Format, 1)
@@ -263,27 +261,31 @@ class LigatureSubstTest(unittest.TestCase):
table = otTables.LigatureSubst()
table.ligatures = {
"c": self.makeLigatures("ct"),
- "f": self.makeLigatures("ffi ff fi")
+ "f": self.makeLigatures("ffi ff fi"),
}
table.toXML2(writer, self.font)
- self.assertEqual(writer.file.getvalue().splitlines()[1:], [
- '<LigatureSet glyph="c">',
- ' <Ligature components="c,t" glyph="c_t"/>',
- '</LigatureSet>',
- '<LigatureSet glyph="f">',
- ' <Ligature components="f,f,i" glyph="f_f_i"/>',
- ' <Ligature components="f,f" glyph="f_f"/>',
- ' <Ligature components="f,i" glyph="f_i"/>',
- '</LigatureSet>'
- ])
+ self.assertEqual(
+ writer.file.getvalue().splitlines()[1:],
+ [
+ '<LigatureSet glyph="c">',
+ ' <Ligature components="c,t" glyph="c_t"/>',
+ "</LigatureSet>",
+ '<LigatureSet glyph="f">',
+ ' <Ligature components="f,f,i" glyph="f_f_i"/>',
+ ' <Ligature components="f,f" glyph="f_f"/>',
+ ' <Ligature components="f,i" glyph="f_i"/>',
+ "</LigatureSet>",
+ ],
+ )
def test_fromXML(self):
table = otTables.LigatureSubst()
for name, attrs, content in parseXML(
- '<LigatureSet glyph="f">'
- ' <Ligature components="f,f,i" glyph="f_f_i"/>'
- ' <Ligature components="f,f" glyph="f_f"/>'
- '</LigatureSet>'):
+ '<LigatureSet glyph="f">'
+ ' <Ligature components="f,f,i" glyph="f_f_i"/>'
+ ' <Ligature components="f,f" glyph="f_f"/>'
+ "</LigatureSet>"
+ ):
table.fromXML(name, attrs, content, self.font)
self.assertEqual(set(table.ligatures.keys()), {"f"})
[ffi, ff] = table.ligatures["f"]
@@ -310,14 +312,11 @@ class AlternateSubstTest(unittest.TestCase):
"Coverage": makeCoverage(["G", "Z"]),
"AlternateSet": [
self.makeAlternateSet("G.alt2 G.alt1"),
- self.makeAlternateSet("Z.fina")
- ]
+ self.makeAlternateSet("Z.fina"),
+ ],
}
table.postRead(rawTable, self.font)
- self.assertEqual(table.alternates, {
- "G": ["G.alt2", "G.alt1"],
- "Z": ["Z.fina"]
- })
+ self.assertEqual(table.alternates, {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]})
def test_postRead_formatUnknown(self):
table = otTables.AlternateSubst()
@@ -341,36 +340,37 @@ class AlternateSubstTest(unittest.TestCase):
table = otTables.AlternateSubst()
table.alternates = {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]}
table.toXML2(writer, self.font)
- self.assertEqual(writer.file.getvalue().splitlines()[1:], [
- '<AlternateSet glyph="G">',
- ' <Alternate glyph="G.alt2"/>',
- ' <Alternate glyph="G.alt1"/>',
- '</AlternateSet>',
- '<AlternateSet glyph="Z">',
- ' <Alternate glyph="Z.fina"/>',
- '</AlternateSet>'
- ])
+ self.assertEqual(
+ writer.file.getvalue().splitlines()[1:],
+ [
+ '<AlternateSet glyph="G">',
+ ' <Alternate glyph="G.alt2"/>',
+ ' <Alternate glyph="G.alt1"/>',
+ "</AlternateSet>",
+ '<AlternateSet glyph="Z">',
+ ' <Alternate glyph="Z.fina"/>',
+ "</AlternateSet>",
+ ],
+ )
def test_fromXML(self):
table = otTables.AlternateSubst()
for name, attrs, content in parseXML(
- '<AlternateSet glyph="G">'
- ' <Alternate glyph="G.alt2"/>'
- ' <Alternate glyph="G.alt1"/>'
- '</AlternateSet>'
- '<AlternateSet glyph="Z">'
- ' <Alternate glyph="Z.fina"/>'
- '</AlternateSet>'):
+ '<AlternateSet glyph="G">'
+ ' <Alternate glyph="G.alt2"/>'
+ ' <Alternate glyph="G.alt1"/>'
+ "</AlternateSet>"
+ '<AlternateSet glyph="Z">'
+ ' <Alternate glyph="Z.fina"/>'
+ "</AlternateSet>"
+ ):
table.fromXML(name, attrs, content, self.font)
- self.assertEqual(table.alternates, {
- "G": ["G.alt2", "G.alt1"],
- "Z": ["Z.fina"]
- })
+ self.assertEqual(table.alternates, {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]})
class RearrangementMorphActionTest(unittest.TestCase):
def setUp(self):
- self.font = FakeFont(['.notdef', 'A', 'B', 'C'])
+ self.font = FakeFont([".notdef", "A", "B", "C"])
def testCompile(self):
r = otTables.RearrangementMorphAction()
@@ -387,22 +387,24 @@ class RearrangementMorphActionTest(unittest.TestCase):
def testDecompileToXML(self):
r = otTables.RearrangementMorphAction()
- r.decompile(OTTableReader(deHexStr("1234fffd")),
- self.font, actionReader=None)
+ r.decompile(OTTableReader(deHexStr("1234fffd")), self.font, actionReader=None)
toXML = lambda w, f: r.toXML(w, f, {"Test": "Foo"}, "Transition")
- self.assertEqual(getXML(toXML, self.font), [
+ self.assertEqual(
+ getXML(toXML, self.font),
+ [
'<Transition Test="Foo">',
' <NewState value="4660"/>', # 0x1234 = 4660
' <Flags value="MarkFirst,DontAdvance,MarkLast"/>',
' <ReservedFlags value="0x1FF0"/>',
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
- '</Transition>',
- ])
+ "</Transition>",
+ ],
+ )
class ContextualMorphActionTest(unittest.TestCase):
def setUp(self):
- self.font = FakeFont(['.notdef', 'A', 'B', 'C'])
+ self.font = FakeFont([".notdef", "A", "B", "C"])
def testCompile(self):
a = otTables.ContextualMorphAction()
@@ -419,50 +421,55 @@ class ContextualMorphActionTest(unittest.TestCase):
def testDecompileToXML(self):
a = otTables.ContextualMorphAction()
- a.decompile(OTTableReader(deHexStr("1234f117deadbeef")),
- self.font, actionReader=None)
+ a.decompile(
+ OTTableReader(deHexStr("1234f117deadbeef")), self.font, actionReader=None
+ )
toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition")
- self.assertEqual(getXML(toXML, self.font), [
+ self.assertEqual(
+ getXML(toXML, self.font),
+ [
'<Transition Test="Foo">',
' <NewState value="4660"/>', # 0x1234 = 4660
' <Flags value="SetMark,DontAdvance"/>',
' <ReservedFlags value="0x3117"/>',
' <MarkIndex value="57005"/>', # 0xDEAD = 57005
' <CurrentIndex value="48879"/>', # 0xBEEF = 48879
- '</Transition>',
- ])
+ "</Transition>",
+ ],
+ )
class LigatureMorphActionTest(unittest.TestCase):
def setUp(self):
- self.font = FakeFont(['.notdef', 'A', 'B', 'C'])
+ self.font = FakeFont([".notdef", "A", "B", "C"])
def testDecompileToXML(self):
a = otTables.LigatureMorphAction()
actionReader = OTTableReader(deHexStr("DEADBEEF 7FFFFFFE 80000003"))
- a.decompile(OTTableReader(deHexStr("1234FAB30001")),
- self.font, actionReader)
+ a.decompile(OTTableReader(deHexStr("1234FAB30001")), self.font, actionReader)
toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition")
- self.assertEqual(getXML(toXML, self.font), [
+ self.assertEqual(
+ getXML(toXML, self.font),
+ [
'<Transition Test="Foo">',
' <NewState value="4660"/>', # 0x1234 = 4660
' <Flags value="SetComponent,DontAdvance"/>',
' <ReservedFlags value="0x1AB3"/>',
' <Action GlyphIndexDelta="-2" Flags="Store"/>',
' <Action GlyphIndexDelta="3"/>',
- '</Transition>',
- ])
+ "</Transition>",
+ ],
+ )
def testCompileActions_empty(self):
act = otTables.LigatureMorphAction()
actions, actionIndex = act.compileActions(self.font, [])
- self.assertEqual(actions, b'')
+ self.assertEqual(actions, b"")
self.assertEqual(actionIndex, {})
def testCompileActions_shouldShareSubsequences(self):
state = otTables.AATState()
- t = state.Transitions = {i: otTables.LigatureMorphAction()
- for i in range(3)}
+ t = state.Transitions = {i: otTables.LigatureMorphAction() for i in range(3)}
ligs = [otTables.LigAction() for _ in range(3)]
for i, lig in enumerate(ligs):
lig.GlyphIndexDelta = i
@@ -470,14 +477,16 @@ class LigatureMorphActionTest(unittest.TestCase):
t[1].Actions = ligs[0:3]
t[2].Actions = ligs[1:3]
actions, actionIndex = t[0].compileActions(self.font, [state])
- self.assertEqual(actions,
- deHexStr("00000000 00000001 80000002 80000001"))
- self.assertEqual(actionIndex, {
- deHexStr("00000000 00000001 80000002"): 0,
- deHexStr("00000001 80000002"): 1,
- deHexStr("80000002"): 2,
- deHexStr("80000001"): 3,
- })
+ self.assertEqual(actions, deHexStr("00000000 00000001 80000002 80000001"))
+ self.assertEqual(
+ actionIndex,
+ {
+ deHexStr("00000000 00000001 80000002"): 0,
+ deHexStr("00000001 80000002"): 1,
+ deHexStr("80000002"): 2,
+ deHexStr("80000001"): 3,
+ },
+ )
class InsertionMorphActionTest(unittest.TestCase):
@@ -485,25 +494,27 @@ class InsertionMorphActionTest(unittest.TestCase):
'<Transition Test="Foo">',
' <NewState value="4660"/>', # 0x1234 = 4660
' <Flags value="SetMark,DontAdvance,CurrentIsKashidaLike,'
- 'MarkedIsKashidaLike,CurrentInsertBefore,MarkedInsertBefore"/>',
+ 'MarkedIsKashidaLike,CurrentInsertBefore,MarkedInsertBefore"/>',
' <CurrentInsertionAction glyph="B"/>',
' <CurrentInsertionAction glyph="C"/>',
' <MarkedInsertionAction glyph="B"/>',
' <MarkedInsertionAction glyph="A"/>',
' <MarkedInsertionAction glyph="D"/>',
- '</Transition>'
+ "</Transition>",
]
def setUp(self):
- self.font = FakeFont(['.notdef', 'A', 'B', 'C', 'D'])
+ self.font = FakeFont([".notdef", "A", "B", "C", "D"])
self.maxDiff = None
def testDecompileToXML(self):
a = otTables.InsertionMorphAction()
actionReader = OTTableReader(
- deHexStr("DEAD BEEF 0002 0001 0004 0002 0003 DEAD BEEF"))
- a.decompile(OTTableReader(deHexStr("1234 FC43 0005 0002")),
- self.font, actionReader)
+ deHexStr("DEAD BEEF 0002 0001 0004 0002 0003 DEAD BEEF")
+ )
+ a.decompile(
+ OTTableReader(deHexStr("1234 FC43 0005 0002")), self.font, actionReader
+ )
toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition")
self.assertEqual(getXML(toXML, self.font), self.MORPH_ACTION_XML)
@@ -515,37 +526,39 @@ class InsertionMorphActionTest(unittest.TestCase):
a.compile(
writer,
self.font,
- actionIndex={('B', 'C'): 9, ('B', 'A', 'D'): 7},
+ actionIndex={("B", "C"): 9, ("B", "A", "D"): 7},
)
self.assertEqual(hexStr(writer.getAllData()), "1234fc4300090007")
def testCompileActions_empty(self):
act = otTables.InsertionMorphAction()
actions, actionIndex = act.compileActions(self.font, [])
- self.assertEqual(actions, b'')
+ self.assertEqual(actions, b"")
self.assertEqual(actionIndex, {})
def testCompileActions_shouldShareSubsequences(self):
state = otTables.AATState()
- t = state.Transitions = {i: otTables.InsertionMorphAction()
- for i in range(3)}
+ t = state.Transitions = {i: otTables.InsertionMorphAction() for i in range(3)}
t[1].CurrentInsertionAction = []
- t[0].MarkedInsertionAction = ['A']
- t[1].CurrentInsertionAction = ['C', 'D']
- t[1].MarkedInsertionAction = ['B']
- t[2].CurrentInsertionAction = ['B', 'C', 'D']
- t[2].MarkedInsertionAction = ['C', 'D']
+ t[0].MarkedInsertionAction = ["A"]
+ t[1].CurrentInsertionAction = ["C", "D"]
+ t[1].MarkedInsertionAction = ["B"]
+ t[2].CurrentInsertionAction = ["B", "C", "D"]
+ t[2].MarkedInsertionAction = ["C", "D"]
actions, actionIndex = t[0].compileActions(self.font, [state])
- self.assertEqual(actions, deHexStr('0002 0003 0004 0001'))
- self.assertEqual(actionIndex, {
- ('A',): 3,
- ('B',): 0,
- ('B', 'C'): 0,
- ('B', 'C', 'D'): 0,
- ('C',): 1,
- ('C', 'D'): 1,
- ('D',): 2,
- })
+ self.assertEqual(actions, deHexStr("0002 0003 0004 0001"))
+ self.assertEqual(
+ actionIndex,
+ {
+ ("A",): 3,
+ ("B",): 0,
+ ("B", "C"): 0,
+ ("B", "C", "D"): 0,
+ ("C",): 1,
+ ("C", "D"): 1,
+ ("D",): 2,
+ },
+ )
class SplitMultipleSubstTest:
@@ -553,28 +566,34 @@ class SplitMultipleSubstTest:
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 = buildMultipleSubstSubtable(
+ {"e": 1, "a": 2, "b": 3, "c": 4, "d": 5}
+ )
newSubTable = otTables.MultipleSubst()
- ok = otTables.splitMultipleSubst(oldSubTable, newSubTable, OverflowErrorRecord((None, None, None, itemName, itemRecord)))
+ ok = otTables.splitMultipleSubst(
+ oldSubTable,
+ newSubTable,
+ OverflowErrorRecord((None, None, None, itemName, itemRecord)),
+ )
assert ok
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}
+ 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}
+ 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}
+ oldMapping, newMapping = self.overflow("Sequence", 4)
+ assert oldMapping == {"a": 2, "b": 3, "c": 4}
+ assert newMapping == {"d": 5, "e": 1}
def test_splitMarkBasePos():
@@ -607,95 +626,95 @@ def test_splitMarkBasePos():
assert getXML(oldSubTable.toXML) == [
'<MarkBasePos Format="1">',
- ' <MarkCoverage>',
+ " <MarkCoverage>",
' <Glyph value="acutecomb"/>',
' <Glyph value="gravecomb"/>',
- ' </MarkCoverage>',
- ' <BaseCoverage>',
+ " </MarkCoverage>",
+ " <BaseCoverage>",
' <Glyph value="a"/>',
' <Glyph value="c"/>',
- ' </BaseCoverage>',
- ' <!-- ClassCount=1 -->',
- ' <MarkArray>',
- ' <!-- MarkCount=2 -->',
+ " </BaseCoverage>",
+ " <!-- ClassCount=1 -->",
+ " <MarkArray>",
+ " <!-- MarkCount=2 -->",
' <MarkRecord index="0">',
' <Class value="0"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="0"/>',
' <YCoordinate value="600"/>',
- ' </MarkAnchor>',
- ' </MarkRecord>',
+ " </MarkAnchor>",
+ " </MarkRecord>",
' <MarkRecord index="1">',
' <Class value="0"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="0"/>',
' <YCoordinate value="590"/>',
- ' </MarkAnchor>',
- ' </MarkRecord>',
- ' </MarkArray>',
- ' <BaseArray>',
- ' <!-- BaseCount=2 -->',
+ " </MarkAnchor>",
+ " </MarkRecord>",
+ " </MarkArray>",
+ " <BaseArray>",
+ " <!-- BaseCount=2 -->",
' <BaseRecord index="0">',
' <BaseAnchor index="0" Format="1">',
' <XCoordinate value="350"/>',
' <YCoordinate value="500"/>',
- ' </BaseAnchor>',
- ' </BaseRecord>',
+ " </BaseAnchor>",
+ " </BaseRecord>",
' <BaseRecord index="1">',
' <BaseAnchor index="0" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="700"/>',
- ' </BaseAnchor>',
- ' </BaseRecord>',
- ' </BaseArray>',
- '</MarkBasePos>',
+ " </BaseAnchor>",
+ " </BaseRecord>",
+ " </BaseArray>",
+ "</MarkBasePos>",
]
assert getXML(newSubTable.toXML) == [
'<MarkBasePos Format="1">',
- ' <MarkCoverage>',
+ " <MarkCoverage>",
' <Glyph value="cedillacomb"/>',
- ' </MarkCoverage>',
- ' <BaseCoverage>',
+ " </MarkCoverage>",
+ " <BaseCoverage>",
' <Glyph value="a"/>',
' <Glyph value="c"/>',
- ' </BaseCoverage>',
- ' <!-- ClassCount=1 -->',
- ' <MarkArray>',
- ' <!-- MarkCount=1 -->',
+ " </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 -->',
+ " </MarkAnchor>",
+ " </MarkRecord>",
+ " </MarkArray>",
+ " <BaseArray>",
+ " <!-- BaseCount=2 -->",
' <BaseRecord index="0">',
' <BaseAnchor index="0" empty="1"/>',
- ' </BaseRecord>',
+ " </BaseRecord>",
' <BaseRecord index="1">',
' <BaseAnchor index="0" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="0"/>',
- ' </BaseAnchor>',
- ' </BaseRecord>',
- ' </BaseArray>',
- '</MarkBasePos>',
+ " </BaseAnchor>",
+ " </BaseRecord>",
+ " </BaseArray>",
+ "</MarkBasePos>",
]
class ColrV1Test(unittest.TestCase):
- def setUp(self):
- self.font = FakeFont(['.notdef', 'meh'])
-
- def test_traverseEmptyPaintColrLayersNeedsNoLayerList(self):
- colr = parseXmlInto(
- self.font,
- otTables.COLR(),
- '''
+ def setUp(self):
+ self.font = FakeFont([".notdef", "meh"])
+
+ def test_traverseEmptyPaintColrLayersNeedsNoLayerList(self):
+ colr = parseXmlInto(
+ self.font,
+ otTables.COLR(),
+ """
<Version value="1"/>
<BaseGlyphList>
<BaseGlyphPaintRecord index="0">
@@ -706,16 +725,17 @@ class ColrV1Test(unittest.TestCase):
</Paint>
</BaseGlyphPaintRecord>
</BaseGlyphList>
- ''',
- )
- paint = colr.BaseGlyphList.BaseGlyphPaintRecord[0].Paint
+ """,
+ )
+ paint = colr.BaseGlyphList.BaseGlyphPaintRecord[0].Paint
- # Just want to confirm we don't crash
- visited = []
- paint.traverse(colr, lambda p: visited.append(p))
- assert len(visited) == 1
+ # Just want to confirm we don't crash
+ visited = []
+ paint.traverse(colr, lambda p: visited.append(p))
+ assert len(visited) == 1
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/tables/tables_test.py b/Tests/ttLib/tables/tables_test.py
index be8c63e3..816385b6 100644
--- a/Tests/ttLib/tables/tables_test.py
+++ b/Tests/ttLib/tables/tables_test.py
@@ -14,225 +14,234 @@ except ImportError:
else:
# on 3.6 the built-in unicodedata is the same as unicodedata2 backport
import unicodedata
+
unicodedata2 = unicodedata
# Font files in data/*.{o,t}tf; output gets compared to data/*.ttx.*
TESTS = {
- "aots/base.otf": ('CFF ', 'cmap', 'head',
- 'hhea', 'hmtx', 'maxp',
- 'name', 'OS/2', 'post'),
- "aots/classdef1_font1.otf": ('GSUB',),
- "aots/classdef1_font2.otf": ('GSUB',),
- "aots/classdef1_font3.otf": ('GSUB',),
- "aots/classdef1_font4.otf": ('GSUB',),
- "aots/classdef2_font1.otf": ('GSUB',),
- "aots/classdef2_font2.otf": ('GSUB',),
- "aots/classdef2_font3.otf": ('GSUB',),
- "aots/classdef2_font4.otf": ('GSUB',),
- "aots/cmap0_font1.otf": ('cmap',),
- "aots/cmap10_font1.otf": ('cmap',),
- "aots/cmap10_font2.otf": ('cmap',),
- "aots/cmap12_font1.otf": ('cmap',),
- "aots/cmap14_font1.otf": ('cmap',),
- "aots/cmap2_font1.otf": ('cmap',),
- "aots/cmap4_font1.otf": ('cmap',),
- "aots/cmap4_font2.otf": ('cmap',),
- "aots/cmap4_font3.otf": ('cmap',),
- "aots/cmap4_font4.otf": ('cmap',),
- "aots/cmap6_font1.otf": ('cmap',),
- "aots/cmap6_font2.otf": ('cmap',),
- "aots/cmap8_font1.otf": ('cmap',),
- "aots/cmap_composition_font1.otf": ('cmap',),
- "aots/cmap_subtableselection_font1.otf": ('cmap',),
- "aots/cmap_subtableselection_font2.otf": ('cmap',),
- "aots/cmap_subtableselection_font3.otf": ('cmap',),
- "aots/cmap_subtableselection_font4.otf": ('cmap',),
- "aots/cmap_subtableselection_font5.otf": ('cmap',),
- "aots/gpos1_1_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos1_1_simple_f1.otf": ('GPOS',),
- "aots/gpos1_1_simple_f2.otf": ('GPOS',),
- "aots/gpos1_1_simple_f3.otf": ('GPOS',),
- "aots/gpos1_1_simple_f4.otf": ('GPOS',),
- "aots/gpos1_2_font1.otf": ('GPOS',),
- "aots/gpos1_2_font2.otf": ('GDEF', 'GPOS'),
- "aots/gpos2_1_font6.otf": ('GPOS',),
- "aots/gpos2_1_font7.otf": ('GPOS',),
- "aots/gpos2_1_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos2_1_lookupflag_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos2_1_next_glyph_f1.otf": ('GPOS',),
- "aots/gpos2_1_next_glyph_f2.otf": ('GPOS',),
- "aots/gpos2_1_simple_f1.otf": ('GPOS',),
- "aots/gpos2_2_font1.otf": ('GPOS',),
- "aots/gpos2_2_font2.otf": ('GDEF', 'GPOS'),
- "aots/gpos2_2_font3.otf": ('GDEF', 'GPOS'),
- "aots/gpos2_2_font4.otf": ('GPOS',),
- "aots/gpos2_2_font5.otf": ('GPOS',),
- "aots/gpos3_font1.otf": ('GPOS',),
- "aots/gpos3_font2.otf": ('GDEF', 'GPOS'),
- "aots/gpos3_font3.otf": ('GDEF', 'GPOS'),
- "aots/gpos4_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos4_lookupflag_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos4_multiple_anchors_1.otf": ('GDEF', 'GPOS'),
- "aots/gpos4_simple_1.otf": ('GDEF', 'GPOS'),
- "aots/gpos5_font1.otf": ('GDEF', 'GPOS', 'GSUB'),
- "aots/gpos6_font1.otf": ('GDEF', 'GPOS'),
- "aots/gpos7_1_font1.otf": ('GPOS',),
- "aots/gpos9_font1.otf": ('GPOS',),
- "aots/gpos9_font2.otf": ('GPOS',),
- "aots/gpos_chaining1_boundary_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_boundary_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_boundary_f3.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_boundary_f4.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_multiple_subrules_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_multiple_subrules_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_next_glyph_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_simple_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_simple_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining1_successive_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_boundary_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_boundary_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_boundary_f3.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_boundary_f4.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_multiple_subrules_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_multiple_subrules_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_next_glyph_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_simple_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_simple_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining2_successive_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_boundary_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_boundary_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_boundary_f3.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_boundary_f4.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_next_glyph_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_simple_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_simple_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_chaining3_successive_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_boundary_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_boundary_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_expansion_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_lookupflag_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_multiple_subrules_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_multiple_subrules_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_next_glyph_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_simple_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_simple_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context1_successive_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_boundary_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_boundary_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_classes_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_classes_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_expansion_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_lookupflag_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_multiple_subrules_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_multiple_subrules_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_next_glyph_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_simple_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_simple_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context2_successive_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context3_boundary_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context3_boundary_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context3_lookupflag_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context3_lookupflag_f2.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context3_next_glyph_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context3_simple_f1.otf": ('GDEF', 'GPOS'),
- "aots/gpos_context3_successive_f1.otf": ('GDEF', 'GPOS'),
- "aots/gsub1_1_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub1_1_modulo_f1.otf": ('GSUB',),
- "aots/gsub1_1_simple_f1.otf": ('GSUB',),
- "aots/gsub1_2_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub1_2_simple_f1.otf": ('GSUB',),
- "aots/gsub2_1_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub2_1_multiple_sequences_f1.otf": ('GSUB',),
- "aots/gsub2_1_simple_f1.otf": ('GSUB',),
- "aots/gsub3_1_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub3_1_multiple_f1.otf": ('GSUB',),
- "aots/gsub3_1_simple_f1.otf": ('GSUB',),
- "aots/gsub4_1_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub4_1_multiple_ligatures_f1.otf": ('GSUB',),
- "aots/gsub4_1_multiple_ligatures_f2.otf": ('GSUB',),
- "aots/gsub4_1_multiple_ligsets_f1.otf": ('GSUB',),
- "aots/gsub4_1_simple_f1.otf": ('GSUB',),
- "aots/gsub7_font1.otf": ('GSUB',),
- "aots/gsub7_font2.otf": ('GSUB',),
- "aots/gsub_chaining1_boundary_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_boundary_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_boundary_f3.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_boundary_f4.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_multiple_subrules_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_multiple_subrules_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_next_glyph_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_simple_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_simple_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining1_successive_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_boundary_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_boundary_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_boundary_f3.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_boundary_f4.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_multiple_subrules_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_multiple_subrules_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_next_glyph_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_simple_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_simple_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining2_successive_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_boundary_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_boundary_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_boundary_f3.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_boundary_f4.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_next_glyph_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_simple_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_simple_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_chaining3_successive_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_boundary_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_boundary_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_expansion_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_lookupflag_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_multiple_subrules_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_multiple_subrules_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_next_glyph_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_simple_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_simple_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context1_successive_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_boundary_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_boundary_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_classes_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_classes_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_expansion_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_lookupflag_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_multiple_subrules_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_multiple_subrules_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_next_glyph_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_simple_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_simple_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context2_successive_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context3_boundary_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context3_boundary_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context3_lookupflag_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context3_lookupflag_f2.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context3_next_glyph_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context3_simple_f1.otf": ('GDEF', 'GSUB'),
- "aots/gsub_context3_successive_f1.otf": ('GDEF', 'GSUB'),
- "aots/lookupflag_ignore_attach_f1.otf": ('GDEF', 'GSUB'),
- "aots/lookupflag_ignore_base_f1.otf": ('GDEF', 'GSUB'),
- "aots/lookupflag_ignore_combination_f1.otf": ('GDEF', 'GSUB'),
- "aots/lookupflag_ignore_ligatures_f1.otf": ('GDEF', 'GSUB'),
- "aots/lookupflag_ignore_marks_f1.otf": ('GDEF', 'GSUB'),
- "graphite/graphite_tests.ttf": ('Silf', 'Glat', 'Feat', 'Sill'),
+ "aots/base.otf": (
+ "CFF ",
+ "cmap",
+ "head",
+ "hhea",
+ "hmtx",
+ "maxp",
+ "name",
+ "OS/2",
+ "post",
+ ),
+ "aots/classdef1_font1.otf": ("GSUB",),
+ "aots/classdef1_font2.otf": ("GSUB",),
+ "aots/classdef1_font3.otf": ("GSUB",),
+ "aots/classdef1_font4.otf": ("GSUB",),
+ "aots/classdef2_font1.otf": ("GSUB",),
+ "aots/classdef2_font2.otf": ("GSUB",),
+ "aots/classdef2_font3.otf": ("GSUB",),
+ "aots/classdef2_font4.otf": ("GSUB",),
+ "aots/cmap0_font1.otf": ("cmap",),
+ "aots/cmap10_font1.otf": ("cmap",),
+ "aots/cmap10_font2.otf": ("cmap",),
+ "aots/cmap12_font1.otf": ("cmap",),
+ "aots/cmap14_font1.otf": ("cmap",),
+ "aots/cmap2_font1.otf": ("cmap",),
+ "aots/cmap4_font1.otf": ("cmap",),
+ "aots/cmap4_font2.otf": ("cmap",),
+ "aots/cmap4_font3.otf": ("cmap",),
+ "aots/cmap4_font4.otf": ("cmap",),
+ "aots/cmap6_font1.otf": ("cmap",),
+ "aots/cmap6_font2.otf": ("cmap",),
+ "aots/cmap8_font1.otf": ("cmap",),
+ "aots/cmap_composition_font1.otf": ("cmap",),
+ "aots/cmap_subtableselection_font1.otf": ("cmap",),
+ "aots/cmap_subtableselection_font2.otf": ("cmap",),
+ "aots/cmap_subtableselection_font3.otf": ("cmap",),
+ "aots/cmap_subtableselection_font4.otf": ("cmap",),
+ "aots/cmap_subtableselection_font5.otf": ("cmap",),
+ "aots/gpos1_1_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos1_1_simple_f1.otf": ("GPOS",),
+ "aots/gpos1_1_simple_f2.otf": ("GPOS",),
+ "aots/gpos1_1_simple_f3.otf": ("GPOS",),
+ "aots/gpos1_1_simple_f4.otf": ("GPOS",),
+ "aots/gpos1_2_font1.otf": ("GPOS",),
+ "aots/gpos1_2_font2.otf": ("GDEF", "GPOS"),
+ "aots/gpos2_1_font6.otf": ("GPOS",),
+ "aots/gpos2_1_font7.otf": ("GPOS",),
+ "aots/gpos2_1_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos2_1_lookupflag_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos2_1_next_glyph_f1.otf": ("GPOS",),
+ "aots/gpos2_1_next_glyph_f2.otf": ("GPOS",),
+ "aots/gpos2_1_simple_f1.otf": ("GPOS",),
+ "aots/gpos2_2_font1.otf": ("GPOS",),
+ "aots/gpos2_2_font2.otf": ("GDEF", "GPOS"),
+ "aots/gpos2_2_font3.otf": ("GDEF", "GPOS"),
+ "aots/gpos2_2_font4.otf": ("GPOS",),
+ "aots/gpos2_2_font5.otf": ("GPOS",),
+ "aots/gpos3_font1.otf": ("GPOS",),
+ "aots/gpos3_font2.otf": ("GDEF", "GPOS"),
+ "aots/gpos3_font3.otf": ("GDEF", "GPOS"),
+ "aots/gpos4_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos4_lookupflag_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos4_multiple_anchors_1.otf": ("GDEF", "GPOS"),
+ "aots/gpos4_simple_1.otf": ("GDEF", "GPOS"),
+ "aots/gpos5_font1.otf": ("GDEF", "GPOS", "GSUB"),
+ "aots/gpos6_font1.otf": ("GDEF", "GPOS"),
+ "aots/gpos7_1_font1.otf": ("GPOS",),
+ "aots/gpos9_font1.otf": ("GPOS",),
+ "aots/gpos9_font2.otf": ("GPOS",),
+ "aots/gpos_chaining1_boundary_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_boundary_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_boundary_f3.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_boundary_f4.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_next_glyph_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_simple_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_simple_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining1_successive_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_boundary_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_boundary_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_boundary_f3.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_boundary_f4.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_next_glyph_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_simple_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_simple_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining2_successive_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_boundary_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_boundary_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_boundary_f3.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_boundary_f4.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_next_glyph_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_simple_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_simple_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_chaining3_successive_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_boundary_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_boundary_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_expansion_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_lookupflag_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_next_glyph_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_simple_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_simple_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context1_successive_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_boundary_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_boundary_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_classes_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_classes_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_expansion_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_lookupflag_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_multiple_subrules_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_multiple_subrules_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_next_glyph_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_simple_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_simple_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context2_successive_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context3_boundary_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context3_boundary_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context3_lookupflag_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context3_lookupflag_f2.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context3_next_glyph_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context3_simple_f1.otf": ("GDEF", "GPOS"),
+ "aots/gpos_context3_successive_f1.otf": ("GDEF", "GPOS"),
+ "aots/gsub1_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub1_1_modulo_f1.otf": ("GSUB",),
+ "aots/gsub1_1_simple_f1.otf": ("GSUB",),
+ "aots/gsub1_2_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub1_2_simple_f1.otf": ("GSUB",),
+ "aots/gsub2_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub2_1_multiple_sequences_f1.otf": ("GSUB",),
+ "aots/gsub2_1_simple_f1.otf": ("GSUB",),
+ "aots/gsub3_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub3_1_multiple_f1.otf": ("GSUB",),
+ "aots/gsub3_1_simple_f1.otf": ("GSUB",),
+ "aots/gsub4_1_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub4_1_multiple_ligatures_f1.otf": ("GSUB",),
+ "aots/gsub4_1_multiple_ligatures_f2.otf": ("GSUB",),
+ "aots/gsub4_1_multiple_ligsets_f1.otf": ("GSUB",),
+ "aots/gsub4_1_simple_f1.otf": ("GSUB",),
+ "aots/gsub7_font1.otf": ("GSUB",),
+ "aots/gsub7_font2.otf": ("GSUB",),
+ "aots/gsub_chaining1_boundary_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_boundary_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_boundary_f3.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_boundary_f4.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_next_glyph_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_simple_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_simple_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining1_successive_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_boundary_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_boundary_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_boundary_f3.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_boundary_f4.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_next_glyph_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_simple_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_simple_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining2_successive_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_boundary_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_boundary_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_boundary_f3.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_boundary_f4.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_next_glyph_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_simple_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_simple_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_chaining3_successive_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_boundary_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_boundary_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_expansion_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_lookupflag_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_next_glyph_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_simple_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_simple_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context1_successive_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_boundary_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_boundary_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_classes_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_classes_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_expansion_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_lookupflag_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_multiple_subrules_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_multiple_subrules_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_next_glyph_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_simple_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_simple_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context2_successive_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context3_boundary_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context3_boundary_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context3_lookupflag_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context3_lookupflag_f2.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context3_next_glyph_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context3_simple_f1.otf": ("GDEF", "GSUB"),
+ "aots/gsub_context3_successive_f1.otf": ("GDEF", "GSUB"),
+ "aots/lookupflag_ignore_attach_f1.otf": ("GDEF", "GSUB"),
+ "aots/lookupflag_ignore_base_f1.otf": ("GDEF", "GSUB"),
+ "aots/lookupflag_ignore_combination_f1.otf": ("GDEF", "GSUB"),
+ "aots/lookupflag_ignore_ligatures_f1.otf": ("GDEF", "GSUB"),
+ "aots/lookupflag_ignore_marks_f1.otf": ("GDEF", "GSUB"),
+ "graphite/graphite_tests.ttf": ("Silf", "Glat", "Feat", "Sill"),
}
TEST_REQUIREMENTS = {
- "aots/cmap4_font4.otf": ("unicodedata2",),
+ "aots/cmap4_font4.otf": ("unicodedata2",),
}
@@ -247,15 +256,15 @@ def getpath(testfile):
def read_expected_ttx(testfile, tableTag):
name = os.path.splitext(testfile)[0]
xml_expected_path = getpath("%s.ttx.%s" % (name, tagToXML(tableTag)))
- with open(xml_expected_path, 'r', encoding="utf-8") as xml_file:
- xml_expected = ttLibVersion_RE.sub('', xml_file.read())
+ with open(xml_expected_path, "r", encoding="utf-8") as xml_file:
+ xml_expected = ttLibVersion_RE.sub("", xml_file.read())
return xml_expected
def dump_ttx(font, tableTag):
f = StringIO()
font.saveXML(f, tables=[tableTag])
- return ttLibVersion_RE.sub('', f.getvalue())
+ return ttLibVersion_RE.sub("", f.getvalue())
def load_ttx(ttx):
@@ -280,7 +289,7 @@ def _skip_if_requirement_missing(testfile):
if testfile in TEST_REQUIREMENTS:
for req in TEST_REQUIREMENTS[testfile]:
if globals()[req] is None:
- pytest.skip('%s not installed' % req)
+ pytest.skip("%s not installed" % req)
def test_xml_from_binary(testfile, tableTag):
@@ -305,7 +314,7 @@ def test_xml_from_xml(testfile, tableTag):
name = os.path.splitext(testfile)[0]
setupfile = getpath("%s.ttx.%s.setup" % (name, tagToXML(tableTag)))
if os.path.exists(setupfile):
-# import pdb; pdb.set_trace()
+ # import pdb; pdb.set_trace()
font.importXML(setupfile)
xml_from_xml = dump_ttx(font, tableTag)
@@ -317,11 +326,13 @@ def pytest_generate_tests(metafunc):
fixturenames = metafunc.fixturenames
argnames = ("testfile", "tableTag")
if all(fn in fixturenames for fn in argnames):
- argvalues = [(testfile, tableTag)
- for testfile, tableTags in sorted(TESTS.items())
- for tableTag in tableTags]
+ argvalues = [
+ (testfile, tableTag)
+ for testfile, tableTags in sorted(TESTS.items())
+ for tableTag in tableTags
+ ]
metafunc.parametrize(argnames, argvalues)
-if __name__ == '__main__':
+if __name__ == "__main__":
sys.exit(pytest.main(sys.argv))
diff --git a/Tests/ttLib/tables/ttProgram_test.py b/Tests/ttLib/tables/ttProgram_test.py
index 13d1ba87..10a02958 100644
--- a/Tests/ttLib/tables/ttProgram_test.py
+++ b/Tests/ttLib/tables/ttProgram_test.py
@@ -7,62 +7,70 @@ import os
import unittest
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-DATA_DIR = os.path.join(CURR_DIR, 'data')
+DATA_DIR = os.path.join(CURR_DIR, "data")
TTPROGRAM_TTX = os.path.join(DATA_DIR, "ttProgram.ttx")
-#TTPROGRAM_BIN = os.path.join(DATA_DIR, "ttProgram.bin")
+# TTPROGRAM_BIN = os.path.join(DATA_DIR, "ttProgram.bin")
+
+ASSEMBLY = [
+ "PUSH[ ]",
+ "0 4 3",
+ "INSTCTRL[ ]",
+ "POP[ ]",
+]
BYTECODE = deHexStr(
- '403b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a'
- '191817161514131211100f0e0d0c0b0a090807060504030201002c01b0184358456ab0'
- '194360b0462344231020b0464ef04d2fb000121b21231133592d2c01b0184358b0052b'
- 'b000134bb0145058b100403859b0062b1b21231133592d2c01b01843584eb0032510f2'
- '21b000124d1b2045b00425b00425234a6164b0285258212310d61bb0032510f221b000'
- '1259592d2cb01a435821211bb00225b0022549b00325b003254a612064b01050582121'
- '211bb00325b0032549b0005058b0005058b8ffe238211bb0103821591bb0005258b01e'
- '38211bb8fff03821595959592d2c01b0184358b0052bb000134bb0145058b90000ffc0'
- '3859b0062b1b21231133592d2c4e018a10b146194344b00014b10046e2b00015b90000'
- 'fff03800b0003cb0282bb0022510b0003c2d2c0118b0002fb00114f2b00113b001154d'
- 'b000122d2c01b0184358b0052bb00013b90000ffe038b0062b1b21231133592d2c01b0'
- '18435845646a23456469b01943646060b0462344231020b046f02fb000121b2121208a'
- '208a525811331b212159592d2c01b10b0a432343650a2d2c00b10a0b4323430b2d2c00'
- 'b0462370b101463e01b0462370b10246453ab10200080d2d2cb0122bb0022545b00225'
- '456ab0408b60b0022523442121212d2cb0132bb0022545b00225456ab8ffc08c60b002'
- '2523442121212d2cb000b0122b2121212d2cb000b0132b2121212d2c01b00643b00743'
- '650a2d2c2069b04061b0008b20b12cc08a8cb8100062602b0c642364615c58b0036159'
- '2d2cb1000325456854b01c4b505a58b0032545b0032545606820b004252344b0042523'
- '441bb00325204568208a2344b00325456860b003252344592d2cb00325204568208a23'
- '44b003254564686560b00425b0016023442d2cb00943588721c01bb01243588745b011'
- '2bb0472344b0477ae41b038a45186920b04723448a8a8720b0a05158b0112bb0472344'
- 'b0477ae41b21b0477ae4595959182d2c208a4523456860442d2c456a422d2c01182f2d'
- '2c01b0184358b00425b00425496423456469b0408b6120b080626ab00225b00225618c'
- 'b0194360b0462344218a10b046f6211b21212121592d2c01b0184358b0022545b00225'
- '4564606ab00325456a6120b00425456a208a8b65b0042523448cb00325234421211b20'
- '456a4420456a44592d2c012045b00055b018435a584568234569b0408b6120b080626a'
- '208a236120b003258b65b0042523448cb00325234421211b2121b0192b592d2c018a8a'
- '45642345646164422d2cb00425b00425b0192bb0184358b00425b00425b00325b01b2b'
- '01b0022543b04054b0022543b000545a58b003252045b040614459b0022543b00054b0'
- '022543b040545a58b004252045b04060445959212121212d2c014b525843b002254523'
- '61441b2121592d2c014b525843b00225452360441b2121592d2c4b525845441b212159'
- '2d2c0120b003252349b04060b0206320b000525823b002253823b002256538008a6338'
- '1b212121212159012d2c4b505845441b2121592d2c01b005251023208af500b0016023'
- 'edec2d2c01b005251023208af500b0016123edec2d2c01b0062510f500edec2d2c4623'
- '46608a8a462320468a608a61b8ff8062232010238ab14b4b8a70456020b0005058b001'
- '61b8ffba8b1bb0468c59b0106068013a2d2c2045b00325465258b0022546206861b003'
- '25b003253f2321381b2111592d2c2045b00325465058b0022546206861b00325b00325'
- '3f2321381b2111592d2c00b00743b006430b2d2c8a10ec2d2cb00c4358211b2046b000'
- '5258b8fff0381bb0103859592d2c20b0005558b8100063b003254564b00325456461b0'
- '005358b0021bb04061b00359254569535845441b2121591b21b0022545b00225456164'
- 'b028515845441b212159592d2c21210c6423648bb84000622d2c21b08051580c642364'
- '8bb82000621bb200402f2b59b002602d2c21b0c051580c6423648bb81555621bb20080'
- '2f2b59b002602d2c0c6423648bb84000626023212d2c4b5358b00425b0042549642345'
- '6469b0408b6120b080626ab00225b00225618cb0462344218a10b046f6211b218a1123'
- '1220392f592d2cb00225b002254964b0c05458b8fff838b008381b2121592d2cb01343'
- '58031b02592d2cb0134358021b03592d2cb00a2b2310203cb0172b2d2cb00225b8fff0'
- '38b0282b8a102320d023b0102bb0054358c01b3c59201011b00012012d2c4b53234b51'
- '5a58381b2121592d2c01b0022510d023c901b00113b0001410b0013cb001162d2c01b0'
- '0013b001b0032549b0031738b001132d2c4b53234b515a5820458a60441b2121592d2c'
- '20392f2d')
+ "403b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a"
+ "191817161514131211100f0e0d0c0b0a090807060504030201002c01b0184358456ab0"
+ "194360b0462344231020b0464ef04d2fb000121b21231133592d2c01b0184358b0052b"
+ "b000134bb0145058b100403859b0062b1b21231133592d2c01b01843584eb0032510f2"
+ "21b000124d1b2045b00425b00425234a6164b0285258212310d61bb0032510f221b000"
+ "1259592d2cb01a435821211bb00225b0022549b00325b003254a612064b01050582121"
+ "211bb00325b0032549b0005058b0005058b8ffe238211bb0103821591bb0005258b01e"
+ "38211bb8fff03821595959592d2c01b0184358b0052bb000134bb0145058b90000ffc0"
+ "3859b0062b1b21231133592d2c4e018a10b146194344b00014b10046e2b00015b90000"
+ "fff03800b0003cb0282bb0022510b0003c2d2c0118b0002fb00114f2b00113b001154d"
+ "b000122d2c01b0184358b0052bb00013b90000ffe038b0062b1b21231133592d2c01b0"
+ "18435845646a23456469b01943646060b0462344231020b046f02fb000121b2121208a"
+ "208a525811331b212159592d2c01b10b0a432343650a2d2c00b10a0b4323430b2d2c00"
+ "b0462370b101463e01b0462370b10246453ab10200080d2d2cb0122bb0022545b00225"
+ "456ab0408b60b0022523442121212d2cb0132bb0022545b00225456ab8ffc08c60b002"
+ "2523442121212d2cb000b0122b2121212d2cb000b0132b2121212d2c01b00643b00743"
+ "650a2d2c2069b04061b0008b20b12cc08a8cb8100062602b0c642364615c58b0036159"
+ "2d2cb1000325456854b01c4b505a58b0032545b0032545606820b004252344b0042523"
+ "441bb00325204568208a2344b00325456860b003252344592d2cb00325204568208a23"
+ "44b003254564686560b00425b0016023442d2cb00943588721c01bb01243588745b011"
+ "2bb0472344b0477ae41b038a45186920b04723448a8a8720b0a05158b0112bb0472344"
+ "b0477ae41b21b0477ae4595959182d2c208a4523456860442d2c456a422d2c01182f2d"
+ "2c01b0184358b00425b00425496423456469b0408b6120b080626ab00225b00225618c"
+ "b0194360b0462344218a10b046f6211b21212121592d2c01b0184358b0022545b00225"
+ "4564606ab00325456a6120b00425456a208a8b65b0042523448cb00325234421211b20"
+ "456a4420456a44592d2c012045b00055b018435a584568234569b0408b6120b080626a"
+ "208a236120b003258b65b0042523448cb00325234421211b2121b0192b592d2c018a8a"
+ "45642345646164422d2cb00425b00425b0192bb0184358b00425b00425b00325b01b2b"
+ "01b0022543b04054b0022543b000545a58b003252045b040614459b0022543b00054b0"
+ "022543b040545a58b004252045b04060445959212121212d2c014b525843b002254523"
+ "61441b2121592d2c014b525843b00225452360441b2121592d2c4b525845441b212159"
+ "2d2c0120b003252349b04060b0206320b000525823b002253823b002256538008a6338"
+ "1b212121212159012d2c4b505845441b2121592d2c01b005251023208af500b0016023"
+ "edec2d2c01b005251023208af500b0016123edec2d2c01b0062510f500edec2d2c4623"
+ "46608a8a462320468a608a61b8ff8062232010238ab14b4b8a70456020b0005058b001"
+ "61b8ffba8b1bb0468c59b0106068013a2d2c2045b00325465258b0022546206861b003"
+ "25b003253f2321381b2111592d2c2045b00325465058b0022546206861b00325b00325"
+ "3f2321381b2111592d2c00b00743b006430b2d2c8a10ec2d2cb00c4358211b2046b000"
+ "5258b8fff0381bb0103859592d2c20b0005558b8100063b003254564b00325456461b0"
+ "005358b0021bb04061b00359254569535845441b2121591b21b0022545b00225456164"
+ "b028515845441b212159592d2c21210c6423648bb84000622d2c21b08051580c642364"
+ "8bb82000621bb200402f2b59b002602d2c21b0c051580c6423648bb81555621bb20080"
+ "2f2b59b002602d2c0c6423648bb84000626023212d2c4b5358b00425b0042549642345"
+ "6469b0408b6120b080626ab00225b00225618cb0462344218a10b046f6211b218a1123"
+ "1220392f592d2cb00225b002254964b0c05458b8fff838b008381b2121592d2cb01343"
+ "58031b02592d2cb0134358021b03592d2cb00a2b2310203cb0172b2d2cb00225b8fff0"
+ "38b0282b8a102320d023b0102bb0054358c01b3c59201011b00012012d2c4b53234b51"
+ "5a58381b2121592d2c01b0022510d023c901b00113b0001410b0013cb001162d2c01b0"
+ "0013b001b0032549b0031738b001132d2c4b53234b515a5820458a60441b2121592d2c"
+ "20392f2d"
+)
class TestFont(object):
@@ -70,7 +78,6 @@ class TestFont(object):
class ProgramTest(unittest.TestCase):
-
def test__bool__(self):
p = Program()
assert not bool(p)
@@ -83,13 +90,25 @@ class ProgramTest(unittest.TestCase):
assert not bool(p)
p = Program()
- asm = ['SVTCA[0]']
+ asm = ["SVTCA[0]"]
p.fromAssembly(asm)
assert bool(p)
- assert p.assembly.pop() == 'SVTCA[0]'
+ assert p.assembly.pop() == "SVTCA[0]"
assert not bool(p)
+ def test_from_assembly_list(self):
+ p = Program()
+ p.fromAssembly(ASSEMBLY)
+ asm = p.getAssembly()
+ assert ASSEMBLY == asm
+
+ def test_from_assembly_str(self):
+ p = Program()
+ p.fromAssembly("\n".join(ASSEMBLY))
+ asm = p.getAssembly()
+ assert ASSEMBLY == asm
+
def test_roundtrip(self):
p = Program()
p.fromBytecode(BYTECODE)
@@ -98,7 +117,7 @@ class ProgramTest(unittest.TestCase):
assert BYTECODE == p.getBytecode()
def test_xml_indentation(self):
- with open(TTPROGRAM_TTX, 'r', encoding='utf-8') as f:
+ with open(TTPROGRAM_TTX, "r", encoding="utf-8") as f:
ttProgramXML = f.read()
p = Program()
p.fromBytecode(BYTECODE)
@@ -110,8 +129,9 @@ class ProgramTest(unittest.TestCase):
finally:
output_string = buf.getvalue()
assert output_string == ttProgramXML
-
-if __name__ == '__main__':
+
+if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/ttLib/ttFont_test.py b/Tests/ttLib/ttFont_test.py
index e0e82b24..2203b4d9 100644
--- a/Tests/ttLib/ttFont_test.py
+++ b/Tests/ttLib/ttFont_test.py
@@ -2,8 +2,16 @@ import io
import os
import re
import random
+import tempfile
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
-from fontTools.ttLib import TTFont, newTable, registerCustomTableClass, unregisterCustomTableClass
+from fontTools.ttLib import (
+ TTFont,
+ TTLibError,
+ newTable,
+ registerCustomTableClass,
+ unregisterCustomTableClass,
+)
+from fontTools.ttLib.standardGlyphOrder import standardGlyphOrder
from fontTools.ttLib.tables.DefaultTable import DefaultTable
from fontTools.ttLib.tables._c_m_a_p import CmapSubtable
import pytest
@@ -13,7 +21,6 @@ DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data")
class CustomTableClass(DefaultTable):
-
def decompile(self, data, ttFont):
self.numbers = list(data)
@@ -143,6 +150,17 @@ def test_setGlyphOrder_also_updates_glyf_glyphOrder():
assert font["glyf"].glyphOrder == new_order
+def test_getGlyphOrder_not_true_post_format_1(caplog):
+ # https://github.com/fonttools/fonttools/issues/2736
+ caplog.set_level("WARNING")
+ font = TTFont(os.path.join(DATA_DIR, "bogus_post_format_1.ttf"))
+ hmtx = font["hmtx"]
+ assert len(hmtx.metrics) > len(standardGlyphOrder)
+ log_rec = caplog.records[-1]
+ assert log_rec.levelname == "WARNING"
+ assert "Not enough names found in the 'post' table" in log_rec.message
+
+
@pytest.mark.parametrize("lazy", [None, True, False])
def test_ensureDecompiled(lazy):
# test that no matter the lazy value, ensureDecompiled decompiles all tables
@@ -159,7 +177,7 @@ def test_ensureDecompiled(lazy):
feature dist {
pos period period -30;
} dist;
- """
+ """,
)
# also add an additional cmap subtable that will be lazily-loaded
cm = CmapSubtable.newSubtable(14)
@@ -167,7 +185,7 @@ def test_ensureDecompiled(lazy):
cm.platEncID = 5
cm.language = 0
cm.cmap = {}
- cm.uvsDict = {0xFE00: [(0x002e, None)]}
+ cm.uvsDict = {0xFE00: [(0x002E, None)]}
font["cmap"].tables.append(cm)
# save and reload, potentially lazily
@@ -212,3 +230,91 @@ def test_ensureDecompiled(lazy):
assert "Lookup" in font["GSUB"].table.LookupList.__dict__
assert "reader" not in font["GPOS"].table.LookupList.__dict__
assert "Lookup" in font["GPOS"].table.LookupList.__dict__
+
+
+@pytest.fixture
+def testFont_fvar_avar():
+ ttxpath = os.path.join(DATA_DIR, "TestTTF_normalizeLocation.ttx")
+ ttf = TTFont()
+ ttf.importXML(ttxpath)
+ return ttf
+
+
+@pytest.mark.parametrize(
+ "userLocation, expectedNormalizedLocation",
+ [
+ ({}, {"wght": 0.0}),
+ ({"wght": 100}, {"wght": -1.0}),
+ ({"wght": 250}, {"wght": -0.75}),
+ ({"wght": 400}, {"wght": 0.0}),
+ ({"wght": 550}, {"wght": 0.75}),
+ ({"wght": 625}, {"wght": 0.875}),
+ ({"wght": 700}, {"wght": 1.0}),
+ ],
+)
+def test_font_normalizeLocation(
+ testFont_fvar_avar, userLocation, expectedNormalizedLocation
+):
+ normalizedLocation = testFont_fvar_avar.normalizeLocation(userLocation)
+ assert expectedNormalizedLocation == normalizedLocation
+
+
+def test_font_normalizeLocation_no_VF():
+ ttf = TTFont()
+ with pytest.raises(TTLibError, match="Not a variable font"):
+ ttf.normalizeLocation({})
+
+
+def test_getGlyphID():
+ font = TTFont()
+ font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx"))
+
+ assert font.getGlyphID("space") == 3
+ assert font.getGlyphID("glyph12345") == 12345 # virtual glyph
+ with pytest.raises(KeyError):
+ font.getGlyphID("non_existent")
+ with pytest.raises(KeyError):
+ font.getGlyphID("glyph_prefix_but_invalid_id")
+
+
+def test_spooled_tempfile_may_not_have_attribute_seekable():
+ # SpooledTemporaryFile only got a seekable attribute on Python 3.11
+ # https://github.com/fonttools/fonttools/issues/3052
+ font = TTFont()
+ font.importXML(os.path.join(DATA_DIR, "TestTTF-Regular.ttx"))
+ tmp = tempfile.SpooledTemporaryFile()
+ font.save(tmp)
+ # this should not fail
+ _ = TTFont(tmp)
+
+
+def test_unseekable_file_lazy_loading_fails():
+ class NonSeekableFile:
+ def __init__(self):
+ self.file = io.BytesIO()
+
+ def read(self, size):
+ return self.file.read(size)
+
+ def seekable(self):
+ return False
+
+ f = NonSeekableFile()
+ with pytest.raises(TTLibError, match="Input file must be seekable when lazy=True"):
+ TTFont(f, lazy=True)
+
+
+def test_unsupported_seek_operation_lazy_loading_fails():
+ class UnsupportedSeekFile:
+ def __init__(self):
+ self.file = io.BytesIO()
+
+ def read(self, size):
+ return self.file.read(size)
+
+ def seek(self, offset):
+ raise io.UnsupportedOperation("Unsupported seek operation")
+
+ f = UnsupportedSeekFile()
+ with pytest.raises(TTLibError, match="Input file must be seekable when lazy=True"):
+ TTFont(f, lazy=True)
diff --git a/Tests/ttLib/ttGlyphSet_test.py b/Tests/ttLib/ttGlyphSet_test.py
index bc0bf2ce..56514464 100644
--- a/Tests/ttLib/ttGlyphSet_test.py
+++ b/Tests/ttLib/ttGlyphSet_test.py
@@ -1,112 +1,656 @@
from fontTools.ttLib import TTFont
from fontTools.ttLib import ttGlyphSet
-from fontTools.pens.recordingPen import RecordingPen
+from fontTools.pens.recordingPen import (
+ RecordingPen,
+ RecordingPointPen,
+ DecomposingRecordingPen,
+)
+from fontTools.misc.roundTools import otRound
+from fontTools.misc.transform import DecomposedTransform
import os
import pytest
class TTGlyphSetTest(object):
-
@staticmethod
def getpath(testfile):
path = os.path.dirname(__file__)
return os.path.join(path, "data", testfile)
@pytest.mark.parametrize(
- "location, expected",
+ "fontfile, location, expected",
[
(
+ "I.ttf",
None,
[
- ('moveTo', ((175, 0),)),
- ('lineTo', ((367, 0),)),
- ('lineTo', ((367, 1456),)),
- ('lineTo', ((175, 1456),)),
- ('closePath', ())
- ]
+ ("moveTo", ((175, 0),)),
+ ("lineTo", ((367, 0),)),
+ ("lineTo", ((367, 1456),)),
+ ("lineTo", ((175, 1456),)),
+ ("closePath", ()),
+ ],
),
(
+ "I.ttf",
{},
[
- ('moveTo', ((175, 0),)),
- ('lineTo', ((367, 0),)),
- ('lineTo', ((367, 1456),)),
- ('lineTo', ((175, 1456),)),
- ('closePath', ())
- ]
+ ("moveTo", ((175, 0),)),
+ ("lineTo", ((367, 0),)),
+ ("lineTo", ((367, 1456),)),
+ ("lineTo", ((175, 1456),)),
+ ("closePath", ()),
+ ],
),
(
- {'wght': 100},
+ "I.ttf",
+ {"wght": 100},
[
- ('moveTo', ((175, 0),)),
- ('lineTo', ((271, 0),)),
- ('lineTo', ((271, 1456),)),
- ('lineTo', ((175, 1456),)),
- ('closePath', ())
- ]
+ ("moveTo", ((175, 0),)),
+ ("lineTo", ((271, 0),)),
+ ("lineTo", ((271, 1456),)),
+ ("lineTo", ((175, 1456),)),
+ ("closePath", ()),
+ ],
),
(
- {'wght': 1000},
+ "I.ttf",
+ {"wght": 1000},
[
- ('moveTo', ((128, 0),)),
- ('lineTo', ((550, 0),)),
- ('lineTo', ((550, 1456),)),
- ('lineTo', ((128, 1456),)),
- ('closePath', ())
- ]
+ ("moveTo", ((128, 0),)),
+ ("lineTo", ((550, 0),)),
+ ("lineTo", ((550, 1456),)),
+ ("lineTo", ((128, 1456),)),
+ ("closePath", ()),
+ ],
),
(
- {'wght': 1000, 'wdth': 25},
+ "I.ttf",
+ {"wght": 1000, "wdth": 25},
[
- ('moveTo', ((140, 0),)),
- ('lineTo', ((553, 0),)),
- ('lineTo', ((553, 1456),)),
- ('lineTo', ((140, 1456),)),
- ('closePath', ())
- ]
+ ("moveTo", ((140, 0),)),
+ ("lineTo", ((553, 0),)),
+ ("lineTo", ((553, 1456),)),
+ ("lineTo", ((140, 1456),)),
+ ("closePath", ()),
+ ],
),
(
- {'wght': 1000, 'wdth': 50},
+ "I.ttf",
+ {"wght": 1000, "wdth": 50},
[
- ('moveTo', ((136, 0),)),
- ('lineTo', ((552, 0),)),
- ('lineTo', ((552, 1456),)),
- ('lineTo', ((136, 1456),)),
- ('closePath', ())
- ]
+ ("moveTo", ((136, 0),)),
+ ("lineTo", ((552, 0),)),
+ ("lineTo", ((552, 1456),)),
+ ("lineTo", ((136, 1456),)),
+ ("closePath", ()),
+ ],
),
- ]
+ (
+ "I.otf",
+ {"wght": 1000},
+ [
+ ("moveTo", ((179, 74),)),
+ ("lineTo", ((28, 59),)),
+ ("lineTo", ((28, 0),)),
+ ("lineTo", ((367, 0),)),
+ ("lineTo", ((367, 59),)),
+ ("lineTo", ((212, 74),)),
+ ("lineTo", ((179, 74),)),
+ ("closePath", ()),
+ ("moveTo", ((179, 578),)),
+ ("lineTo", ((212, 578),)),
+ ("lineTo", ((367, 593),)),
+ ("lineTo", ((367, 652),)),
+ ("lineTo", ((28, 652),)),
+ ("lineTo", ((28, 593),)),
+ ("lineTo", ((179, 578),)),
+ ("closePath", ()),
+ ("moveTo", ((98, 310),)),
+ ("curveTo", ((98, 205), (98, 101), (95, 0))),
+ ("lineTo", ((299, 0),)),
+ ("curveTo", ((296, 103), (296, 207), (296, 311))),
+ ("lineTo", ((296, 342),)),
+ ("curveTo", ((296, 447), (296, 551), (299, 652))),
+ ("lineTo", ((95, 652),)),
+ ("curveTo", ((98, 549), (98, 445), (98, 342))),
+ ("lineTo", ((98, 310),)),
+ ("closePath", ()),
+ ],
+ ),
+ (
+ # In this font, /I has an lsb of 30, but an xMin of 25, so an
+ # offset of 5 units needs to be applied when drawing the outline.
+ # See https://github.com/fonttools/fonttools/issues/2824
+ "issue2824.ttf",
+ None,
+ [
+ ("moveTo", ((309, 180),)),
+ ("qCurveTo", ((274, 151), (187, 136), (104, 166), (74, 201))),
+ ("qCurveTo", ((45, 236), (30, 323), (59, 407), (95, 436))),
+ ("qCurveTo", ((130, 466), (217, 480), (301, 451), (330, 415))),
+ ("qCurveTo", ((360, 380), (374, 293), (345, 210), (309, 180))),
+ ("closePath", ()),
+ ],
+ ),
+ ],
)
- def test_glyphset(
- self, location, expected
- ):
- # TODO: also test loading CFF-flavored fonts
- font = TTFont(self.getpath("I.ttf"))
+ def test_glyphset(self, fontfile, location, expected):
+ font = TTFont(self.getpath(fontfile))
glyphset = font.getGlyphSet(location=location)
assert isinstance(glyphset, ttGlyphSet._TTGlyphSet)
- if location:
- assert isinstance(glyphset, ttGlyphSet._TTVarGlyphSet)
assert list(glyphset.keys()) == [".notdef", "I"]
assert "I" in glyphset
- assert glyphset.has_key("I") # we should really get rid of this...
+ with pytest.deprecated_call():
+ assert glyphset.has_key("I") # we should really get rid of this...
assert len(glyphset) == 2
pen = RecordingPen()
- glyph = glyphset['I']
+ glyph = glyphset["I"]
assert glyphset.get("foobar") is None
assert isinstance(glyph, ttGlyphSet._TTGlyph)
- if location:
- assert isinstance(glyph, ttGlyphSet._TTVarGlyphGlyf)
- else:
- assert isinstance(glyph, ttGlyphSet._TTGlyphGlyf)
+ is_glyf = fontfile.endswith(".ttf")
+ glyphType = ttGlyphSet._TTGlyphGlyf if is_glyf else ttGlyphSet._TTGlyphCFF
+ assert isinstance(glyph, glyphType)
glyph.draw(pen)
actual = pen.value
assert actual == expected, (location, actual, expected)
+
+ def test_glyphset_varComposite_components(self):
+ font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
+ glyphset = font.getGlyphSet()
+
+ pen = RecordingPen()
+ glyph = glyphset["uniAC00"]
+
+ glyph.draw(pen)
+ actual = pen.value
+
+ expected = [
+ (
+ "addVarComponent",
+ (
+ "glyph00003",
+ DecomposedTransform(460.0, 676.0, 0, 1, 1, 0, 0, 0, 0),
+ {
+ "0000": 0.84661865234375,
+ "0001": 0.98944091796875,
+ "0002": 0.47283935546875,
+ "0003": 0.446533203125,
+ },
+ ),
+ ),
+ (
+ "addVarComponent",
+ (
+ "glyph00004",
+ DecomposedTransform(932.0, 382.0, 0, 1, 1, 0, 0, 0, 0),
+ {
+ "0000": 0.93359375,
+ "0001": 0.916015625,
+ "0002": 0.523193359375,
+ "0003": 0.32806396484375,
+ "0004": 0.85089111328125,
+ },
+ ),
+ ),
+ ]
+
+ assert actual == expected, (actual, expected)
+
+ def test_glyphset_varComposite1(self):
+ font = TTFont(self.getpath("varc-ac00-ac01.ttf"))
+ glyphset = font.getGlyphSet(location={"wght": 600})
+
+ pen = DecomposingRecordingPen(glyphset)
+ glyph = glyphset["uniAC00"]
+
+ glyph.draw(pen)
+ actual = pen.value
+
+ expected = [
+ ("moveTo", ((432, 678),)),
+ ("lineTo", ((432, 620),)),
+ (
+ "qCurveTo",
+ (
+ (419, 620),
+ (374, 621),
+ (324, 619),
+ (275, 618),
+ (237, 617),
+ (228, 616),
+ ),
+ ),
+ ("qCurveTo", ((218, 616), (188, 612), (160, 605), (149, 601))),
+ ("qCurveTo", ((127, 611), (83, 639), (67, 654))),
+ ("qCurveTo", ((64, 657), (63, 662), (64, 666))),
+ ("lineTo", ((72, 678),)),
+ ("qCurveTo", ((93, 674), (144, 672), (164, 672))),
+ (
+ "qCurveTo",
+ (
+ (173, 672),
+ (213, 672),
+ (266, 673),
+ (323, 674),
+ (377, 675),
+ (421, 678),
+ (432, 678),
+ ),
+ ),
+ ("closePath", ()),
+ ("moveTo", ((525, 619),)),
+ ("lineTo", ((412, 620),)),
+ ("lineTo", ((429, 678),)),
+ ("lineTo", ((466, 697),)),
+ ("qCurveTo", ((470, 698), (482, 698), (486, 697))),
+ ("qCurveTo", ((494, 693), (515, 682), (536, 670), (541, 667))),
+ ("qCurveTo", ((545, 663), (545, 656), (543, 652))),
+ ("lineTo", ((525, 619),)),
+ ("closePath", ()),
+ ("moveTo", ((63, 118),)),
+ ("lineTo", ((47, 135),)),
+ ("qCurveTo", ((42, 141), (48, 146))),
+ ("qCurveTo", ((135, 213), (278, 373), (383, 541), (412, 620))),
+ ("lineTo", ((471, 642),)),
+ ("lineTo", ((525, 619),)),
+ ("qCurveTo", ((496, 529), (365, 342), (183, 179), (75, 121))),
+ ("qCurveTo", ((72, 119), (65, 118), (63, 118))),
+ ("closePath", ()),
+ ("moveTo", ((925, 372),)),
+ ("lineTo", ((739, 368),)),
+ ("lineTo", ((739, 427),)),
+ ("lineTo", ((822, 430),)),
+ ("lineTo", ((854, 451),)),
+ ("qCurveTo", ((878, 453), (930, 449), (944, 445))),
+ ("qCurveTo", ((961, 441), (962, 426))),
+ ("qCurveTo", ((964, 411), (956, 386), (951, 381))),
+ ("qCurveTo", ((947, 376), (931, 372), (925, 372))),
+ ("closePath", ()),
+ ("moveTo", ((729, -113),)),
+ ("lineTo", ((674, -113),)),
+ ("qCurveTo", ((671, -98), (669, -42), (666, 22), (665, 83), (665, 102))),
+ ("lineTo", ((665, 763),)),
+ ("qCurveTo", ((654, 780), (608, 810), (582, 820))),
+ ("lineTo", ((593, 850),)),
+ ("qCurveTo", ((594, 852), (599, 856), (607, 856))),
+ ("qCurveTo", ((628, 855), (684, 846), (736, 834), (752, 827))),
+ ("qCurveTo", ((766, 818), (766, 802))),
+ ("lineTo", ((762, 745),)),
+ ("lineTo", ((762, 134),)),
+ ("qCurveTo", ((762, 107), (757, 43), (749, -25), (737, -87), (729, -113))),
+ ("closePath", ()),
+ ]
+
+ actual = [
+ (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
+ for op, args in actual
+ ]
+
+ assert actual == expected, (actual, expected)
+
+ # Test that drawing twice works, we accidentally don't change the component
+ pen = DecomposingRecordingPen(glyphset)
+ glyph.draw(pen)
+ actual = pen.value
+ actual = [
+ (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
+ for op, args in actual
+ ]
+ assert actual == expected, (actual, expected)
+
+ pen = RecordingPointPen()
+ glyph.drawPoints(pen)
+ assert pen.value
+
+ def test_glyphset_varComposite2(self):
+ # This test font has axis variations
+
+ font = TTFont(self.getpath("varc-6868.ttf"))
+ glyphset = font.getGlyphSet(location={"wght": 600})
+
+ pen = DecomposingRecordingPen(glyphset)
+ glyph = glyphset["uni6868"]
+
+ glyph.draw(pen)
+ actual = pen.value
+
+ expected = [
+ ("moveTo", ((460, 565),)),
+ (
+ "qCurveTo",
+ (
+ (482, 577),
+ (526, 603),
+ (568, 632),
+ (607, 663),
+ (644, 698),
+ (678, 735),
+ (708, 775),
+ (721, 796),
+ ),
+ ),
+ ("lineTo", ((632, 835),)),
+ (
+ "qCurveTo",
+ (
+ (621, 817),
+ (595, 784),
+ (566, 753),
+ (534, 724),
+ (499, 698),
+ (462, 675),
+ (423, 653),
+ (403, 644),
+ ),
+ ),
+ ("closePath", ()),
+ ("moveTo", ((616, 765),)),
+ ("lineTo", ((590, 682),)),
+ ("lineTo", ((830, 682),)),
+ ("lineTo", ((833, 682),)),
+ ("lineTo", ((828, 693),)),
+ (
+ "qCurveTo",
+ (
+ (817, 671),
+ (775, 620),
+ (709, 571),
+ (615, 525),
+ (492, 490),
+ (413, 480),
+ ),
+ ),
+ ("lineTo", ((454, 386),)),
+ (
+ "qCurveTo",
+ (
+ (544, 403),
+ (687, 455),
+ (798, 519),
+ (877, 590),
+ (926, 655),
+ (937, 684),
+ ),
+ ),
+ ("lineTo", ((937, 765),)),
+ ("closePath", ()),
+ ("moveTo", ((723, 555),)),
+ (
+ "qCurveTo",
+ (
+ (713, 563),
+ (693, 579),
+ (672, 595),
+ (651, 610),
+ (629, 625),
+ (606, 638),
+ (583, 651),
+ (572, 657),
+ ),
+ ),
+ ("lineTo", ((514, 590),)),
+ (
+ "qCurveTo",
+ (
+ (525, 584),
+ (547, 572),
+ (568, 559),
+ (589, 545),
+ (609, 531),
+ (629, 516),
+ (648, 500),
+ (657, 492),
+ ),
+ ),
+ ("closePath", ()),
+ ("moveTo", ((387, 375),)),
+ ("lineTo", ((387, 830),)),
+ ("lineTo", ((289, 830),)),
+ ("lineTo", ((289, 375),)),
+ ("closePath", ()),
+ ("moveTo", ((96, 383),)),
+ (
+ "qCurveTo",
+ (
+ (116, 390),
+ (156, 408),
+ (194, 427),
+ (231, 449),
+ (268, 472),
+ (302, 497),
+ (335, 525),
+ (351, 539),
+ ),
+ ),
+ ("lineTo", ((307, 610),)),
+ (
+ "qCurveTo",
+ (
+ (291, 597),
+ (257, 572),
+ (221, 549),
+ (185, 528),
+ (147, 509),
+ (108, 492),
+ (69, 476),
+ (48, 469),
+ ),
+ ),
+ ("closePath", ()),
+ ("moveTo", ((290, 653),)),
+ (
+ "qCurveTo",
+ (
+ (281, 664),
+ (261, 687),
+ (240, 708),
+ (219, 729),
+ (196, 749),
+ (173, 768),
+ (148, 786),
+ (136, 794),
+ ),
+ ),
+ ("lineTo", ((69, 727),)),
+ (
+ "qCurveTo",
+ (
+ (81, 719),
+ (105, 702),
+ (129, 684),
+ (151, 665),
+ (173, 645),
+ (193, 625),
+ (213, 604),
+ (222, 593),
+ ),
+ ),
+ ("closePath", ()),
+ ("moveTo", ((913, -57),)),
+ ("lineTo", ((953, 30),)),
+ (
+ "qCurveTo",
+ (
+ (919, 41),
+ (854, 67),
+ (790, 98),
+ (729, 134),
+ (671, 173),
+ (616, 217),
+ (564, 264),
+ (540, 290),
+ ),
+ ),
+ ("lineTo", ((522, 286),)),
+ ("qCurveTo", ((511, 267), (498, 235), (493, 213), (492, 206))),
+ ("lineTo", ((515, 209),)),
+ ("qCurveTo", ((569, 146), (695, 44), (835, -32), (913, -57))),
+ ("closePath", ()),
+ ("moveTo", ((474, 274),)),
+ ("lineTo", ((452, 284),)),
+ (
+ "qCurveTo",
+ (
+ (428, 260),
+ (377, 214),
+ (323, 172),
+ (266, 135),
+ (206, 101),
+ (144, 71),
+ (80, 46),
+ (47, 36),
+ ),
+ ),
+ ("lineTo", ((89, -53),)),
+ ("qCurveTo", ((163, -29), (299, 46), (423, 142), (476, 201))),
+ ("lineTo", ((498, 196),)),
+ ("qCurveTo", ((498, 203), (494, 225), (482, 255), (474, 274))),
+ ("closePath", ()),
+ ("moveTo", ((450, 250),)),
+ ("lineTo", ((550, 250),)),
+ ("lineTo", ((550, 379),)),
+ ("lineTo", ((450, 379),)),
+ ("closePath", ()),
+ ("moveTo", ((68, 215),)),
+ ("lineTo", ((932, 215),)),
+ ("lineTo", ((932, 305),)),
+ ("lineTo", ((68, 305),)),
+ ("closePath", ()),
+ ("moveTo", ((450, -71),)),
+ ("lineTo", ((550, -71),)),
+ ("lineTo", ((550, -71),)),
+ ("lineTo", ((550, 267),)),
+ ("lineTo", ((450, 267),)),
+ ("lineTo", ((450, -71),)),
+ ("closePath", ()),
+ ]
+
+ actual = [
+ (op, tuple((otRound(pt[0]), otRound(pt[1])) for pt in args))
+ for op, args in actual
+ ]
+
+ assert actual == expected, (actual, expected)
+
+ pen = RecordingPointPen()
+ glyph.drawPoints(pen)
+ assert pen.value
+
+ def test_cubic_glyf(self):
+ font = TTFont(self.getpath("dot-cubic.ttf"))
+ glyphset = font.getGlyphSet()
+
+ expected = [
+ ("moveTo", ((76, 181),)),
+ ("curveTo", ((103, 181), (125, 158), (125, 131))),
+ ("curveTo", ((125, 104), (103, 82), (76, 82))),
+ ("curveTo", ((48, 82), (26, 104), (26, 131))),
+ ("curveTo", ((26, 158), (48, 181), (76, 181))),
+ ("closePath", ()),
+ ]
+
+ pen = RecordingPen()
+ glyphset["one"].draw(pen)
+ assert pen.value == expected
+
+ expectedPoints = [
+ ("beginPath", (), {}),
+ ("addPoint", ((76, 181), "curve", False, None), {}),
+ ("addPoint", ((103, 181), None, False, None), {}),
+ ("addPoint", ((125, 158), None, False, None), {}),
+ ("addPoint", ((125, 104), None, False, None), {}),
+ ("addPoint", ((103, 82), None, False, None), {}),
+ ("addPoint", ((76, 82), "curve", False, None), {}),
+ ("addPoint", ((48, 82), None, False, None), {}),
+ ("addPoint", ((26, 104), None, False, None), {}),
+ ("addPoint", ((26, 158), None, False, None), {}),
+ ("addPoint", ((48, 181), None, False, None), {}),
+ ("endPath", (), {}),
+ ]
+ pen = RecordingPointPen()
+ glyphset["one"].drawPoints(pen)
+ assert pen.value == expectedPoints
+
+ pen = RecordingPen()
+ glyphset["two"].draw(pen)
+ assert pen.value == expected
+
+ expectedPoints = [
+ ("beginPath", (), {}),
+ ("addPoint", ((26, 158), None, False, None), {}),
+ ("addPoint", ((48, 181), None, False, None), {}),
+ ("addPoint", ((76, 181), "curve", False, None), {}),
+ ("addPoint", ((103, 181), None, False, None), {}),
+ ("addPoint", ((125, 158), None, False, None), {}),
+ ("addPoint", ((125, 104), None, False, None), {}),
+ ("addPoint", ((103, 82), None, False, None), {}),
+ ("addPoint", ((76, 82), "curve", False, None), {}),
+ ("addPoint", ((48, 82), None, False, None), {}),
+ ("addPoint", ((26, 104), None, False, None), {}),
+ ("endPath", (), {}),
+ ]
+ pen = RecordingPointPen()
+ glyphset["two"].drawPoints(pen)
+ assert pen.value == expectedPoints
+
+ pen = RecordingPen()
+ glyphset["three"].draw(pen)
+ assert pen.value == expected
+
+ expectedPoints = [
+ ("beginPath", (), {}),
+ ("addPoint", ((48, 82), None, False, None), {}),
+ ("addPoint", ((26, 104), None, False, None), {}),
+ ("addPoint", ((26, 158), None, False, None), {}),
+ ("addPoint", ((48, 181), None, False, None), {}),
+ ("addPoint", ((76, 181), "curve", False, None), {}),
+ ("addPoint", ((103, 181), None, False, None), {}),
+ ("addPoint", ((125, 158), None, False, None), {}),
+ ("addPoint", ((125, 104), None, False, None), {}),
+ ("addPoint", ((103, 82), None, False, None), {}),
+ ("addPoint", ((76, 82), "curve", False, None), {}),
+ ("endPath", (), {}),
+ ]
+ pen = RecordingPointPen()
+ glyphset["three"].drawPoints(pen)
+ assert pen.value == expectedPoints
+
+ pen = RecordingPen()
+ glyphset["four"].draw(pen)
+ assert pen.value == [
+ ("moveTo", ((75.5, 181),)),
+ ("curveTo", ((103, 181), (125, 158), (125, 131))),
+ ("curveTo", ((125, 104), (103, 82), (75.5, 82))),
+ ("curveTo", ((48, 82), (26, 104), (26, 131))),
+ ("curveTo", ((26, 158), (48, 181), (75.5, 181))),
+ ("closePath", ()),
+ ]
+
+ # Ouch! We can't represent all-cubic-offcurves in pointPen!
+ # https://github.com/fonttools/fonttools/issues/3191
+ expectedPoints = [
+ ("beginPath", (), {}),
+ ("addPoint", ((103, 181), None, False, None), {}),
+ ("addPoint", ((125, 158), None, False, None), {}),
+ ("addPoint", ((125, 104), None, False, None), {}),
+ ("addPoint", ((103, 82), None, False, None), {}),
+ ("addPoint", ((48, 82), None, False, None), {}),
+ ("addPoint", ((26, 104), None, False, None), {}),
+ ("addPoint", ((26, 158), None, False, None), {}),
+ ("addPoint", ((48, 181), None, False, None), {}),
+ ("endPath", (), {}),
+ ]
+ pen = RecordingPointPen()
+ glyphset["four"].drawPoints(pen)
+ print(pen.value)
+ assert pen.value == expectedPoints
diff --git a/Tests/ttLib/ttVisitor_test.py b/Tests/ttLib/ttVisitor_test.py
index e84e213c..1c429343 100644
--- a/Tests/ttLib/ttVisitor_test.py
+++ b/Tests/ttLib/ttVisitor_test.py
@@ -21,14 +21,12 @@ class TestVisitor(TTVisitor):
class TTVisitorTest(object):
-
@staticmethod
def getpath(testfile):
path = os.path.dirname(__file__)
return os.path.join(path, "data", testfile)
def test_ttvisitor(self):
-
font = TTFont(self.getpath("TestVGID-Regular.otf"))
visitor = TestVisitor()
diff --git a/Tests/ttLib/woff2_test.py b/Tests/ttLib/woff2_test.py
index 7fe40dd1..e098eb99 100644
--- a/Tests/ttLib/woff2_test.py
+++ b/Tests/ttLib/woff2_test.py
@@ -2,16 +2,33 @@ 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,
- getKnownTagIndex, packBase128, base128Size, woff2UnknownTagIndex,
- WOFF2FlavorData, woff2TransformedTableTags, WOFF2GlyfTable, WOFF2LocaTable,
- WOFF2HmtxTable, WOFF2Writer, unpackBase128, unpack255UShort, pack255UShort)
+ WOFF2Reader,
+ woff2DirectorySize,
+ woff2DirectoryFormat,
+ woff2FlagsSize,
+ woff2UnknownTagSize,
+ woff2Base128MaxSize,
+ WOFF2DirectoryEntry,
+ getKnownTagIndex,
+ packBase128,
+ base128Size,
+ woff2UnknownTagIndex,
+ WOFF2FlavorData,
+ woff2TransformedTableTags,
+ WOFF2GlyfTable,
+ WOFF2LocaTable,
+ WOFF2HmtxTable,
+ WOFF2Writer,
+ unpackBase128,
+ unpack255UShort,
+ pack255UShort,
+)
import unittest
from fontTools.misc import sstruct
from fontTools.misc.textTools import Tag, bytechr, byteord
from fontTools import fontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen
+from fontTools.pens.recordingPen import RecordingPen
from io import BytesIO
import struct
import os
@@ -23,1421 +40,1495 @@ import pytest
haveBrotli = False
try:
- try:
- import brotlicffi as brotli
- except ImportError:
- import brotli
- haveBrotli = True
+ try:
+ import brotlicffi as brotli
+ except ImportError:
+ import brotli
+ haveBrotli = True
except ImportError:
- pass
+ pass
# 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
+if not hasattr(unittest.TestCase, "assertRaisesRegex"):
+ unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
current_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
-data_dir = os.path.join(current_dir, 'data')
-TTX = os.path.join(data_dir, 'TestTTF-Regular.ttx')
-OTX = os.path.join(data_dir, 'TestOTF-Regular.otx')
-METADATA = os.path.join(data_dir, 'test_woff2_metadata.xml')
+data_dir = os.path.join(current_dir, "data")
+TTX = os.path.join(data_dir, "TestTTF-Regular.ttx")
+OTX = os.path.join(data_dir, "TestOTF-Regular.otx")
+METADATA = os.path.join(data_dir, "test_woff2_metadata.xml")
TT_WOFF2 = BytesIO()
CFF_WOFF2 = BytesIO()
def setUpModule():
- if not haveBrotli:
- raise unittest.SkipTest("No module named brotli")
- assert os.path.exists(TTX)
- assert os.path.exists(OTX)
- # import TT-flavoured test font and save it as WOFF2
- ttf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- ttf.importXML(TTX)
- ttf.flavor = "woff2"
- ttf.save(TT_WOFF2, reorderTables=None)
- # import CFF-flavoured test font and save it as WOFF2
- otf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- otf.importXML(OTX)
- otf.flavor = "woff2"
- otf.save(CFF_WOFF2, reorderTables=None)
+ if not haveBrotli:
+ raise unittest.SkipTest("No module named brotli")
+ assert os.path.exists(TTX)
+ assert os.path.exists(OTX)
+ # import TT-flavoured test font and save it as WOFF2
+ ttf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ ttf.importXML(TTX)
+ ttf.flavor = "woff2"
+ ttf.save(TT_WOFF2, reorderTables=None)
+ # import CFF-flavoured test font and save it as WOFF2
+ otf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ otf.importXML(OTX)
+ otf.flavor = "woff2"
+ otf.save(CFF_WOFF2, reorderTables=None)
class WOFF2ReaderTest(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- cls.file = BytesIO(CFF_WOFF2.getvalue())
- cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- cls.font.importXML(OTX)
-
- def setUp(self):
- self.file.seek(0)
-
- def test_bad_signature(self):
- with self.assertRaisesRegex(ttLib.TTLibError, 'bad signature'):
- WOFF2Reader(BytesIO(b"wOFF"))
-
- def test_not_enough_data_header(self):
- incomplete_header = self.file.read(woff2DirectorySize - 1)
- with self.assertRaisesRegex(ttLib.TTLibError, 'not enough data'):
- WOFF2Reader(BytesIO(incomplete_header))
-
- def test_incorrect_compressed_size(self):
- data = self.file.read(woff2DirectorySize)
- header = sstruct.unpack(woff2DirectoryFormat, data)
- header['totalCompressedSize'] = 0
- data = sstruct.pack(woff2DirectoryFormat, header)
- with self.assertRaises((brotli.error, ttLib.TTLibError)):
- WOFF2Reader(BytesIO(data + self.file.read()))
-
- def test_incorrect_uncompressed_size(self):
- decompress_backup = brotli.decompress
- brotli.decompress = lambda data: b"" # return empty byte string
- with self.assertRaisesRegex(ttLib.TTLibError, 'unexpected size for decompressed'):
- WOFF2Reader(self.file)
- brotli.decompress = decompress_backup
-
- def test_incorrect_file_size(self):
- data = self.file.read(woff2DirectorySize)
- header = sstruct.unpack(woff2DirectoryFormat, data)
- header['length'] -= 1
- data = sstruct.pack(woff2DirectoryFormat, header)
- with self.assertRaisesRegex(
- ttLib.TTLibError, "doesn't match the actual file size"):
- WOFF2Reader(BytesIO(data + self.file.read()))
-
- def test_num_tables(self):
- tags = [t for t in self.font.keys() if t not in ('GlyphOrder', 'DSIG')]
- data = self.file.read(woff2DirectorySize)
- header = sstruct.unpack(woff2DirectoryFormat, data)
- self.assertEqual(header['numTables'], len(tags))
-
- def test_table_tags(self):
- tags = set([t for t in self.font.keys() if t not in ('GlyphOrder', 'DSIG')])
- reader = WOFF2Reader(self.file)
- self.assertEqual(set(reader.keys()), tags)
-
- def test_get_normal_tables(self):
- woff2Reader = WOFF2Reader(self.file)
- specialTags = woff2TransformedTableTags + ('head', 'GlyphOrder', 'DSIG')
- for tag in [t for t in self.font.keys() if t not in specialTags]:
- origData = self.font.getTableData(tag)
- decompressedData = woff2Reader[tag]
- self.assertEqual(origData, decompressedData)
-
- def test_reconstruct_unknown(self):
- reader = WOFF2Reader(self.file)
- with self.assertRaisesRegex(ttLib.TTLibError, 'transform for table .* unknown'):
- reader.reconstructTable('head')
+ @classmethod
+ def setUpClass(cls):
+ cls.file = BytesIO(CFF_WOFF2.getvalue())
+ cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ cls.font.importXML(OTX)
+
+ def setUp(self):
+ self.file.seek(0)
+
+ def test_bad_signature(self):
+ with self.assertRaisesRegex(ttLib.TTLibError, "bad signature"):
+ WOFF2Reader(BytesIO(b"wOFF"))
+
+ def test_not_enough_data_header(self):
+ incomplete_header = self.file.read(woff2DirectorySize - 1)
+ with self.assertRaisesRegex(ttLib.TTLibError, "not enough data"):
+ WOFF2Reader(BytesIO(incomplete_header))
+
+ def test_incorrect_compressed_size(self):
+ data = self.file.read(woff2DirectorySize)
+ header = sstruct.unpack(woff2DirectoryFormat, data)
+ header["totalCompressedSize"] = 0
+ data = sstruct.pack(woff2DirectoryFormat, header)
+ with self.assertRaises((brotli.error, ttLib.TTLibError)):
+ WOFF2Reader(BytesIO(data + self.file.read()))
+
+ def test_incorrect_uncompressed_size(self):
+ decompress_backup = brotli.decompress
+ brotli.decompress = lambda data: b"" # return empty byte string
+ with self.assertRaisesRegex(
+ ttLib.TTLibError, "unexpected size for decompressed"
+ ):
+ WOFF2Reader(self.file)
+ brotli.decompress = decompress_backup
+
+ def test_incorrect_file_size(self):
+ data = self.file.read(woff2DirectorySize)
+ header = sstruct.unpack(woff2DirectoryFormat, data)
+ header["length"] -= 1
+ data = sstruct.pack(woff2DirectoryFormat, header)
+ with self.assertRaisesRegex(
+ ttLib.TTLibError, "doesn't match the actual file size"
+ ):
+ WOFF2Reader(BytesIO(data + self.file.read()))
+
+ def test_num_tables(self):
+ tags = [t for t in self.font.keys() if t not in ("GlyphOrder", "DSIG")]
+ data = self.file.read(woff2DirectorySize)
+ header = sstruct.unpack(woff2DirectoryFormat, data)
+ self.assertEqual(header["numTables"], len(tags))
+
+ def test_table_tags(self):
+ tags = set([t for t in self.font.keys() if t not in ("GlyphOrder", "DSIG")])
+ reader = WOFF2Reader(self.file)
+ self.assertEqual(set(reader.keys()), tags)
+
+ def test_get_normal_tables(self):
+ woff2Reader = WOFF2Reader(self.file)
+ specialTags = woff2TransformedTableTags + ("head", "GlyphOrder", "DSIG")
+ for tag in [t for t in self.font.keys() if t not in specialTags]:
+ origData = self.font.getTableData(tag)
+ decompressedData = woff2Reader[tag]
+ self.assertEqual(origData, decompressedData)
+
+ def test_reconstruct_unknown(self):
+ reader = WOFF2Reader(self.file)
+ with self.assertRaisesRegex(ttLib.TTLibError, "transform for table .* unknown"):
+ reader.reconstructTable("head")
class WOFF2ReaderTTFTest(WOFF2ReaderTest):
- """ Tests specific to TT-flavored fonts. """
-
- @classmethod
- def setUpClass(cls):
- cls.file = BytesIO(TT_WOFF2.getvalue())
- cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- cls.font.importXML(TTX)
-
- def setUp(self):
- self.file.seek(0)
-
- def test_reconstruct_glyf(self):
- woff2Reader = WOFF2Reader(self.file)
- reconstructedData = woff2Reader['glyf']
- self.assertEqual(self.font.getTableData('glyf'), reconstructedData)
-
- def test_reconstruct_loca(self):
- woff2Reader = WOFF2Reader(self.file)
- reconstructedData = woff2Reader['loca']
- self.font.getTableData("glyf") # 'glyf' needs to be compiled before 'loca'
- self.assertEqual(self.font.getTableData('loca'), reconstructedData)
- self.assertTrue(hasattr(woff2Reader.tables['glyf'], 'data'))
-
- def test_reconstruct_loca_not_match_orig_size(self):
- reader = WOFF2Reader(self.file)
- reader.tables['loca'].origLength -= 1
- with self.assertRaisesRegex(
- ttLib.TTLibError, "'loca' table doesn't match original size"):
- reader.reconstructTable('loca')
+ """Tests specific to TT-flavored fonts."""
+
+ @classmethod
+ def setUpClass(cls):
+ cls.file = BytesIO(TT_WOFF2.getvalue())
+ cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ cls.font.importXML(TTX)
+
+ def setUp(self):
+ self.file.seek(0)
+
+ def test_reconstruct_glyf(self):
+ woff2Reader = WOFF2Reader(self.file)
+ reconstructedData = woff2Reader["glyf"]
+ self.assertEqual(self.font.getTableData("glyf"), reconstructedData)
+
+ def test_reconstruct_loca(self):
+ woff2Reader = WOFF2Reader(self.file)
+ reconstructedData = woff2Reader["loca"]
+ self.font.getTableData("glyf") # 'glyf' needs to be compiled before 'loca'
+ self.assertEqual(self.font.getTableData("loca"), reconstructedData)
+ self.assertTrue(hasattr(woff2Reader.tables["glyf"], "data"))
+
+ def test_reconstruct_loca_not_match_orig_size(self):
+ reader = WOFF2Reader(self.file)
+ reader.tables["loca"].origLength -= 1
+ with self.assertRaisesRegex(
+ ttLib.TTLibError, "'loca' table doesn't match original size"
+ ):
+ reader.reconstructTable("loca")
def normalise_table(font, tag, padding=4):
- """ Return normalised table data. Keep 'font' instance unmodified. """
- assert tag in ('glyf', 'loca', 'head')
- assert tag in font
- if tag == 'head':
- origHeadFlags = font['head'].flags
- font['head'].flags |= (1 << 11)
- tableData = font['head'].compile(font)
- if font.sfntVersion in ("\x00\x01\x00\x00", "true"):
- assert {'glyf', 'loca', 'head'}.issubset(font.keys())
- origIndexFormat = font['head'].indexToLocFormat
- if hasattr(font['loca'], 'locations'):
- origLocations = font['loca'].locations[:]
- else:
- origLocations = []
- glyfTable = ttLib.newTable('glyf')
- glyfTable.decompile(font.getTableData('glyf'), font)
- glyfTable.padding = padding
- if tag == 'glyf':
- tableData = glyfTable.compile(font)
- elif tag == 'loca':
- glyfTable.compile(font)
- tableData = font['loca'].compile(font)
- if tag == 'head':
- glyfTable.compile(font)
- font['loca'].compile(font)
- tableData = font['head'].compile(font)
- font['head'].indexToLocFormat = origIndexFormat
- font['loca'].set(origLocations)
- if tag == 'head':
- font['head'].flags = origHeadFlags
- return tableData
+ """Return normalised table data. Keep 'font' instance unmodified."""
+ assert tag in ("glyf", "loca", "head")
+ assert tag in font
+ if tag == "head":
+ origHeadFlags = font["head"].flags
+ font["head"].flags |= 1 << 11
+ tableData = font["head"].compile(font)
+ if font.sfntVersion in ("\x00\x01\x00\x00", "true"):
+ assert {"glyf", "loca", "head"}.issubset(font.keys())
+ origIndexFormat = font["head"].indexToLocFormat
+ if hasattr(font["loca"], "locations"):
+ origLocations = font["loca"].locations[:]
+ else:
+ origLocations = []
+ glyfTable = ttLib.newTable("glyf")
+ glyfTable.decompile(font.getTableData("glyf"), font)
+ glyfTable.padding = padding
+ if tag == "glyf":
+ tableData = glyfTable.compile(font)
+ elif tag == "loca":
+ glyfTable.compile(font)
+ tableData = font["loca"].compile(font)
+ if tag == "head":
+ glyfTable.compile(font)
+ font["loca"].compile(font)
+ tableData = font["head"].compile(font)
+ font["head"].indexToLocFormat = origIndexFormat
+ font["loca"].set(origLocations)
+ if tag == "head":
+ font["head"].flags = origHeadFlags
+ return tableData
def normalise_font(font, padding=4):
- """ Return normalised font data. Keep 'font' instance unmodified. """
- # drop DSIG but keep a copy
- DSIG_copy = copy.deepcopy(font['DSIG'])
- del font['DSIG']
- # override TTFont attributes
- origFlavor = font.flavor
- origRecalcBBoxes = font.recalcBBoxes
- origRecalcTimestamp = font.recalcTimestamp
- origLazy = font.lazy
- font.flavor = None
- font.recalcBBoxes = False
- font.recalcTimestamp = False
- font.lazy = True
- # save font to temporary stream
- infile = BytesIO()
- font.save(infile)
- infile.seek(0)
- # reorder tables alphabetically
- outfile = BytesIO()
- reader = ttLib.sfnt.SFNTReader(infile)
- writer = ttLib.sfnt.SFNTWriter(
- outfile, len(reader.tables), reader.sfntVersion, reader.flavor, reader.flavorData)
- for tag in sorted(reader.keys()):
- if tag in woff2TransformedTableTags + ('head',):
- writer[tag] = normalise_table(font, tag, padding)
- else:
- writer[tag] = reader[tag]
- writer.close()
- # restore font attributes
- font['DSIG'] = DSIG_copy
- font.flavor = origFlavor
- font.recalcBBoxes = origRecalcBBoxes
- font.recalcTimestamp = origRecalcTimestamp
- font.lazy = origLazy
- return outfile.getvalue()
+ """Return normalised font data. Keep 'font' instance unmodified."""
+ # drop DSIG but keep a copy
+ DSIG_copy = copy.deepcopy(font["DSIG"])
+ del font["DSIG"]
+ # override TTFont attributes
+ origFlavor = font.flavor
+ origRecalcBBoxes = font.recalcBBoxes
+ origRecalcTimestamp = font.recalcTimestamp
+ origLazy = font.lazy
+ font.flavor = None
+ font.recalcBBoxes = False
+ font.recalcTimestamp = False
+ font.lazy = True
+ # save font to temporary stream
+ infile = BytesIO()
+ font.save(infile)
+ infile.seek(0)
+ # reorder tables alphabetically
+ outfile = BytesIO()
+ reader = ttLib.sfnt.SFNTReader(infile)
+ writer = ttLib.sfnt.SFNTWriter(
+ outfile,
+ len(reader.tables),
+ reader.sfntVersion,
+ reader.flavor,
+ reader.flavorData,
+ )
+ for tag in sorted(reader.keys()):
+ if tag in woff2TransformedTableTags + ("head",):
+ writer[tag] = normalise_table(font, tag, padding)
+ else:
+ writer[tag] = reader[tag]
+ writer.close()
+ # restore font attributes
+ font["DSIG"] = DSIG_copy
+ font.flavor = origFlavor
+ font.recalcBBoxes = origRecalcBBoxes
+ font.recalcTimestamp = origRecalcTimestamp
+ font.lazy = origLazy
+ return outfile.getvalue()
class WOFF2DirectoryEntryTest(unittest.TestCase):
-
- def setUp(self):
- self.entry = WOFF2DirectoryEntry()
-
- def test_not_enough_data_table_flags(self):
- with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'flags'"):
- self.entry.fromString(b"")
-
- def test_not_enough_data_table_tag(self):
- incompleteData = bytearray([0x3F, 0, 0, 0])
- with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'tag'"):
- self.entry.fromString(bytes(incompleteData))
-
- def test_loca_zero_transformLength(self):
- data = bytechr(getKnownTagIndex('loca')) # flags
- data += packBase128(random.randint(1, 100)) # origLength
- data += packBase128(1) # non-zero transformLength
- with self.assertRaisesRegex(
- ttLib.TTLibError, "transformLength of the 'loca' table must be 0"):
- self.entry.fromString(data)
-
- def test_fromFile(self):
- unknownTag = Tag('ZZZZ')
- data = bytechr(getKnownTagIndex(unknownTag))
- data += unknownTag.tobytes()
- data += packBase128(random.randint(1, 100))
- expectedPos = len(data)
- f = BytesIO(data + b'\0'*100)
- self.entry.fromFile(f)
- self.assertEqual(f.tell(), expectedPos)
-
- def test_transformed_toString(self):
- self.entry.tag = Tag('glyf')
- self.entry.flags = getKnownTagIndex(self.entry.tag)
- self.entry.origLength = random.randint(101, 200)
- self.entry.length = random.randint(1, 100)
- expectedSize = (woff2FlagsSize + base128Size(self.entry.origLength) +
- base128Size(self.entry.length))
- data = self.entry.toString()
- self.assertEqual(len(data), expectedSize)
-
- def test_known_toString(self):
- self.entry.tag = Tag('head')
- self.entry.flags = getKnownTagIndex(self.entry.tag)
- self.entry.origLength = 54
- expectedSize = (woff2FlagsSize + base128Size(self.entry.origLength))
- data = self.entry.toString()
- self.assertEqual(len(data), expectedSize)
-
- def test_unknown_toString(self):
- self.entry.tag = Tag('ZZZZ')
- self.entry.flags = woff2UnknownTagIndex
- self.entry.origLength = random.randint(1, 100)
- expectedSize = (woff2FlagsSize + woff2UnknownTagSize +
- base128Size(self.entry.origLength))
- data = self.entry.toString()
- self.assertEqual(len(data), expectedSize)
-
- def test_glyf_loca_transform_flags(self):
- for tag in ("glyf", "loca"):
- entry = WOFF2DirectoryEntry()
- entry.tag = Tag(tag)
- entry.flags = getKnownTagIndex(entry.tag)
-
- self.assertEqual(entry.transformVersion, 0)
- self.assertTrue(entry.transformed)
-
- entry.transformed = False
-
- self.assertEqual(entry.transformVersion, 3)
- self.assertEqual(entry.flags & 0b11000000, (3 << 6))
- self.assertFalse(entry.transformed)
-
- def test_other_transform_flags(self):
- entry = WOFF2DirectoryEntry()
- entry.tag = Tag('ZZZZ')
- entry.flags = woff2UnknownTagIndex
-
- self.assertEqual(entry.transformVersion, 0)
- self.assertFalse(entry.transformed)
-
- entry.transformed = True
-
- self.assertEqual(entry.transformVersion, 1)
- self.assertEqual(entry.flags & 0b11000000, (1 << 6))
- self.assertTrue(entry.transformed)
+ def setUp(self):
+ self.entry = WOFF2DirectoryEntry()
+
+ def test_not_enough_data_table_flags(self):
+ with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'flags'"):
+ self.entry.fromString(b"")
+
+ def test_not_enough_data_table_tag(self):
+ incompleteData = bytearray([0x3F, 0, 0, 0])
+ with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'tag'"):
+ self.entry.fromString(bytes(incompleteData))
+
+ def test_loca_zero_transformLength(self):
+ data = bytechr(getKnownTagIndex("loca")) # flags
+ data += packBase128(random.randint(1, 100)) # origLength
+ data += packBase128(1) # non-zero transformLength
+ with self.assertRaisesRegex(
+ ttLib.TTLibError, "transformLength of the 'loca' table must be 0"
+ ):
+ self.entry.fromString(data)
+
+ def test_fromFile(self):
+ unknownTag = Tag("ZZZZ")
+ data = bytechr(getKnownTagIndex(unknownTag))
+ data += unknownTag.tobytes()
+ data += packBase128(random.randint(1, 100))
+ expectedPos = len(data)
+ f = BytesIO(data + b"\0" * 100)
+ self.entry.fromFile(f)
+ self.assertEqual(f.tell(), expectedPos)
+
+ def test_transformed_toString(self):
+ self.entry.tag = Tag("glyf")
+ self.entry.flags = getKnownTagIndex(self.entry.tag)
+ self.entry.origLength = random.randint(101, 200)
+ self.entry.length = random.randint(1, 100)
+ expectedSize = (
+ woff2FlagsSize
+ + base128Size(self.entry.origLength)
+ + base128Size(self.entry.length)
+ )
+ data = self.entry.toString()
+ self.assertEqual(len(data), expectedSize)
+
+ def test_known_toString(self):
+ self.entry.tag = Tag("head")
+ self.entry.flags = getKnownTagIndex(self.entry.tag)
+ self.entry.origLength = 54
+ expectedSize = woff2FlagsSize + base128Size(self.entry.origLength)
+ data = self.entry.toString()
+ self.assertEqual(len(data), expectedSize)
+
+ def test_unknown_toString(self):
+ self.entry.tag = Tag("ZZZZ")
+ self.entry.flags = woff2UnknownTagIndex
+ self.entry.origLength = random.randint(1, 100)
+ expectedSize = (
+ woff2FlagsSize + woff2UnknownTagSize + base128Size(self.entry.origLength)
+ )
+ data = self.entry.toString()
+ self.assertEqual(len(data), expectedSize)
+
+ def test_glyf_loca_transform_flags(self):
+ for tag in ("glyf", "loca"):
+ entry = WOFF2DirectoryEntry()
+ entry.tag = Tag(tag)
+ entry.flags = getKnownTagIndex(entry.tag)
+
+ self.assertEqual(entry.transformVersion, 0)
+ self.assertTrue(entry.transformed)
+
+ entry.transformed = False
+
+ self.assertEqual(entry.transformVersion, 3)
+ self.assertEqual(entry.flags & 0b11000000, (3 << 6))
+ self.assertFalse(entry.transformed)
+
+ def test_other_transform_flags(self):
+ entry = WOFF2DirectoryEntry()
+ entry.tag = Tag("ZZZZ")
+ entry.flags = woff2UnknownTagIndex
+
+ self.assertEqual(entry.transformVersion, 0)
+ self.assertFalse(entry.transformed)
+
+ entry.transformed = True
+
+ self.assertEqual(entry.transformVersion, 1)
+ self.assertEqual(entry.flags & 0b11000000, (1 << 6))
+ self.assertTrue(entry.transformed)
class DummyReader(WOFF2Reader):
-
- def __init__(self, file, checkChecksums=1, fontNumber=-1):
- self.file = file
- for attr in ('majorVersion', 'minorVersion', 'metaOffset', 'metaLength',
- 'metaOrigLength', 'privLength', 'privOffset'):
- setattr(self, attr, 0)
- self.tables = {}
+ def __init__(self, file, checkChecksums=1, fontNumber=-1):
+ self.file = file
+ for attr in (
+ "majorVersion",
+ "minorVersion",
+ "metaOffset",
+ "metaLength",
+ "metaOrigLength",
+ "privLength",
+ "privOffset",
+ ):
+ setattr(self, attr, 0)
+ self.tables = {}
class WOFF2FlavorDataTest(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- assert os.path.exists(METADATA)
- with open(METADATA, 'rb') as f:
- cls.xml_metadata = f.read()
- cls.compressed_metadata = brotli.compress(cls.xml_metadata, mode=brotli.MODE_TEXT)
- # make random byte strings; font data must be 4-byte aligned
- cls.fontdata = bytes(bytearray(random.sample(range(0, 256), 80)))
- cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
-
- def setUp(self):
- self.file = BytesIO(self.fontdata)
- self.file.seek(0, 2)
-
- def test_get_metaData_no_privData(self):
- self.file.write(self.compressed_metadata)
- reader = DummyReader(self.file)
- reader.metaOffset = len(self.fontdata)
- reader.metaLength = len(self.compressed_metadata)
- reader.metaOrigLength = len(self.xml_metadata)
- flavorData = WOFF2FlavorData(reader)
- self.assertEqual(self.xml_metadata, flavorData.metaData)
-
- def test_get_privData_no_metaData(self):
- self.file.write(self.privData)
- reader = DummyReader(self.file)
- reader.privOffset = len(self.fontdata)
- reader.privLength = len(self.privData)
- flavorData = WOFF2FlavorData(reader)
- self.assertEqual(self.privData, flavorData.privData)
-
- def test_get_metaData_and_privData(self):
- self.file.write(self.compressed_metadata + self.privData)
- reader = DummyReader(self.file)
- reader.metaOffset = len(self.fontdata)
- reader.metaLength = len(self.compressed_metadata)
- reader.metaOrigLength = len(self.xml_metadata)
- reader.privOffset = reader.metaOffset + reader.metaLength
- reader.privLength = len(self.privData)
- flavorData = WOFF2FlavorData(reader)
- self.assertEqual(self.xml_metadata, flavorData.metaData)
- self.assertEqual(self.privData, flavorData.privData)
-
- def test_get_major_minorVersion(self):
- reader = DummyReader(self.file)
- reader.majorVersion = reader.minorVersion = 1
- flavorData = WOFF2FlavorData(reader)
- self.assertEqual(flavorData.majorVersion, 1)
- self.assertEqual(flavorData.minorVersion, 1)
-
- def test_mutually_exclusive_args(self):
- msg = "arguments are mutually exclusive"
- reader = DummyReader(self.file)
- with self.assertRaisesRegex(TypeError, msg):
- WOFF2FlavorData(reader, transformedTables={"hmtx"})
- with self.assertRaisesRegex(TypeError, msg):
- WOFF2FlavorData(reader, data=WOFF2FlavorData())
-
- def test_transformedTables_default(self):
- flavorData = WOFF2FlavorData()
- self.assertEqual(flavorData.transformedTables, set(woff2TransformedTableTags))
-
- def test_transformedTables_invalid(self):
- msg = r"'glyf' and 'loca' must be transformed \(or not\) together"
-
- with self.assertRaisesRegex(ValueError, msg):
- WOFF2FlavorData(transformedTables={"glyf"})
-
- with self.assertRaisesRegex(ValueError, msg):
- WOFF2FlavorData(transformedTables={"loca"})
+ @classmethod
+ def setUpClass(cls):
+ assert os.path.exists(METADATA)
+ with open(METADATA, "rb") as f:
+ cls.xml_metadata = f.read()
+ cls.compressed_metadata = brotli.compress(
+ cls.xml_metadata, mode=brotli.MODE_TEXT
+ )
+ # make random byte strings; font data must be 4-byte aligned
+ cls.fontdata = bytes(bytearray(random.sample(range(0, 256), 80)))
+ cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
+
+ def setUp(self):
+ self.file = BytesIO(self.fontdata)
+ self.file.seek(0, 2)
+
+ def test_get_metaData_no_privData(self):
+ self.file.write(self.compressed_metadata)
+ reader = DummyReader(self.file)
+ reader.metaOffset = len(self.fontdata)
+ reader.metaLength = len(self.compressed_metadata)
+ reader.metaOrigLength = len(self.xml_metadata)
+ flavorData = WOFF2FlavorData(reader)
+ self.assertEqual(self.xml_metadata, flavorData.metaData)
+
+ def test_get_privData_no_metaData(self):
+ self.file.write(self.privData)
+ reader = DummyReader(self.file)
+ reader.privOffset = len(self.fontdata)
+ reader.privLength = len(self.privData)
+ flavorData = WOFF2FlavorData(reader)
+ self.assertEqual(self.privData, flavorData.privData)
+
+ def test_get_metaData_and_privData(self):
+ self.file.write(self.compressed_metadata + self.privData)
+ reader = DummyReader(self.file)
+ reader.metaOffset = len(self.fontdata)
+ reader.metaLength = len(self.compressed_metadata)
+ reader.metaOrigLength = len(self.xml_metadata)
+ reader.privOffset = reader.metaOffset + reader.metaLength
+ reader.privLength = len(self.privData)
+ flavorData = WOFF2FlavorData(reader)
+ self.assertEqual(self.xml_metadata, flavorData.metaData)
+ self.assertEqual(self.privData, flavorData.privData)
+
+ def test_get_major_minorVersion(self):
+ reader = DummyReader(self.file)
+ reader.majorVersion = reader.minorVersion = 1
+ flavorData = WOFF2FlavorData(reader)
+ self.assertEqual(flavorData.majorVersion, 1)
+ self.assertEqual(flavorData.minorVersion, 1)
+
+ def test_mutually_exclusive_args(self):
+ msg = "arguments are mutually exclusive"
+ reader = DummyReader(self.file)
+ with self.assertRaisesRegex(TypeError, msg):
+ WOFF2FlavorData(reader, transformedTables={"hmtx"})
+ with self.assertRaisesRegex(TypeError, msg):
+ WOFF2FlavorData(reader, data=WOFF2FlavorData())
+
+ def test_transformedTables_default(self):
+ flavorData = WOFF2FlavorData()
+ self.assertEqual(flavorData.transformedTables, set(woff2TransformedTableTags))
+
+ def test_transformedTables_invalid(self):
+ msg = r"'glyf' and 'loca' must be transformed \(or not\) together"
+
+ with self.assertRaisesRegex(ValueError, msg):
+ WOFF2FlavorData(transformedTables={"glyf"})
+
+ with self.assertRaisesRegex(ValueError, msg):
+ WOFF2FlavorData(transformedTables={"loca"})
class WOFF2WriterTest(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls.font = ttLib.TTFont(
+ recalcBBoxes=False, recalcTimestamp=False, flavor="woff2"
+ )
+ cls.font.importXML(OTX)
+ cls.tags = sorted(t for t in cls.font.keys() if t != "GlyphOrder")
+ cls.numTables = len(cls.tags)
+ cls.file = BytesIO(CFF_WOFF2.getvalue())
+ cls.file.seek(0, 2)
+ cls.length = (cls.file.tell() + 3) & ~3
+ cls.setUpFlavorData()
+
+ @classmethod
+ def setUpFlavorData(cls):
+ assert os.path.exists(METADATA)
+ with open(METADATA, "rb") as f:
+ cls.xml_metadata = f.read()
+ cls.compressed_metadata = brotli.compress(
+ cls.xml_metadata, mode=brotli.MODE_TEXT
+ )
+ cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
+
+ def setUp(self):
+ self.file.seek(0)
+ self.writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
+
+ def test_DSIG_dropped(self):
+ self.writer["DSIG"] = b"\0"
+ self.assertEqual(len(self.writer.tables), 0)
+ self.assertEqual(self.writer.numTables, self.numTables - 1)
+
+ def test_no_rewrite_table(self):
+ self.writer["ZZZZ"] = b"\0"
+ with self.assertRaisesRegex(ttLib.TTLibError, "cannot rewrite"):
+ self.writer["ZZZZ"] = b"\0"
+
+ def test_num_tables(self):
+ self.writer["ABCD"] = b"\0"
+ with self.assertRaisesRegex(ttLib.TTLibError, "wrong number of tables"):
+ self.writer.close()
+
+ def test_required_tables(self):
+ font = ttLib.TTFont(flavor="woff2")
+ with self.assertRaisesRegex(ttLib.TTLibError, "missing required table"):
+ font.save(BytesIO())
+
+ def test_head_transform_flag(self):
+ headData = self.font.getTableData("head")
+ origFlags = byteord(headData[16])
+ woff2font = ttLib.TTFont(self.file)
+ newHeadData = woff2font.getTableData("head")
+ modifiedFlags = byteord(newHeadData[16])
+ self.assertNotEqual(origFlags, modifiedFlags)
+ restoredFlags = modifiedFlags & ~0x08 # turn off bit 11
+ self.assertEqual(origFlags, restoredFlags)
+
+ def test_tables_sorted_alphabetically(self):
+ expected = sorted([t for t in self.tags if t != "DSIG"])
+ woff2font = ttLib.TTFont(self.file)
+ self.assertEqual(expected, list(woff2font.reader.keys()))
+
+ def test_checksums(self):
+ normFile = BytesIO(normalise_font(self.font, padding=4))
+ normFile.seek(0)
+ normFont = ttLib.TTFont(normFile, checkChecksums=2)
+ w2font = ttLib.TTFont(self.file)
+ # force reconstructing glyf table using 4-byte padding
+ w2font.reader.padding = 4
+ for tag in [t for t in self.tags if t != "DSIG"]:
+ w2data = w2font.reader[tag]
+ normData = normFont.reader[tag]
+ if tag == "head":
+ w2data = w2data[:8] + b"\0\0\0\0" + w2data[12:]
+ normData = normData[:8] + b"\0\0\0\0" + normData[12:]
+ w2CheckSum = ttLib.sfnt.calcChecksum(w2data)
+ normCheckSum = ttLib.sfnt.calcChecksum(normData)
+ self.assertEqual(w2CheckSum, normCheckSum)
+ normCheckSumAdjustment = normFont["head"].checkSumAdjustment
+ self.assertEqual(normCheckSumAdjustment, w2font["head"].checkSumAdjustment)
+
+ def test_calcSFNTChecksumsLengthsAndOffsets(self):
+ normFont = ttLib.TTFont(BytesIO(normalise_font(self.font, padding=4)))
+ for tag in self.tags:
+ self.writer[tag] = self.font.getTableData(tag)
+ self.writer._normaliseGlyfAndLoca(padding=4)
+ self.writer._setHeadTransformFlag()
+ self.writer.tables = OrderedDict(sorted(self.writer.tables.items()))
+ self.writer._calcSFNTChecksumsLengthsAndOffsets()
+ for tag, entry in normFont.reader.tables.items():
+ self.assertEqual(entry.offset, self.writer.tables[tag].origOffset)
+ self.assertEqual(entry.length, self.writer.tables[tag].origLength)
+ self.assertEqual(entry.checkSum, self.writer.tables[tag].checkSum)
+
+ def test_bad_sfntVersion(self):
+ for i in range(self.numTables):
+ self.writer[bytechr(65 + i) * 4] = b"\0"
+ self.writer.sfntVersion = "ZZZZ"
+ with self.assertRaisesRegex(ttLib.TTLibError, "bad sfntVersion"):
+ self.writer.close()
+
+ def test_calcTotalSize_no_flavorData(self):
+ expected = self.length
+ self.writer.file = BytesIO()
+ for tag in self.tags:
+ self.writer[tag] = self.font.getTableData(tag)
+ self.writer.close()
+ self.assertEqual(expected, self.writer.length)
+ self.assertEqual(expected, self.writer.file.tell())
+
+ def test_calcTotalSize_with_metaData(self):
+ expected = self.length + len(self.compressed_metadata)
+ flavorData = self.writer.flavorData = WOFF2FlavorData()
+ flavorData.metaData = self.xml_metadata
+ self.writer.file = BytesIO()
+ for tag in self.tags:
+ self.writer[tag] = self.font.getTableData(tag)
+ self.writer.close()
+ self.assertEqual(expected, self.writer.length)
+ self.assertEqual(expected, self.writer.file.tell())
+
+ def test_calcTotalSize_with_privData(self):
+ expected = self.length + len(self.privData)
+ flavorData = self.writer.flavorData = WOFF2FlavorData()
+ flavorData.privData = self.privData
+ self.writer.file = BytesIO()
+ for tag in self.tags:
+ self.writer[tag] = self.font.getTableData(tag)
+ self.writer.close()
+ self.assertEqual(expected, self.writer.length)
+ self.assertEqual(expected, self.writer.file.tell())
+
+ def test_calcTotalSize_with_metaData_and_privData(self):
+ metaDataLength = (len(self.compressed_metadata) + 3) & ~3
+ expected = self.length + metaDataLength + len(self.privData)
+ flavorData = self.writer.flavorData = WOFF2FlavorData()
+ flavorData.metaData = self.xml_metadata
+ flavorData.privData = self.privData
+ self.writer.file = BytesIO()
+ for tag in self.tags:
+ self.writer[tag] = self.font.getTableData(tag)
+ self.writer.close()
+ self.assertEqual(expected, self.writer.length)
+ self.assertEqual(expected, self.writer.file.tell())
+
+ def test_getVersion(self):
+ # no version
+ self.assertEqual((0, 0), self.writer._getVersion())
+ # version from head.fontRevision
+ fontRevision = self.font["head"].fontRevision
+ versionTuple = tuple(int(i) for i in str(fontRevision).split("."))
+ entry = self.writer.tables["head"] = ttLib.newTable("head")
+ entry.data = self.font.getTableData("head")
+ self.assertEqual(versionTuple, self.writer._getVersion())
+ # version from writer.flavorData
+ flavorData = self.writer.flavorData = WOFF2FlavorData()
+ flavorData.majorVersion, flavorData.minorVersion = (10, 11)
+ self.assertEqual((10, 11), self.writer._getVersion())
+
+ def test_hmtx_trasform(self):
+ tableTransforms = {"glyf", "loca", "hmtx"}
+
+ writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
+ writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
+
+ for tag in self.tags:
+ writer[tag] = self.font.getTableData(tag)
+ writer.close()
+
+ # enabling hmtx transform has no effect when font has no glyf table
+ self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
+
+ def test_no_transforms(self):
+ writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
+ writer.flavorData = WOFF2FlavorData(transformedTables=())
+
+ for tag in self.tags:
+ writer[tag] = self.font.getTableData(tag)
+ writer.close()
+
+ # transforms settings have no effect when font is CFF-flavored, since
+ # all the current transforms only apply to TrueType-flavored fonts.
+ self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
- @classmethod
- def setUpClass(cls):
- cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False, flavor="woff2")
- cls.font.importXML(OTX)
- cls.tags = sorted(t for t in cls.font.keys() if t != 'GlyphOrder')
- cls.numTables = len(cls.tags)
- cls.file = BytesIO(CFF_WOFF2.getvalue())
- cls.file.seek(0, 2)
- cls.length = (cls.file.tell() + 3) & ~3
- cls.setUpFlavorData()
-
- @classmethod
- def setUpFlavorData(cls):
- assert os.path.exists(METADATA)
- with open(METADATA, 'rb') as f:
- cls.xml_metadata = f.read()
- cls.compressed_metadata = brotli.compress(cls.xml_metadata, mode=brotli.MODE_TEXT)
- cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
-
- def setUp(self):
- self.file.seek(0)
- self.writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
-
- def test_DSIG_dropped(self):
- self.writer['DSIG'] = b"\0"
- self.assertEqual(len(self.writer.tables), 0)
- self.assertEqual(self.writer.numTables, self.numTables-1)
-
- def test_no_rewrite_table(self):
- self.writer['ZZZZ'] = b"\0"
- with self.assertRaisesRegex(ttLib.TTLibError, "cannot rewrite"):
- self.writer['ZZZZ'] = b"\0"
-
- def test_num_tables(self):
- self.writer['ABCD'] = b"\0"
- with self.assertRaisesRegex(ttLib.TTLibError, "wrong number of tables"):
- self.writer.close()
-
- def test_required_tables(self):
- font = ttLib.TTFont(flavor="woff2")
- with self.assertRaisesRegex(ttLib.TTLibError, "missing required table"):
- font.save(BytesIO())
-
- def test_head_transform_flag(self):
- headData = self.font.getTableData('head')
- origFlags = byteord(headData[16])
- woff2font = ttLib.TTFont(self.file)
- newHeadData = woff2font.getTableData('head')
- modifiedFlags = byteord(newHeadData[16])
- self.assertNotEqual(origFlags, modifiedFlags)
- restoredFlags = modifiedFlags & ~0x08 # turn off bit 11
- self.assertEqual(origFlags, restoredFlags)
-
- def test_tables_sorted_alphabetically(self):
- expected = sorted([t for t in self.tags if t != 'DSIG'])
- woff2font = ttLib.TTFont(self.file)
- self.assertEqual(expected, list(woff2font.reader.keys()))
-
- def test_checksums(self):
- normFile = BytesIO(normalise_font(self.font, padding=4))
- normFile.seek(0)
- normFont = ttLib.TTFont(normFile, checkChecksums=2)
- w2font = ttLib.TTFont(self.file)
- # force reconstructing glyf table using 4-byte padding
- w2font.reader.padding = 4
- for tag in [t for t in self.tags if t != 'DSIG']:
- w2data = w2font.reader[tag]
- normData = normFont.reader[tag]
- if tag == "head":
- w2data = w2data[:8] + b'\0\0\0\0' + w2data[12:]
- normData = normData[:8] + b'\0\0\0\0' + normData[12:]
- w2CheckSum = ttLib.sfnt.calcChecksum(w2data)
- normCheckSum = ttLib.sfnt.calcChecksum(normData)
- self.assertEqual(w2CheckSum, normCheckSum)
- normCheckSumAdjustment = normFont['head'].checkSumAdjustment
- self.assertEqual(normCheckSumAdjustment, w2font['head'].checkSumAdjustment)
-
- def test_calcSFNTChecksumsLengthsAndOffsets(self):
- normFont = ttLib.TTFont(BytesIO(normalise_font(self.font, padding=4)))
- for tag in self.tags:
- self.writer[tag] = self.font.getTableData(tag)
- self.writer._normaliseGlyfAndLoca(padding=4)
- self.writer._setHeadTransformFlag()
- self.writer.tables = OrderedDict(sorted(self.writer.tables.items()))
- self.writer._calcSFNTChecksumsLengthsAndOffsets()
- for tag, entry in normFont.reader.tables.items():
- self.assertEqual(entry.offset, self.writer.tables[tag].origOffset)
- self.assertEqual(entry.length, self.writer.tables[tag].origLength)
- self.assertEqual(entry.checkSum, self.writer.tables[tag].checkSum)
-
- def test_bad_sfntVersion(self):
- for i in range(self.numTables):
- self.writer[bytechr(65 + i)*4] = b"\0"
- self.writer.sfntVersion = 'ZZZZ'
- with self.assertRaisesRegex(ttLib.TTLibError, "bad sfntVersion"):
- self.writer.close()
-
- def test_calcTotalSize_no_flavorData(self):
- expected = self.length
- self.writer.file = BytesIO()
- for tag in self.tags:
- self.writer[tag] = self.font.getTableData(tag)
- self.writer.close()
- self.assertEqual(expected, self.writer.length)
- self.assertEqual(expected, self.writer.file.tell())
-
- def test_calcTotalSize_with_metaData(self):
- expected = self.length + len(self.compressed_metadata)
- flavorData = self.writer.flavorData = WOFF2FlavorData()
- flavorData.metaData = self.xml_metadata
- self.writer.file = BytesIO()
- for tag in self.tags:
- self.writer[tag] = self.font.getTableData(tag)
- self.writer.close()
- self.assertEqual(expected, self.writer.length)
- self.assertEqual(expected, self.writer.file.tell())
-
- def test_calcTotalSize_with_privData(self):
- expected = self.length + len(self.privData)
- flavorData = self.writer.flavorData = WOFF2FlavorData()
- flavorData.privData = self.privData
- self.writer.file = BytesIO()
- for tag in self.tags:
- self.writer[tag] = self.font.getTableData(tag)
- self.writer.close()
- self.assertEqual(expected, self.writer.length)
- self.assertEqual(expected, self.writer.file.tell())
-
- def test_calcTotalSize_with_metaData_and_privData(self):
- metaDataLength = (len(self.compressed_metadata) + 3) & ~3
- expected = self.length + metaDataLength + len(self.privData)
- flavorData = self.writer.flavorData = WOFF2FlavorData()
- flavorData.metaData = self.xml_metadata
- flavorData.privData = self.privData
- self.writer.file = BytesIO()
- for tag in self.tags:
- self.writer[tag] = self.font.getTableData(tag)
- self.writer.close()
- self.assertEqual(expected, self.writer.length)
- self.assertEqual(expected, self.writer.file.tell())
-
- def test_getVersion(self):
- # no version
- self.assertEqual((0, 0), self.writer._getVersion())
- # version from head.fontRevision
- fontRevision = self.font['head'].fontRevision
- versionTuple = tuple(int(i) for i in str(fontRevision).split("."))
- entry = self.writer.tables['head'] = ttLib.newTable('head')
- entry.data = self.font.getTableData('head')
- self.assertEqual(versionTuple, self.writer._getVersion())
- # version from writer.flavorData
- flavorData = self.writer.flavorData = WOFF2FlavorData()
- flavorData.majorVersion, flavorData.minorVersion = (10, 11)
- self.assertEqual((10, 11), self.writer._getVersion())
-
- def test_hmtx_trasform(self):
- tableTransforms = {"glyf", "loca", "hmtx"}
-
- writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
- writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
-
- for tag in self.tags:
- writer[tag] = self.font.getTableData(tag)
- writer.close()
-
- # enabling hmtx transform has no effect when font has no glyf table
- self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
-
- def test_no_transforms(self):
- writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
- writer.flavorData = WOFF2FlavorData(transformedTables=())
-
- for tag in self.tags:
- writer[tag] = self.font.getTableData(tag)
- writer.close()
-
- # transforms settings have no effect when font is CFF-flavored, since
- # all the current transforms only apply to TrueType-flavored fonts.
- self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
class WOFF2WriterTTFTest(WOFF2WriterTest):
-
- @classmethod
- def setUpClass(cls):
- cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False, flavor="woff2")
- cls.font.importXML(TTX)
- cls.tags = sorted(t for t in cls.font.keys() if t != 'GlyphOrder')
- cls.numTables = len(cls.tags)
- cls.file = BytesIO(TT_WOFF2.getvalue())
- cls.file.seek(0, 2)
- cls.length = (cls.file.tell() + 3) & ~3
- cls.setUpFlavorData()
-
- def test_normaliseGlyfAndLoca(self):
- normTables = {}
- for tag in ('head', 'loca', 'glyf'):
- normTables[tag] = normalise_table(self.font, tag, padding=4)
- for tag in self.tags:
- tableData = self.font.getTableData(tag)
- self.writer[tag] = tableData
- if tag in normTables:
- self.assertNotEqual(tableData, normTables[tag])
- self.writer._normaliseGlyfAndLoca(padding=4)
- self.writer._setHeadTransformFlag()
- for tag in normTables:
- self.assertEqual(self.writer.tables[tag].data, normTables[tag])
-
- def test_hmtx_trasform(self):
- tableTransforms = {"glyf", "loca", "hmtx"}
-
- writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
- writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
-
- for tag in self.tags:
- writer[tag] = self.font.getTableData(tag)
- writer.close()
-
- length = len(writer.file.getvalue())
-
- # enabling optional hmtx transform shaves off a few bytes
- self.assertLess(length, len(TT_WOFF2.getvalue()))
-
- def test_no_transforms(self):
- writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
- writer.flavorData = WOFF2FlavorData(transformedTables=())
-
- for tag in self.tags:
- writer[tag] = self.font.getTableData(tag)
- writer.close()
-
- self.assertNotEqual(writer.file.getvalue(), TT_WOFF2.getvalue())
-
- writer.file.seek(0)
- reader = WOFF2Reader(writer.file)
- self.assertEqual(len(reader.flavorData.transformedTables), 0)
+ @classmethod
+ def setUpClass(cls):
+ cls.font = ttLib.TTFont(
+ recalcBBoxes=False, recalcTimestamp=False, flavor="woff2"
+ )
+ cls.font.importXML(TTX)
+ cls.tags = sorted(t for t in cls.font.keys() if t != "GlyphOrder")
+ cls.numTables = len(cls.tags)
+ cls.file = BytesIO(TT_WOFF2.getvalue())
+ cls.file.seek(0, 2)
+ cls.length = (cls.file.tell() + 3) & ~3
+ cls.setUpFlavorData()
+
+ def test_normaliseGlyfAndLoca(self):
+ normTables = {}
+ for tag in ("head", "loca", "glyf"):
+ normTables[tag] = normalise_table(self.font, tag, padding=4)
+ for tag in self.tags:
+ tableData = self.font.getTableData(tag)
+ self.writer[tag] = tableData
+ if tag in normTables:
+ self.assertNotEqual(tableData, normTables[tag])
+ self.writer._normaliseGlyfAndLoca(padding=4)
+ self.writer._setHeadTransformFlag()
+ for tag in normTables:
+ self.assertEqual(self.writer.tables[tag].data, normTables[tag])
+
+ def test_hmtx_trasform(self):
+ def compile_hmtx(compressed):
+ tableTransforms = woff2TransformedTableTags
+ if compressed:
+ tableTransforms += ("hmtx",)
+ writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
+ writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
+ for tag in self.tags:
+ writer[tag] = self.font.getTableData(tag)
+ writer.close()
+ return writer.tables["hmtx"].length
+
+ uncompressed_length = compile_hmtx(compressed=False)
+ compressed_length = compile_hmtx(compressed=True)
+
+ # enabling optional hmtx transform shaves off a few bytes
+ self.assertLess(compressed_length, uncompressed_length)
+
+ def test_no_transforms(self):
+ writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
+ writer.flavorData = WOFF2FlavorData(transformedTables=())
+
+ for tag in self.tags:
+ writer[tag] = self.font.getTableData(tag)
+ writer.close()
+
+ self.assertNotEqual(writer.file.getvalue(), TT_WOFF2.getvalue())
+
+ writer.file.seek(0)
+ reader = WOFF2Reader(writer.file)
+ self.assertEqual(len(reader.flavorData.transformedTables), 0)
class WOFF2LocaTableTest(unittest.TestCase):
-
- def setUp(self):
- self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- font['head'] = ttLib.newTable('head')
- font['loca'] = WOFF2LocaTable()
- font['glyf'] = WOFF2GlyfTable()
-
- def test_compile_short_loca(self):
- locaTable = self.font['loca']
- locaTable.set(list(range(0, 0x20000, 2)))
- self.font['glyf'].indexFormat = 0
- locaData = locaTable.compile(self.font)
- self.assertEqual(len(locaData), 0x20000)
-
- def test_compile_short_loca_overflow(self):
- locaTable = self.font['loca']
- locaTable.set(list(range(0x20000 + 1)))
- self.font['glyf'].indexFormat = 0
- with self.assertRaisesRegex(
- ttLib.TTLibError, "indexFormat is 0 but local offsets > 0x20000"):
- locaTable.compile(self.font)
-
- def test_compile_short_loca_not_multiples_of_2(self):
- locaTable = self.font['loca']
- locaTable.set([1, 3, 5, 7])
- self.font['glyf'].indexFormat = 0
- with self.assertRaisesRegex(ttLib.TTLibError, "offsets not multiples of 2"):
- locaTable.compile(self.font)
-
- def test_compile_long_loca(self):
- locaTable = self.font['loca']
- locaTable.set(list(range(0x20001)))
- self.font['glyf'].indexFormat = 1
- locaData = locaTable.compile(self.font)
- self.assertEqual(len(locaData), 0x20001 * 4)
-
- def test_compile_set_indexToLocFormat_0(self):
- locaTable = self.font['loca']
- # offsets are all multiples of 2 and max length is < 0x10000
- locaTable.set(list(range(0, 0x20000, 2)))
- locaTable.compile(self.font)
- newIndexFormat = self.font['head'].indexToLocFormat
- self.assertEqual(0, newIndexFormat)
-
- def test_compile_set_indexToLocFormat_1(self):
- locaTable = self.font['loca']
- # offsets are not multiples of 2
- locaTable.set(list(range(10)))
- locaTable.compile(self.font)
- newIndexFormat = self.font['head'].indexToLocFormat
- self.assertEqual(1, newIndexFormat)
- # max length is >= 0x10000
- locaTable.set(list(range(0, 0x20000 + 1, 2)))
- locaTable.compile(self.font)
- newIndexFormat = self.font['head'].indexToLocFormat
- self.assertEqual(1, newIndexFormat)
+ def setUp(self):
+ self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ font["head"] = ttLib.newTable("head")
+ font["loca"] = WOFF2LocaTable()
+ font["glyf"] = WOFF2GlyfTable()
+
+ def test_compile_short_loca(self):
+ locaTable = self.font["loca"]
+ locaTable.set(list(range(0, 0x20000, 2)))
+ self.font["glyf"].indexFormat = 0
+ locaData = locaTable.compile(self.font)
+ self.assertEqual(len(locaData), 0x20000)
+
+ def test_compile_short_loca_overflow(self):
+ locaTable = self.font["loca"]
+ locaTable.set(list(range(0x20000 + 1)))
+ self.font["glyf"].indexFormat = 0
+ with self.assertRaisesRegex(
+ ttLib.TTLibError, "indexFormat is 0 but local offsets > 0x20000"
+ ):
+ locaTable.compile(self.font)
+
+ def test_compile_short_loca_not_multiples_of_2(self):
+ locaTable = self.font["loca"]
+ locaTable.set([1, 3, 5, 7])
+ self.font["glyf"].indexFormat = 0
+ with self.assertRaisesRegex(ttLib.TTLibError, "offsets not multiples of 2"):
+ locaTable.compile(self.font)
+
+ def test_compile_long_loca(self):
+ locaTable = self.font["loca"]
+ locaTable.set(list(range(0x20001)))
+ self.font["glyf"].indexFormat = 1
+ locaData = locaTable.compile(self.font)
+ self.assertEqual(len(locaData), 0x20001 * 4)
+
+ def test_compile_set_indexToLocFormat_0(self):
+ locaTable = self.font["loca"]
+ # offsets are all multiples of 2 and max length is < 0x10000
+ locaTable.set(list(range(0, 0x20000, 2)))
+ locaTable.compile(self.font)
+ newIndexFormat = self.font["head"].indexToLocFormat
+ self.assertEqual(0, newIndexFormat)
+
+ def test_compile_set_indexToLocFormat_1(self):
+ locaTable = self.font["loca"]
+ # offsets are not multiples of 2
+ locaTable.set(list(range(10)))
+ locaTable.compile(self.font)
+ newIndexFormat = self.font["head"].indexToLocFormat
+ self.assertEqual(1, newIndexFormat)
+ # max length is >= 0x10000
+ locaTable.set(list(range(0, 0x20000 + 1, 2)))
+ locaTable.compile(self.font)
+ newIndexFormat = self.font["head"].indexToLocFormat
+ self.assertEqual(1, newIndexFormat)
class WOFF2GlyfTableTest(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- font.importXML(TTX)
- cls.tables = {}
- cls.transformedTags = ('maxp', 'head', 'loca', 'glyf')
- for tag in reversed(cls.transformedTags): # compile in inverse order
- cls.tables[tag] = font.getTableData(tag)
- infile = BytesIO(TT_WOFF2.getvalue())
- reader = WOFF2Reader(infile)
- cls.transformedGlyfData = reader.tables['glyf'].loadData(
- reader.transformBuffer)
- cls.glyphOrder = ['.notdef'] + ["glyph%.5d" % i for i in range(1, font['maxp'].numGlyphs)]
-
- def setUp(self):
- self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- font.setGlyphOrder(self.glyphOrder)
- font['head'] = ttLib.newTable('head')
- font['maxp'] = ttLib.newTable('maxp')
- font['loca'] = WOFF2LocaTable()
- font['glyf'] = WOFF2GlyfTable()
- for tag in self.transformedTags:
- font[tag].decompile(self.tables[tag], font)
-
- def test_reconstruct_glyf_padded_4(self):
- glyfTable = WOFF2GlyfTable()
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- glyfTable.padding = 4
- data = glyfTable.compile(self.font)
- normGlyfData = normalise_table(self.font, 'glyf', glyfTable.padding)
- self.assertEqual(normGlyfData, data)
-
- def test_reconstruct_glyf_padded_2(self):
- glyfTable = WOFF2GlyfTable()
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- glyfTable.padding = 2
- data = glyfTable.compile(self.font)
- normGlyfData = normalise_table(self.font, 'glyf', glyfTable.padding)
- self.assertEqual(normGlyfData, data)
-
- def test_reconstruct_glyf_unpadded(self):
- glyfTable = WOFF2GlyfTable()
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- data = glyfTable.compile(self.font)
- self.assertEqual(self.tables['glyf'], data)
-
- def test_reconstruct_glyf_incorrect_glyphOrder(self):
- glyfTable = WOFF2GlyfTable()
- badGlyphOrder = self.font.getGlyphOrder()[:-1]
- self.font.setGlyphOrder(badGlyphOrder)
- with self.assertRaisesRegex(ttLib.TTLibError, "incorrect glyphOrder"):
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
-
- def test_reconstruct_glyf_missing_glyphOrder(self):
- glyfTable = WOFF2GlyfTable()
- del self.font.glyphOrder
- numGlyphs = self.font['maxp'].numGlyphs
- del self.font['maxp']
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- expected = [".notdef"]
- expected.extend(["glyph%.5d" % i for i in range(1, numGlyphs)])
- self.assertEqual(expected, glyfTable.glyphOrder)
-
- def test_reconstruct_loca_padded_4(self):
- locaTable = self.font['loca'] = WOFF2LocaTable()
- glyfTable = self.font['glyf'] = WOFF2GlyfTable()
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- glyfTable.padding = 4
- glyfTable.compile(self.font)
- data = locaTable.compile(self.font)
- normLocaData = normalise_table(self.font, 'loca', glyfTable.padding)
- self.assertEqual(normLocaData, data)
-
- def test_reconstruct_loca_padded_2(self):
- locaTable = self.font['loca'] = WOFF2LocaTable()
- glyfTable = self.font['glyf'] = WOFF2GlyfTable()
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- glyfTable.padding = 2
- glyfTable.compile(self.font)
- data = locaTable.compile(self.font)
- normLocaData = normalise_table(self.font, 'loca', glyfTable.padding)
- self.assertEqual(normLocaData, data)
-
- def test_reconstruct_loca_unpadded(self):
- locaTable = self.font['loca'] = WOFF2LocaTable()
- glyfTable = self.font['glyf'] = WOFF2GlyfTable()
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- glyfTable.compile(self.font)
- data = locaTable.compile(self.font)
- self.assertEqual(self.tables['loca'], data)
-
- def test_reconstruct_glyf_header_not_enough_data(self):
- with self.assertRaisesRegex(ttLib.TTLibError, "not enough 'glyf' data"):
- WOFF2GlyfTable().reconstruct(b"", self.font)
-
- def test_reconstruct_glyf_table_incorrect_size(self):
- msg = "incorrect size of transformed 'glyf'"
- with self.assertRaisesRegex(ttLib.TTLibError, msg):
- WOFF2GlyfTable().reconstruct(self.transformedGlyfData + b"\x00", self.font)
- with self.assertRaisesRegex(ttLib.TTLibError, msg):
- WOFF2GlyfTable().reconstruct(self.transformedGlyfData[:-1], self.font)
-
- def test_transform_glyf(self):
- glyfTable = self.font['glyf']
- data = glyfTable.transform(self.font)
- self.assertEqual(self.transformedGlyfData, data)
-
- def test_roundtrip_glyf_reconstruct_and_transform(self):
- glyfTable = WOFF2GlyfTable()
- glyfTable.reconstruct(self.transformedGlyfData, self.font)
- data = glyfTable.transform(self.font)
- self.assertEqual(self.transformedGlyfData, data)
-
- def test_roundtrip_glyf_transform_and_reconstruct(self):
- glyfTable = self.font['glyf']
- transformedData = glyfTable.transform(self.font)
- newGlyfTable = WOFF2GlyfTable()
- newGlyfTable.reconstruct(transformedData, self.font)
- newGlyfTable.padding = 4
- reconstructedData = newGlyfTable.compile(self.font)
- normGlyfData = normalise_table(self.font, 'glyf', newGlyfTable.padding)
- self.assertEqual(normGlyfData, reconstructedData)
+ @classmethod
+ def setUpClass(cls):
+ font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ font.importXML(TTX)
+ cls.tables = {}
+ cls.transformedTags = ("maxp", "head", "loca", "glyf")
+ for tag in reversed(cls.transformedTags): # compile in inverse order
+ cls.tables[tag] = font.getTableData(tag)
+ infile = BytesIO(TT_WOFF2.getvalue())
+ reader = WOFF2Reader(infile)
+ cls.transformedGlyfData = reader.tables["glyf"].loadData(reader.transformBuffer)
+ cls.glyphOrder = [".notdef"] + [
+ "glyph%.5d" % i for i in range(1, font["maxp"].numGlyphs)
+ ]
+
+ def setUp(self):
+ self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ font.setGlyphOrder(self.glyphOrder)
+ font["head"] = ttLib.newTable("head")
+ font["maxp"] = ttLib.newTable("maxp")
+ font["loca"] = WOFF2LocaTable()
+ font["glyf"] = WOFF2GlyfTable()
+ for tag in self.transformedTags:
+ font[tag].decompile(self.tables[tag], font)
+
+ def test_reconstruct_glyf_padded_4(self):
+ glyfTable = WOFF2GlyfTable()
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ glyfTable.padding = 4
+ data = glyfTable.compile(self.font)
+ normGlyfData = normalise_table(self.font, "glyf", glyfTable.padding)
+ self.assertEqual(normGlyfData, data)
+
+ def test_reconstruct_glyf_padded_2(self):
+ glyfTable = WOFF2GlyfTable()
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ glyfTable.padding = 2
+ data = glyfTable.compile(self.font)
+ normGlyfData = normalise_table(self.font, "glyf", glyfTable.padding)
+ self.assertEqual(normGlyfData, data)
+
+ def test_reconstruct_glyf_unpadded(self):
+ glyfTable = WOFF2GlyfTable()
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ data = glyfTable.compile(self.font)
+ self.assertEqual(self.tables["glyf"], data)
+
+ def test_reconstruct_glyf_incorrect_glyphOrder(self):
+ glyfTable = WOFF2GlyfTable()
+ badGlyphOrder = self.font.getGlyphOrder()[:-1]
+ self.font.setGlyphOrder(badGlyphOrder)
+ with self.assertRaisesRegex(ttLib.TTLibError, "incorrect glyphOrder"):
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+
+ def test_reconstruct_glyf_missing_glyphOrder(self):
+ glyfTable = WOFF2GlyfTable()
+ del self.font.glyphOrder
+ numGlyphs = self.font["maxp"].numGlyphs
+ del self.font["maxp"]
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ expected = [".notdef"]
+ expected.extend(["glyph%.5d" % i for i in range(1, numGlyphs)])
+ self.assertEqual(expected, glyfTable.glyphOrder)
+
+ def test_reconstruct_loca_padded_4(self):
+ locaTable = self.font["loca"] = WOFF2LocaTable()
+ glyfTable = self.font["glyf"] = WOFF2GlyfTable()
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ glyfTable.padding = 4
+ glyfTable.compile(self.font)
+ data = locaTable.compile(self.font)
+ normLocaData = normalise_table(self.font, "loca", glyfTable.padding)
+ self.assertEqual(normLocaData, data)
+
+ def test_reconstruct_loca_padded_2(self):
+ locaTable = self.font["loca"] = WOFF2LocaTable()
+ glyfTable = self.font["glyf"] = WOFF2GlyfTable()
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ glyfTable.padding = 2
+ glyfTable.compile(self.font)
+ data = locaTable.compile(self.font)
+ normLocaData = normalise_table(self.font, "loca", glyfTable.padding)
+ self.assertEqual(normLocaData, data)
+
+ def test_reconstruct_loca_unpadded(self):
+ locaTable = self.font["loca"] = WOFF2LocaTable()
+ glyfTable = self.font["glyf"] = WOFF2GlyfTable()
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ glyfTable.compile(self.font)
+ data = locaTable.compile(self.font)
+ self.assertEqual(self.tables["loca"], data)
+
+ def test_reconstruct_glyf_header_not_enough_data(self):
+ with self.assertRaisesRegex(ttLib.TTLibError, "not enough 'glyf' data"):
+ WOFF2GlyfTable().reconstruct(b"", self.font)
+
+ def test_reconstruct_glyf_table_incorrect_size(self):
+ msg = "incorrect size of transformed 'glyf'"
+ with self.assertRaisesRegex(ttLib.TTLibError, msg):
+ WOFF2GlyfTable().reconstruct(self.transformedGlyfData + b"\x00", self.font)
+ with self.assertRaisesRegex(ttLib.TTLibError, msg):
+ WOFF2GlyfTable().reconstruct(self.transformedGlyfData[:-1], self.font)
+
+ def test_transform_glyf(self):
+ glyfTable = self.font["glyf"]
+ data = glyfTable.transform(self.font)
+ self.assertEqual(self.transformedGlyfData, data)
+
+ def test_roundtrip_glyf_reconstruct_and_transform(self):
+ glyfTable = WOFF2GlyfTable()
+ glyfTable.reconstruct(self.transformedGlyfData, self.font)
+ data = glyfTable.transform(self.font)
+ self.assertEqual(self.transformedGlyfData, data)
+
+ def test_roundtrip_glyf_transform_and_reconstruct(self):
+ glyfTable = self.font["glyf"]
+ transformedData = glyfTable.transform(self.font)
+ newGlyfTable = WOFF2GlyfTable()
+ newGlyfTable.reconstruct(transformedData, self.font)
+ newGlyfTable.padding = 4
+ reconstructedData = newGlyfTable.compile(self.font)
+ normGlyfData = normalise_table(self.font, "glyf", newGlyfTable.padding)
+ self.assertEqual(normGlyfData, reconstructedData)
@pytest.fixture(scope="module")
def fontfile():
-
- class Glyph(object):
- def __init__(self, empty=False, **kwargs):
- if not empty:
- self.draw = partial(self.drawRect, **kwargs)
- else:
- self.draw = lambda pen: None
-
- @staticmethod
- def drawRect(pen, xMin, xMax):
- pen.moveTo((xMin, 0))
- pen.lineTo((xMin, 1000))
- pen.lineTo((xMax, 1000))
- pen.lineTo((xMax, 0))
- pen.closePath()
-
- class CompositeGlyph(object):
- def __init__(self, components):
- self.components = components
-
- def draw(self, pen):
- for baseGlyph, (offsetX, offsetY) in self.components:
- pen.addComponent(baseGlyph, (1, 0, 0, 1, offsetX, offsetY))
-
- fb = fontBuilder.FontBuilder(unitsPerEm=1000, isTTF=True)
- fb.setupGlyphOrder(
- [".notdef", "space", "A", "acutecomb", "Aacute", "zero", "one", "two"]
- )
- fb.setupCharacterMap(
- {
- 0x20: "space",
- 0x41: "A",
- 0x0301: "acutecomb",
- 0xC1: "Aacute",
- 0x30: "zero",
- 0x31: "one",
- 0x32: "two",
- }
- )
- fb.setupHorizontalMetrics(
- {
- ".notdef": (500, 50),
- "space": (600, 0),
- "A": (550, 40),
- "acutecomb": (0, -40),
- "Aacute": (550, 40),
- "zero": (500, 30),
- "one": (500, 50),
- "two": (500, 40),
- }
- )
- fb.setupHorizontalHeader(ascent=1000, descent=-200)
-
- srcGlyphs = {
- ".notdef": Glyph(xMin=50, xMax=450),
- "space": Glyph(empty=True),
- "A": Glyph(xMin=40, xMax=510),
- "acutecomb": Glyph(xMin=-40, xMax=60),
- "Aacute": CompositeGlyph([("A", (0, 0)), ("acutecomb", (200, 0))]),
- "zero": Glyph(xMin=30, xMax=470),
- "one": Glyph(xMin=50, xMax=450),
- "two": Glyph(xMin=40, xMax=460),
- }
- pen = TTGlyphPen(srcGlyphs)
- glyphSet = {}
- for glyphName, glyph in srcGlyphs.items():
- glyph.draw(pen)
- glyphSet[glyphName] = pen.glyph()
- fb.setupGlyf(glyphSet)
-
- fb.setupNameTable(
- {
- "familyName": "TestWOFF2",
- "styleName": "Regular",
- "uniqueFontIdentifier": "TestWOFF2 Regular; Version 1.000; ABCD",
- "fullName": "TestWOFF2 Regular",
- "version": "Version 1.000",
- "psName": "TestWOFF2-Regular",
- }
- )
- fb.setupOS2()
- fb.setupPost()
-
- buf = BytesIO()
- fb.save(buf)
- buf.seek(0)
-
- assert fb.font["maxp"].numGlyphs == 8
- assert fb.font["hhea"].numberOfHMetrics == 6
- for glyphName in fb.font.getGlyphOrder():
- xMin = getattr(fb.font["glyf"][glyphName], "xMin", 0)
- assert xMin == fb.font["hmtx"][glyphName][1]
-
- return buf
+ class Glyph(object):
+ def __init__(self, empty=False, **kwargs):
+ if not empty:
+ self.draw = partial(self.drawRect, **kwargs)
+ else:
+ self.draw = lambda pen: None
+
+ @staticmethod
+ def drawRect(pen, xMin, xMax):
+ pen.moveTo((xMin, 0))
+ pen.lineTo((xMin, 1000))
+ pen.lineTo((xMax, 1000))
+ pen.lineTo((xMax, 0))
+ pen.closePath()
+
+ class CompositeGlyph(object):
+ def __init__(self, components):
+ self.components = components
+
+ def draw(self, pen):
+ for baseGlyph, (offsetX, offsetY) in self.components:
+ pen.addComponent(baseGlyph, (1, 0, 0, 1, offsetX, offsetY))
+
+ fb = fontBuilder.FontBuilder(unitsPerEm=1000, isTTF=True)
+ fb.setupGlyphOrder(
+ [".notdef", "space", "A", "acutecomb", "Aacute", "zero", "one", "two"]
+ )
+ fb.setupCharacterMap(
+ {
+ 0x20: "space",
+ 0x41: "A",
+ 0x0301: "acutecomb",
+ 0xC1: "Aacute",
+ 0x30: "zero",
+ 0x31: "one",
+ 0x32: "two",
+ }
+ )
+ fb.setupHorizontalMetrics(
+ {
+ ".notdef": (500, 50),
+ "space": (600, 0),
+ "A": (550, 40),
+ "acutecomb": (0, -40),
+ "Aacute": (550, 40),
+ "zero": (500, 30),
+ "one": (500, 50),
+ "two": (500, 40),
+ }
+ )
+ fb.setupHorizontalHeader(ascent=1000, descent=-200)
+
+ srcGlyphs = {
+ ".notdef": Glyph(xMin=50, xMax=450),
+ "space": Glyph(empty=True),
+ "A": Glyph(xMin=40, xMax=510),
+ "acutecomb": Glyph(xMin=-40, xMax=60),
+ "Aacute": CompositeGlyph([("A", (0, 0)), ("acutecomb", (200, 0))]),
+ "zero": Glyph(xMin=30, xMax=470),
+ "one": Glyph(xMin=50, xMax=450),
+ "two": Glyph(xMin=40, xMax=460),
+ }
+ pen = TTGlyphPen(srcGlyphs)
+ glyphSet = {}
+ for glyphName, glyph in srcGlyphs.items():
+ glyph.draw(pen)
+ glyphSet[glyphName] = pen.glyph()
+ fb.setupGlyf(glyphSet)
+
+ fb.setupNameTable(
+ {
+ "familyName": "TestWOFF2",
+ "styleName": "Regular",
+ "uniqueFontIdentifier": "TestWOFF2 Regular; Version 1.000; ABCD",
+ "fullName": "TestWOFF2 Regular",
+ "version": "Version 1.000",
+ "psName": "TestWOFF2-Regular",
+ }
+ )
+ fb.setupOS2()
+ fb.setupPost()
+
+ buf = BytesIO()
+ fb.save(buf)
+ buf.seek(0)
+
+ assert fb.font["maxp"].numGlyphs == 8
+ assert fb.font["hhea"].numberOfHMetrics == 6
+ for glyphName in fb.font.getGlyphOrder():
+ xMin = getattr(fb.font["glyf"][glyphName], "xMin", 0)
+ assert xMin == fb.font["hmtx"][glyphName][1]
+
+ return buf
@pytest.fixture
def ttFont(fontfile):
- return ttLib.TTFont(fontfile, recalcBBoxes=False, recalcTimestamp=False)
+ return ttLib.TTFont(fontfile, recalcBBoxes=False, recalcTimestamp=False)
class WOFF2HmtxTableTest(object):
- def test_transform_no_sidebearings(self, ttFont):
- hmtxTable = WOFF2HmtxTable()
- hmtxTable.metrics = ttFont["hmtx"].metrics
-
- data = hmtxTable.transform(ttFont)
-
- assert data == (
- b"\x03" # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
-
- # advanceWidthArray
- b'\x01\xf4' # .notdef: 500
- b'\x02X' # space: 600
- b'\x02&' # A: 550
- b'\x00\x00' # acutecomb: 0
- b'\x02&' # Aacute: 550
- b'\x01\xf4' # zero: 500
- )
-
- def test_transform_proportional_sidebearings(self, ttFont):
- hmtxTable = WOFF2HmtxTable()
- metrics = ttFont["hmtx"].metrics
- # force one of the proportional glyphs to have its left sidebearing be
- # different from its xMin (40)
- metrics["A"] = (550, 39)
- hmtxTable.metrics = metrics
-
- assert ttFont["glyf"]["A"].xMin != metrics["A"][1]
-
- data = hmtxTable.transform(ttFont)
-
- assert data == (
- b"\x02" # 00000010 | bits 0 unset: explicit proportional sidebearings
-
- # advanceWidthArray
- b'\x01\xf4' # .notdef: 500
- b'\x02X' # space: 600
- b'\x02&' # A: 550
- b'\x00\x00' # acutecomb: 0
- b'\x02&' # Aacute: 550
- b'\x01\xf4' # zero: 500
-
- # lsbArray
- b'\x002' # .notdef: 50
- b'\x00\x00' # space: 0
- b"\x00'" # A: 39 (xMin: 40)
- b'\xff\xd8' # acutecomb: -40
- b'\x00(' # Aacute: 40
- b'\x00\x1e' # zero: 30
- )
-
- def test_transform_monospaced_sidebearings(self, ttFont):
- hmtxTable = WOFF2HmtxTable()
- metrics = ttFont["hmtx"].metrics
- hmtxTable.metrics = metrics
-
- # force one of the monospaced glyphs at the end of hmtx table to have
- # its xMin different from its left sidebearing (50)
- ttFont["glyf"]["one"].xMin = metrics["one"][1] + 1
-
- data = hmtxTable.transform(ttFont)
-
- assert data == (
- b"\x01" # 00000001 | bits 1 unset: explicit monospaced sidebearings
-
- # advanceWidthArray
- b'\x01\xf4' # .notdef: 500
- b'\x02X' # space: 600
- b'\x02&' # A: 550
- b'\x00\x00' # acutecomb: 0
- b'\x02&' # Aacute: 550
- b'\x01\xf4' # zero: 500
-
- # leftSideBearingArray
- b'\x002' # one: 50 (xMin: 51)
- b'\x00(' # two: 40
- )
-
- def test_transform_not_applicable(self, ttFont):
- hmtxTable = WOFF2HmtxTable()
- metrics = ttFont["hmtx"].metrics
- # force both a proportional and monospaced glyph to have sidebearings
- # different from the respective xMin coordinates
- metrics["A"] = (550, 39)
- metrics["one"] = (500, 51)
- hmtxTable.metrics = metrics
-
- # 'None' signals to fall back using untransformed hmtx table data
- assert hmtxTable.transform(ttFont) is None
-
- def test_reconstruct_no_sidebearings(self, ttFont):
- hmtxTable = WOFF2HmtxTable()
-
- data = (
- b"\x03" # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
-
- # advanceWidthArray
- b'\x01\xf4' # .notdef: 500
- b'\x02X' # space: 600
- b'\x02&' # A: 550
- b'\x00\x00' # acutecomb: 0
- b'\x02&' # Aacute: 550
- b'\x01\xf4' # zero: 500
- )
-
- hmtxTable.reconstruct(data, ttFont)
-
- assert hmtxTable.metrics == {
- ".notdef": (500, 50),
- "space": (600, 0),
- "A": (550, 40),
- "acutecomb": (0, -40),
- "Aacute": (550, 40),
- "zero": (500, 30),
- "one": (500, 50),
- "two": (500, 40),
- }
-
- def test_reconstruct_proportional_sidebearings(self, ttFont):
- hmtxTable = WOFF2HmtxTable()
-
- data = (
- b"\x02" # 00000010 | bits 0 unset: explicit proportional sidebearings
-
- # advanceWidthArray
- b'\x01\xf4' # .notdef: 500
- b'\x02X' # space: 600
- b'\x02&' # A: 550
- b'\x00\x00' # acutecomb: 0
- b'\x02&' # Aacute: 550
- b'\x01\xf4' # zero: 500
-
- # lsbArray
- b'\x002' # .notdef: 50
- b'\x00\x00' # space: 0
- b"\x00'" # A: 39 (xMin: 40)
- b'\xff\xd8' # acutecomb: -40
- b'\x00(' # Aacute: 40
- b'\x00\x1e' # zero: 30
- )
-
- hmtxTable.reconstruct(data, ttFont)
-
- assert hmtxTable.metrics == {
- ".notdef": (500, 50),
- "space": (600, 0),
- "A": (550, 39),
- "acutecomb": (0, -40),
- "Aacute": (550, 40),
- "zero": (500, 30),
- "one": (500, 50),
- "two": (500, 40),
- }
-
- assert ttFont["glyf"]["A"].xMin == 40
-
- def test_reconstruct_monospaced_sidebearings(self, ttFont):
- hmtxTable = WOFF2HmtxTable()
-
- data = (
- b"\x01" # 00000001 | bits 1 unset: explicit monospaced sidebearings
-
- # advanceWidthArray
- b'\x01\xf4' # .notdef: 500
- b'\x02X' # space: 600
- b'\x02&' # A: 550
- b'\x00\x00' # acutecomb: 0
- b'\x02&' # Aacute: 550
- b'\x01\xf4' # zero: 500
-
- # leftSideBearingArray
- b'\x003' # one: 51 (xMin: 50)
- b'\x00(' # two: 40
- )
-
- hmtxTable.reconstruct(data, ttFont)
-
- assert hmtxTable.metrics == {
- ".notdef": (500, 50),
- "space": (600, 0),
- "A": (550, 40),
- "acutecomb": (0, -40),
- "Aacute": (550, 40),
- "zero": (500, 30),
- "one": (500, 51),
- "two": (500, 40),
- }
-
- assert ttFont["glyf"]["one"].xMin == 50
-
- def test_reconstruct_flags_reserved_bits(self):
- hmtxTable = WOFF2HmtxTable()
-
- with pytest.raises(
- ttLib.TTLibError, match="Bits 2-7 of 'hmtx' flags are reserved"
- ):
- hmtxTable.reconstruct(b"\xFF", ttFont=None)
-
- def test_reconstruct_flags_required_bits(self):
- hmtxTable = WOFF2HmtxTable()
-
- with pytest.raises(ttLib.TTLibError, match="either bits 0 or 1 .* must set"):
- hmtxTable.reconstruct(b"\x00", ttFont=None)
-
- def test_reconstruct_too_much_data(self, ttFont):
- ttFont["hhea"].numberOfHMetrics = 2
- data = b'\x03\x01\xf4\x02X\x02&'
- hmtxTable = WOFF2HmtxTable()
-
- with pytest.raises(ttLib.TTLibError, match="too much 'hmtx' table data"):
- hmtxTable.reconstruct(data, ttFont)
+ def test_transform_no_sidebearings(self, ttFont):
+ hmtxTable = WOFF2HmtxTable()
+ hmtxTable.metrics = ttFont["hmtx"].metrics
+
+ data = hmtxTable.transform(ttFont)
+
+ assert data == (
+ b"\x03" # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
+ # advanceWidthArray
+ b"\x01\xf4" # .notdef: 500
+ b"\x02X" # space: 600
+ b"\x02&" # A: 550
+ b"\x00\x00" # acutecomb: 0
+ b"\x02&" # Aacute: 550
+ b"\x01\xf4" # zero: 500
+ )
+
+ def test_transform_proportional_sidebearings(self, ttFont):
+ hmtxTable = WOFF2HmtxTable()
+ metrics = ttFont["hmtx"].metrics
+ # force one of the proportional glyphs to have its left sidebearing be
+ # different from its xMin (40)
+ metrics["A"] = (550, 39)
+ hmtxTable.metrics = metrics
+
+ assert ttFont["glyf"]["A"].xMin != metrics["A"][1]
+
+ data = hmtxTable.transform(ttFont)
+
+ assert data == (
+ b"\x02" # 00000010 | bits 0 unset: explicit proportional sidebearings
+ # advanceWidthArray
+ b"\x01\xf4" # .notdef: 500
+ b"\x02X" # space: 600
+ b"\x02&" # A: 550
+ b"\x00\x00" # acutecomb: 0
+ b"\x02&" # Aacute: 550
+ b"\x01\xf4" # zero: 500
+ # lsbArray
+ b"\x002" # .notdef: 50
+ b"\x00\x00" # space: 0
+ b"\x00'" # A: 39 (xMin: 40)
+ b"\xff\xd8" # acutecomb: -40
+ b"\x00(" # Aacute: 40
+ b"\x00\x1e" # zero: 30
+ )
+
+ def test_transform_monospaced_sidebearings(self, ttFont):
+ hmtxTable = WOFF2HmtxTable()
+ metrics = ttFont["hmtx"].metrics
+ hmtxTable.metrics = metrics
+
+ # force one of the monospaced glyphs at the end of hmtx table to have
+ # its xMin different from its left sidebearing (50)
+ ttFont["glyf"]["one"].xMin = metrics["one"][1] + 1
+
+ data = hmtxTable.transform(ttFont)
+
+ assert data == (
+ b"\x01" # 00000001 | bits 1 unset: explicit monospaced sidebearings
+ # advanceWidthArray
+ b"\x01\xf4" # .notdef: 500
+ b"\x02X" # space: 600
+ b"\x02&" # A: 550
+ b"\x00\x00" # acutecomb: 0
+ b"\x02&" # Aacute: 550
+ b"\x01\xf4" # zero: 500
+ # leftSideBearingArray
+ b"\x002" # one: 50 (xMin: 51)
+ b"\x00(" # two: 40
+ )
+
+ def test_transform_not_applicable(self, ttFont):
+ hmtxTable = WOFF2HmtxTable()
+ metrics = ttFont["hmtx"].metrics
+ # force both a proportional and monospaced glyph to have sidebearings
+ # different from the respective xMin coordinates
+ metrics["A"] = (550, 39)
+ metrics["one"] = (500, 51)
+ hmtxTable.metrics = metrics
+
+ # 'None' signals to fall back using untransformed hmtx table data
+ assert hmtxTable.transform(ttFont) is None
+
+ def test_reconstruct_no_sidebearings(self, ttFont):
+ hmtxTable = WOFF2HmtxTable()
+
+ data = (
+ b"\x03" # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
+ # advanceWidthArray
+ b"\x01\xf4" # .notdef: 500
+ b"\x02X" # space: 600
+ b"\x02&" # A: 550
+ b"\x00\x00" # acutecomb: 0
+ b"\x02&" # Aacute: 550
+ b"\x01\xf4" # zero: 500
+ )
+
+ hmtxTable.reconstruct(data, ttFont)
+
+ assert hmtxTable.metrics == {
+ ".notdef": (500, 50),
+ "space": (600, 0),
+ "A": (550, 40),
+ "acutecomb": (0, -40),
+ "Aacute": (550, 40),
+ "zero": (500, 30),
+ "one": (500, 50),
+ "two": (500, 40),
+ }
+
+ def test_reconstruct_proportional_sidebearings(self, ttFont):
+ hmtxTable = WOFF2HmtxTable()
+
+ data = (
+ b"\x02" # 00000010 | bits 0 unset: explicit proportional sidebearings
+ # advanceWidthArray
+ b"\x01\xf4" # .notdef: 500
+ b"\x02X" # space: 600
+ b"\x02&" # A: 550
+ b"\x00\x00" # acutecomb: 0
+ b"\x02&" # Aacute: 550
+ b"\x01\xf4" # zero: 500
+ # lsbArray
+ b"\x002" # .notdef: 50
+ b"\x00\x00" # space: 0
+ b"\x00'" # A: 39 (xMin: 40)
+ b"\xff\xd8" # acutecomb: -40
+ b"\x00(" # Aacute: 40
+ b"\x00\x1e" # zero: 30
+ )
+
+ hmtxTable.reconstruct(data, ttFont)
+
+ assert hmtxTable.metrics == {
+ ".notdef": (500, 50),
+ "space": (600, 0),
+ "A": (550, 39),
+ "acutecomb": (0, -40),
+ "Aacute": (550, 40),
+ "zero": (500, 30),
+ "one": (500, 50),
+ "two": (500, 40),
+ }
+
+ assert ttFont["glyf"]["A"].xMin == 40
+
+ def test_reconstruct_monospaced_sidebearings(self, ttFont):
+ hmtxTable = WOFF2HmtxTable()
+
+ data = (
+ b"\x01" # 00000001 | bits 1 unset: explicit monospaced sidebearings
+ # advanceWidthArray
+ b"\x01\xf4" # .notdef: 500
+ b"\x02X" # space: 600
+ b"\x02&" # A: 550
+ b"\x00\x00" # acutecomb: 0
+ b"\x02&" # Aacute: 550
+ b"\x01\xf4" # zero: 500
+ # leftSideBearingArray
+ b"\x003" # one: 51 (xMin: 50)
+ b"\x00(" # two: 40
+ )
+
+ hmtxTable.reconstruct(data, ttFont)
+
+ assert hmtxTable.metrics == {
+ ".notdef": (500, 50),
+ "space": (600, 0),
+ "A": (550, 40),
+ "acutecomb": (0, -40),
+ "Aacute": (550, 40),
+ "zero": (500, 30),
+ "one": (500, 51),
+ "two": (500, 40),
+ }
+
+ assert ttFont["glyf"]["one"].xMin == 50
+
+ def test_reconstruct_flags_reserved_bits(self):
+ hmtxTable = WOFF2HmtxTable()
+
+ with pytest.raises(
+ ttLib.TTLibError, match="Bits 2-7 of 'hmtx' flags are reserved"
+ ):
+ hmtxTable.reconstruct(b"\xFF", ttFont=None)
+
+ def test_reconstruct_flags_required_bits(self):
+ hmtxTable = WOFF2HmtxTable()
+
+ with pytest.raises(ttLib.TTLibError, match="either bits 0 or 1 .* must set"):
+ hmtxTable.reconstruct(b"\x00", ttFont=None)
+
+ def test_reconstruct_too_much_data(self, ttFont):
+ ttFont["hhea"].numberOfHMetrics = 2
+ data = b"\x03\x01\xf4\x02X\x02&"
+ hmtxTable = WOFF2HmtxTable()
+
+ with pytest.raises(ttLib.TTLibError, match="too much 'hmtx' table data"):
+ hmtxTable.reconstruct(data, ttFont)
class WOFF2RoundtripTest(object):
- @staticmethod
- def roundtrip(infile):
- infile.seek(0)
- ttFont = ttLib.TTFont(infile, recalcBBoxes=False, recalcTimestamp=False)
- outfile = BytesIO()
- ttFont.save(outfile)
- return outfile, ttFont
+ @staticmethod
+ def roundtrip(infile):
+ infile.seek(0)
+ ttFont = ttLib.TTFont(infile, recalcBBoxes=False, recalcTimestamp=False)
+ outfile = BytesIO()
+ ttFont.save(outfile)
+ return outfile, ttFont
- def test_roundtrip_default_transforms(self, ttFont):
- ttFont.flavor = "woff2"
- # ttFont.flavorData = None
- tmp = BytesIO()
- ttFont.save(tmp)
+ def test_roundtrip_default_transforms(self, ttFont):
+ ttFont.flavor = "woff2"
+ # ttFont.flavorData = None
+ tmp = BytesIO()
+ ttFont.save(tmp)
- tmp2, ttFont2 = self.roundtrip(tmp)
+ tmp2, ttFont2 = self.roundtrip(tmp)
- assert tmp.getvalue() == tmp2.getvalue()
- assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca"}
+ assert tmp.getvalue() == tmp2.getvalue()
+ assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca"}
- def test_roundtrip_no_transforms(self, ttFont):
- ttFont.flavor = "woff2"
- ttFont.flavorData = WOFF2FlavorData(transformedTables=[])
- tmp = BytesIO()
- ttFont.save(tmp)
+ def test_roundtrip_no_transforms(self, ttFont):
+ ttFont.flavor = "woff2"
+ ttFont.flavorData = WOFF2FlavorData(transformedTables=[])
+ tmp = BytesIO()
+ ttFont.save(tmp)
- tmp2, ttFont2 = self.roundtrip(tmp)
+ tmp2, ttFont2 = self.roundtrip(tmp)
- assert tmp.getvalue() == tmp2.getvalue()
- assert not ttFont2.reader.flavorData.transformedTables
+ assert tmp.getvalue() == tmp2.getvalue()
+ assert not ttFont2.reader.flavorData.transformedTables
- def test_roundtrip_all_transforms(self, ttFont):
- ttFont.flavor = "woff2"
- ttFont.flavorData = WOFF2FlavorData(transformedTables=["glyf", "loca", "hmtx"])
- tmp = BytesIO()
- ttFont.save(tmp)
+ def test_roundtrip_all_transforms(self, ttFont):
+ ttFont.flavor = "woff2"
+ ttFont.flavorData = WOFF2FlavorData(transformedTables=["glyf", "loca", "hmtx"])
+ tmp = BytesIO()
+ ttFont.save(tmp)
- tmp2, ttFont2 = self.roundtrip(tmp)
+ tmp2, ttFont2 = self.roundtrip(tmp)
- assert tmp.getvalue() == tmp2.getvalue()
- assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
+ assert tmp.getvalue() == tmp2.getvalue()
+ assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
- def test_roundtrip_only_hmtx_no_glyf_transform(self, ttFont):
- ttFont.flavor = "woff2"
- ttFont.flavorData = WOFF2FlavorData(transformedTables=["hmtx"])
- tmp = BytesIO()
- ttFont.save(tmp)
+ def test_roundtrip_only_hmtx_no_glyf_transform(self, ttFont):
+ ttFont.flavor = "woff2"
+ ttFont.flavorData = WOFF2FlavorData(transformedTables=["hmtx"])
+ tmp = BytesIO()
+ ttFont.save(tmp)
- tmp2, ttFont2 = self.roundtrip(tmp)
+ tmp2, ttFont2 = self.roundtrip(tmp)
- assert tmp.getvalue() == tmp2.getvalue()
- assert ttFont2.reader.flavorData.transformedTables == {"hmtx"}
+ 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)
+ 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
+ assert "glyf" not in ttFont
+ assert "loca" not in ttFont
- ttFont.flavor = "woff2"
- tmp = BytesIO()
- ttFont.save(tmp)
+ ttFont.flavor = "woff2"
+ tmp = BytesIO()
+ ttFont.save(tmp)
- tmp2, ttFont2 = self.roundtrip(tmp)
- assert tmp.getvalue() == tmp2.getvalue()
- assert ttFont.flavor == "woff2"
+ 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)
+ 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
+ assert ttFont["glyf"]["A"].flags[0] == _g_l_y_f.flagOverlapSimple
- ttFont.flavor = "woff2"
- tmp = BytesIO()
- ttFont.save(tmp)
+ ttFont.flavor = "woff2"
+ tmp = BytesIO()
+ ttFont.save(tmp)
- _, ttFont2 = self.roundtrip(tmp)
- assert ttFont2.flavor == "woff2"
- assert ttFont2["glyf"]["A"].flags[0] == 0
+ _, ttFont2 = self.roundtrip(tmp)
+ assert ttFont2.flavor == "woff2"
+ # check that the off-curve point is still there
+ assert ttFont2["glyf"]["A"].flags[0] & _g_l_y_f.flagOnCurve == 0
+ # check that the overlap bit is still there
+ assert ttFont2["glyf"]["A"].flags[0] & _g_l_y_f.flagOverlapSimple != 0
-class MainTest(object):
- @staticmethod
- def make_ttf(tmpdir):
- ttFont = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- ttFont.importXML(TTX)
- filename = str(tmpdir / "TestTTF-Regular.ttf")
- ttFont.save(filename)
- return filename
+class MainTest(object):
+ @staticmethod
+ def make_ttf(tmpdir):
+ ttFont = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ ttFont.importXML(TTX)
+ filename = str(tmpdir / "TestTTF-Regular.ttf")
+ ttFont.save(filename)
+ return filename
- def test_compress_ttf(self, tmpdir):
- input_file = self.make_ttf(tmpdir)
+ def test_compress_ttf(self, tmpdir):
+ input_file = self.make_ttf(tmpdir)
- assert woff2.main(["compress", input_file]) is None
+ assert woff2.main(["compress", input_file]) is None
- assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
+ assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
- def test_compress_ttf_no_glyf_transform(self, tmpdir):
- input_file = self.make_ttf(tmpdir)
+ def test_compress_ttf_no_glyf_transform(self, tmpdir):
+ input_file = self.make_ttf(tmpdir)
- assert woff2.main(["compress", "--no-glyf-transform", input_file]) is None
+ assert woff2.main(["compress", "--no-glyf-transform", input_file]) is None
- assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
+ assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
- def test_compress_ttf_hmtx_transform(self, tmpdir):
- input_file = self.make_ttf(tmpdir)
+ def test_compress_ttf_hmtx_transform(self, tmpdir):
+ input_file = self.make_ttf(tmpdir)
- assert woff2.main(["compress", "--hmtx-transform", input_file]) is None
+ assert woff2.main(["compress", "--hmtx-transform", input_file]) is None
- assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
+ assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
- def test_compress_ttf_no_glyf_transform_hmtx_transform(self, tmpdir):
- input_file = self.make_ttf(tmpdir)
+ def test_compress_ttf_no_glyf_transform_hmtx_transform(self, tmpdir):
+ input_file = self.make_ttf(tmpdir)
- assert woff2.main(
- ["compress", "--no-glyf-transform", "--hmtx-transform", input_file]
- ) is None
+ assert (
+ woff2.main(
+ ["compress", "--no-glyf-transform", "--hmtx-transform", input_file]
+ )
+ is None
+ )
- assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
+ assert (tmpdir / "TestTTF-Regular.woff2").check(file=True)
- def test_compress_output_file(self, tmpdir):
- input_file = self.make_ttf(tmpdir)
- output_file = tmpdir / "TestTTF.woff2"
+ def test_compress_output_file(self, tmpdir):
+ input_file = self.make_ttf(tmpdir)
+ output_file = tmpdir / "TestTTF.woff2"
- assert woff2.main(
- ["compress", "-o", str(output_file), str(input_file)]
- ) is None
+ assert woff2.main(["compress", "-o", str(output_file), str(input_file)]) is None
- assert output_file.check(file=True)
+ assert output_file.check(file=True)
- def test_compress_otf(self, tmpdir):
- ttFont = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
- ttFont.importXML(OTX)
- input_file = str(tmpdir / "TestOTF-Regular.otf")
- ttFont.save(input_file)
+ def test_compress_otf(self, tmpdir):
+ ttFont = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ ttFont.importXML(OTX)
+ input_file = str(tmpdir / "TestOTF-Regular.otf")
+ ttFont.save(input_file)
- assert woff2.main(["compress", input_file]) is None
+ assert woff2.main(["compress", input_file]) is None
- assert (tmpdir / "TestOTF-Regular.woff2").check(file=True)
+ assert (tmpdir / "TestOTF-Regular.woff2").check(file=True)
- def test_recompress_woff2_keeps_flavorData(self, tmpdir):
- woff2_font = ttLib.TTFont(BytesIO(TT_WOFF2.getvalue()))
- woff2_font.flavorData.privData = b"FOOBAR"
- woff2_file = tmpdir / "TestTTF-Regular.woff2"
- woff2_font.save(str(woff2_file))
+ def test_recompress_woff2_keeps_flavorData(self, tmpdir):
+ woff2_font = ttLib.TTFont(BytesIO(TT_WOFF2.getvalue()))
+ woff2_font.flavorData.privData = b"FOOBAR"
+ woff2_file = tmpdir / "TestTTF-Regular.woff2"
+ woff2_font.save(str(woff2_file))
- assert woff2_font.flavorData.transformedTables == {"glyf", "loca"}
+ assert woff2_font.flavorData.transformedTables == {"glyf", "loca"}
- woff2.main(["compress", "--hmtx-transform", str(woff2_file)])
+ woff2.main(["compress", "--hmtx-transform", str(woff2_file)])
- output_file = tmpdir / "TestTTF-Regular#1.woff2"
- assert output_file.check(file=True)
+ output_file = tmpdir / "TestTTF-Regular#1.woff2"
+ assert output_file.check(file=True)
- new_woff2_font = ttLib.TTFont(str(output_file))
+ new_woff2_font = ttLib.TTFont(str(output_file))
- assert new_woff2_font.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
- assert new_woff2_font.flavorData.privData == b"FOOBAR"
+ assert new_woff2_font.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
+ assert new_woff2_font.flavorData.privData == b"FOOBAR"
- def test_decompress_ttf(self, tmpdir):
- input_file = tmpdir / "TestTTF-Regular.woff2"
- input_file.write_binary(TT_WOFF2.getvalue())
+ def test_decompress_ttf(self, tmpdir):
+ input_file = tmpdir / "TestTTF-Regular.woff2"
+ input_file.write_binary(TT_WOFF2.getvalue())
- assert woff2.main(["decompress", str(input_file)]) is None
+ assert woff2.main(["decompress", str(input_file)]) is None
- assert (tmpdir / "TestTTF-Regular.ttf").check(file=True)
+ assert (tmpdir / "TestTTF-Regular.ttf").check(file=True)
- def test_decompress_otf(self, tmpdir):
- input_file = tmpdir / "TestTTF-Regular.woff2"
- input_file.write_binary(CFF_WOFF2.getvalue())
+ def test_decompress_otf(self, tmpdir):
+ input_file = tmpdir / "TestTTF-Regular.woff2"
+ input_file.write_binary(CFF_WOFF2.getvalue())
- assert woff2.main(["decompress", str(input_file)]) is None
+ assert woff2.main(["decompress", str(input_file)]) is None
- assert (tmpdir / "TestTTF-Regular.otf").check(file=True)
+ assert (tmpdir / "TestTTF-Regular.otf").check(file=True)
- def test_decompress_output_file(self, tmpdir):
- input_file = tmpdir / "TestTTF-Regular.woff2"
- input_file.write_binary(TT_WOFF2.getvalue())
- output_file = tmpdir / "TestTTF.ttf"
+ def test_decompress_output_file(self, tmpdir):
+ input_file = tmpdir / "TestTTF-Regular.woff2"
+ input_file.write_binary(TT_WOFF2.getvalue())
+ output_file = tmpdir / "TestTTF.ttf"
- assert woff2.main(
- ["decompress", "-o", str(output_file), str(input_file)]
- ) is None
+ assert (
+ woff2.main(["decompress", "-o", str(output_file), str(input_file)]) is None
+ )
- assert output_file.check(file=True)
+ assert output_file.check(file=True)
- def test_no_subcommand_show_help(self, capsys):
- with pytest.raises(SystemExit):
- woff2.main(["--help"])
+ def test_no_subcommand_show_help(self, capsys):
+ with pytest.raises(SystemExit):
+ woff2.main(["--help"])
- captured = capsys.readouterr()
- assert "usage: fonttools ttLib.woff2" in captured.out
+ captured = capsys.readouterr()
+ assert "usage: fonttools ttLib.woff2" in captured.out
class Base128Test(unittest.TestCase):
-
- def test_unpackBase128(self):
- self.assertEqual(unpackBase128(b'\x3f\x00\x00'), (63, b"\x00\x00"))
- self.assertEqual(unpackBase128(b'\x8f\xff\xff\xff\x7f')[0], 4294967295)
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "UIntBase128 value must not start with leading zeros",
- unpackBase128, b'\x80\x80\x3f')
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "UIntBase128-encoded sequence is longer than 5 bytes",
- unpackBase128, b'\x8f\xff\xff\xff\xff\x7f')
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- r"UIntBase128 value exceeds 2\*\*32-1",
- unpackBase128, b'\x90\x80\x80\x80\x00')
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "not enough data to unpack UIntBase128",
- unpackBase128, b'')
-
- def test_base128Size(self):
- self.assertEqual(base128Size(0), 1)
- self.assertEqual(base128Size(24567), 3)
- self.assertEqual(base128Size(2**32-1), 5)
-
- def test_packBase128(self):
- self.assertEqual(packBase128(63), b"\x3f")
- self.assertEqual(packBase128(2**32-1), b'\x8f\xff\xff\xff\x7f')
- self.assertRaisesRegex(
- ttLib.TTLibError,
- r"UIntBase128 format requires 0 <= integer <= 2\*\*32-1",
- packBase128, 2**32+1)
- self.assertRaisesRegex(
- ttLib.TTLibError,
- r"UIntBase128 format requires 0 <= integer <= 2\*\*32-1",
- packBase128, -1)
+ def test_unpackBase128(self):
+ self.assertEqual(unpackBase128(b"\x3f\x00\x00"), (63, b"\x00\x00"))
+ self.assertEqual(unpackBase128(b"\x8f\xff\xff\xff\x7f")[0], 4294967295)
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "UIntBase128 value must not start with leading zeros",
+ unpackBase128,
+ b"\x80\x80\x3f",
+ )
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "UIntBase128-encoded sequence is longer than 5 bytes",
+ unpackBase128,
+ b"\x8f\xff\xff\xff\xff\x7f",
+ )
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ r"UIntBase128 value exceeds 2\*\*32-1",
+ unpackBase128,
+ b"\x90\x80\x80\x80\x00",
+ )
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "not enough data to unpack UIntBase128",
+ unpackBase128,
+ b"",
+ )
+
+ def test_base128Size(self):
+ self.assertEqual(base128Size(0), 1)
+ self.assertEqual(base128Size(24567), 3)
+ self.assertEqual(base128Size(2**32 - 1), 5)
+
+ def test_packBase128(self):
+ self.assertEqual(packBase128(63), b"\x3f")
+ self.assertEqual(packBase128(2**32 - 1), b"\x8f\xff\xff\xff\x7f")
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ r"UIntBase128 format requires 0 <= integer <= 2\*\*32-1",
+ packBase128,
+ 2**32 + 1,
+ )
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ r"UIntBase128 format requires 0 <= integer <= 2\*\*32-1",
+ packBase128,
+ -1,
+ )
class UShort255Test(unittest.TestCase):
-
- def test_unpack255UShort(self):
- self.assertEqual(unpack255UShort(bytechr(252))[0], 252)
- # some numbers (e.g. 506) can have multiple encodings
- self.assertEqual(
- unpack255UShort(struct.pack(b"BB", 254, 0))[0], 506)
- self.assertEqual(
- unpack255UShort(struct.pack(b"BB", 255, 253))[0], 506)
- self.assertEqual(
- unpack255UShort(struct.pack(b"BBB", 253, 1, 250))[0], 506)
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "not enough data to unpack 255UInt16",
- unpack255UShort, struct.pack(b"BB", 253, 0))
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "not enough data to unpack 255UInt16",
- unpack255UShort, struct.pack(b"B", 254))
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "not enough data to unpack 255UInt16",
- unpack255UShort, struct.pack(b"B", 255))
-
- def test_pack255UShort(self):
- self.assertEqual(pack255UShort(252), b'\xfc')
- self.assertEqual(pack255UShort(505), b'\xff\xfc')
- self.assertEqual(pack255UShort(506), b'\xfe\x00')
- self.assertEqual(pack255UShort(762), b'\xfd\x02\xfa')
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "255UInt16 format requires 0 <= integer <= 65535",
- pack255UShort, -1)
-
- self.assertRaisesRegex(
- ttLib.TTLibError,
- "255UInt16 format requires 0 <= integer <= 65535",
- pack255UShort, 0xFFFF+1)
+ def test_unpack255UShort(self):
+ self.assertEqual(unpack255UShort(bytechr(252))[0], 252)
+ # some numbers (e.g. 506) can have multiple encodings
+ self.assertEqual(unpack255UShort(struct.pack(b"BB", 254, 0))[0], 506)
+ self.assertEqual(unpack255UShort(struct.pack(b"BB", 255, 253))[0], 506)
+ self.assertEqual(unpack255UShort(struct.pack(b"BBB", 253, 1, 250))[0], 506)
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "not enough data to unpack 255UInt16",
+ unpack255UShort,
+ struct.pack(b"BB", 253, 0),
+ )
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "not enough data to unpack 255UInt16",
+ unpack255UShort,
+ struct.pack(b"B", 254),
+ )
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "not enough data to unpack 255UInt16",
+ unpack255UShort,
+ struct.pack(b"B", 255),
+ )
+
+ def test_pack255UShort(self):
+ self.assertEqual(pack255UShort(252), b"\xfc")
+ self.assertEqual(pack255UShort(505), b"\xff\xfc")
+ self.assertEqual(pack255UShort(506), b"\xfe\x00")
+ self.assertEqual(pack255UShort(762), b"\xfd\x02\xfa")
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "255UInt16 format requires 0 <= integer <= 65535",
+ pack255UShort,
+ -1,
+ )
+
+ self.assertRaisesRegex(
+ ttLib.TTLibError,
+ "255UInt16 format requires 0 <= integer <= 65535",
+ pack255UShort,
+ 0xFFFF + 1,
+ )
+
+
+class VarCompositeTest(unittest.TestCase):
+ def test_var_composite(self):
+ input_path = os.path.join(data_dir, "varc-ac00-ac01.ttf")
+ ttf = ttLib.TTFont(input_path)
+ ttf.flavor = "woff2"
+ out = BytesIO()
+ ttf.save(out)
+
+ ttf = ttLib.TTFont(out)
+ ttf.flavor = None
+ out = BytesIO()
+ ttf.save(out)
+
+
+class CubicTest(unittest.TestCase):
+ def test_cubic(self):
+ input_path = os.path.join(
+ data_dir, "..", "tables", "data", "NotoSans-VF-cubic.subset.ttf"
+ )
+ ttf = ttLib.TTFont(input_path)
+ pen1 = RecordingPen()
+ ttf.getGlyphSet()["a"].draw(pen1)
+ ttf.flavor = "woff2"
+ out = BytesIO()
+ ttf.save(out)
+
+ ttf = ttLib.TTFont(out)
+ ttf.flavor = None
+ pen2 = RecordingPen()
+ ttf.getGlyphSet()["a"].draw(pen2)
+ out = BytesIO()
+ ttf.save(out)
+
+ assert pen1.value == pen2.value
if __name__ == "__main__":
- import sys
- sys.exit(unittest.main())
+ import sys
+
+ sys.exit(unittest.main())
diff --git a/Tests/ttx/data/TestOTF.ttx b/Tests/ttx/data/TestOTF.ttx
index 96f18449..b034a758 100644
--- a/Tests/ttx/data/TestOTF.ttx
+++ b/Tests/ttx/data/TestOTF.ttx
@@ -148,7 +148,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@@ -190,7 +190,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
</name>
diff --git a/Tests/ttx/data/TestTTF.ttx b/Tests/ttx/data/TestTTF.ttx
index 66caf6ce..6ecca985 100644
--- a/Tests/ttx/data/TestTTF.ttx
+++ b/Tests/ttx/data/TestTTF.ttx
@@ -468,7 +468,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="1" platEncID="0" langID="0x0" unicode="True">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
<namerecord nameID="18" platformID="1" platEncID="0" langID="0x0" unicode="True">
Test TTF
@@ -510,7 +510,7 @@
https://github.com/fonttools/fonttools
</namerecord>
<namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
- https://github.com/fonttools/fonttools/blob/master/LICENSE
+ https://github.com/fonttools/fonttools/blob/main/LICENSE
</namerecord>
</name>
diff --git a/Tests/ttx/data/roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx b/Tests/ttx/data/roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx
new file mode 100644
index 00000000..cbab6111
--- /dev/null
+++ b/Tests/ttx/data/roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx
@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.32">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name=".null"/>
+ <GlyphID id="2" name="A"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.0"/>
+ <checkSumAdjustment value="0x5c9585c9"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1024"/>
+ <created value="Fri May 6 19:55:13 2022"/>
+ <modified value="Fri May 6 19:55:13 2022"/>
+ <xMin value="0"/>
+ <yMin value="0"/>
+ <xMax value="0"/>
+ <yMax value="0"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="3"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="824"/>
+ <descent value="200"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="0"/>
+ <minRightSideBearing value="0"/>
+ <xMaxExtent value="0"/>
+ <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="3"/>
+ <maxPoints value="0"/>
+ <maxContours value="0"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="2"/>
+ <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="3"/>
+ <xAvgCharWidth value="600"/>
+ <usWeightClass value="400"/>
+ <usWidthClass value="5"/>
+ <fsType value="00000000 00000100"/>
+ <ySubscriptXSize value="0"/>
+ <ySubscriptYSize value="0"/>
+ <ySubscriptXOffset value="0"/>
+ <ySubscriptYOffset value="0"/>
+ <ySuperscriptXSize value="0"/>
+ <ySuperscriptYSize value="0"/>
+ <ySuperscriptXOffset value="0"/>
+ <ySuperscriptYOffset value="0"/>
+ <yStrikeoutSize value="0"/>
+ <yStrikeoutPosition value="0"/>
+ <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="????"/>
+ <fsSelection value="00000000 00000000"/>
+ <usFirstCharIndex value="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="0"/>
+ <sTypoDescender value="0"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="0"/>
+ <usWinDescent value="0"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000000"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="0"/>
+ <sCapHeight value="0"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="600" lsb="0"/>
+ <mtx name=".null" width="600" lsb="0"/>
+ <mtx name="A" width="600" lsb="0"/>
+ </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=".null"/><!-- contains no outline data -->
+
+ <TTGlyph name="A"/><!-- contains no outline data -->
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ HelloTestFont
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ TotallyNormal
+ </namerecord>
+ <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+ HelloTestFont-TotallyNormal
+ </namerecord>
+ <namerecord nameID="1" platformID="1" platEncID="0" langID="0x4" unicode="True">
+ HalloTestFont
+ </namerecord>
+ <namerecord nameID="2" platformID="1" platEncID="0" langID="0x4" unicode="True">
+ TotaalNormaal
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ HelloTestFont
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ TotallyNormal
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ HelloTestFont-TotallyNormal
+ </namerecord>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x413">
+ HalloTestFont
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x413">
+ TotaalNormaal
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="0"/>
+ <underlineThickness value="0"/>
+ <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>
+
+ <DSIG>
+ <!-- note that the Digital Signature will be invalid after recompilation! -->
+ <tableHeader flag="0x1" numSigs="1" version="1"/>
+ <SignatureRecord format="1">
+-----BEGIN PKCS7-----
+0000000100000000
+-----END PKCS7-----
+ </SignatureRecord>
+ </DSIG>
+
+</ttFont>
diff --git a/Tests/ttx/ttx_test.py b/Tests/ttx/ttx_test.py
index ef8d8789..be009b8a 100644
--- a/Tests/ttx/ttx_test.py
+++ b/Tests/ttx/ttx_test.py
@@ -1,14 +1,18 @@
from fontTools.misc.testTools import parseXML
from fontTools.misc.timeTools import timestampSinceEpoch
from fontTools.ttLib import TTFont, TTLibError
+from fontTools.ttLib.tables.DefaultTable import DefaultTable
from fontTools import ttx
+import base64
import getopt
import logging
import os
import shutil
+import subprocess
import sys
import tempfile
import unittest
+from pathlib import Path
import pytest
@@ -26,7 +30,6 @@ except ImportError:
class TTXTest(unittest.TestCase):
-
def __init__(self, methodName):
unittest.TestCase.__init__(self, methodName)
# Python 3 renamed assertRaisesRegexp to assertRaisesRegex,
@@ -69,9 +72,7 @@ class TTXTest(unittest.TestCase):
def test_parseOptions_no_args(self):
with self.assertRaises(getopt.GetoptError) as cm:
ttx.parseOptions([])
- self.assertTrue(
- "Must specify at least one input file" in str(cm.exception)
- )
+ self.assertTrue("Must specify at least one input file" in str(cm.exception))
def test_parseOptions_invalid_path(self):
file_path = "invalid_font_path"
@@ -151,9 +152,7 @@ class TTXTest(unittest.TestCase):
jobs[i][1:],
(
os.path.join(self.tempdir, file_names[i]),
- os.path.join(
- self.tempdir, file_names[i].split(".")[0] + ".ttx"
- ),
+ os.path.join(self.tempdir, file_names[i].split(".")[0] + ".ttx"),
),
)
@@ -436,6 +435,7 @@ def test_options_b():
tto = ttx.Options([("-b", "")], 1)
assert tto.recalcBBoxes is False
+
def test_options_e():
tto = ttx.Options([("-e", "")], 1)
assert tto.ignoreDecompileErrors is False
@@ -966,9 +966,7 @@ def test_main_system_exit(tmpdir, monkeypatch):
inpath = os.path.join("Tests", "ttx", "data", "TestTTF.ttx")
outpath = tmpdir.join("TestTTF.ttf")
args = ["-o", str(outpath), inpath]
- monkeypatch.setattr(
- ttx, "process", (lambda x, y: raise_exception(SystemExit))
- )
+ monkeypatch.setattr(ttx, "process", (lambda x, y: raise_exception(SystemExit)))
ttx.main(args)
@@ -1002,6 +1000,55 @@ def test_main_base_exception(tmpdir, monkeypatch, caplog):
assert "Unhandled exception has occurred" in caplog.text
+def test_main_ttf_dump_stdin_to_stdout(tmp_path):
+ inpath = Path("Tests").joinpath("ttx", "data", "TestTTF.ttf")
+ outpath = tmp_path / "TestTTF.ttx"
+ args = [sys.executable, "-m", "fontTools.ttx", "-q", "-o", "-", "-"]
+ with inpath.open("rb") as infile, outpath.open("w", encoding="utf-8") as outfile:
+ subprocess.run(args, check=True, stdin=infile, stdout=outfile)
+ assert outpath.is_file()
+
+
+def test_main_ttx_compile_stdin_to_stdout(tmp_path):
+ inpath = Path("Tests").joinpath("ttx", "data", "TestTTF.ttx")
+ outpath = tmp_path / "TestTTF.ttf"
+ args = [sys.executable, "-m", "fontTools.ttx", "-q", "-o", "-", "-"]
+ with inpath.open("r", encoding="utf-8") as infile, outpath.open("wb") as outfile:
+ subprocess.run(args, check=True, stdin=infile, stdout=outfile)
+ assert outpath.is_file()
+
+
+def test_roundtrip_DSIG_split_at_XML_parse_buffer_size(tmp_path):
+ inpath = Path("Tests").joinpath(
+ "ttx", "data", "roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx"
+ )
+ font = TTFont()
+ font.importXML(inpath)
+ font["DMMY"] = DefaultTable(tag="DMMY")
+ # just enough dummy bytes to hit the cut off point whereby DSIG data gets
+ # split into two chunks and triggers the bug from
+ # https://github.com/fonttools/fonttools/issues/2614
+ font["DMMY"].data = b"\x01\x02\x03\x04" * 2438
+ font.saveXML(tmp_path / "roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx")
+
+ outpath = tmp_path / "font.ttf"
+ args = [
+ sys.executable,
+ "-m",
+ "fontTools.ttx",
+ "-q",
+ "-o",
+ str(outpath),
+ str(tmp_path / "roundtrip_DSIG_split_at_XML_parse_buffer_size.ttx"),
+ ]
+ subprocess.run(args, check=True)
+
+ assert outpath.is_file()
+ assert TTFont(outpath)["DSIG"].signatureRecords[0].pkcs7 == base64.b64decode(
+ b"0000000100000000"
+ )
+
+
# ---------------------------
# support functions for tests
# ---------------------------
diff --git a/Tests/ufoLib/GLIF1_test.py b/Tests/ufoLib/GLIF1_test.py
index 85fcc71f..c4991ca3 100644
--- a/Tests/ufoLib/GLIF1_test.py
+++ b/Tests/ufoLib/GLIF1_test.py
@@ -1,5 +1,9 @@
import unittest
-from fontTools.ufoLib.glifLib import GlifLibError, readGlyphFromString, writeGlyphToString
+from fontTools.ufoLib.glifLib import (
+ GlifLibError,
+ readGlyphFromString,
+ writeGlyphToString,
+)
from .testSupport import Glyph, stripText
from itertools import islice
@@ -7,256 +11,262 @@ from itertools import islice
# Test Cases
# ----------
-class TestGLIF1(unittest.TestCase):
- def assertEqual(self, first, second, msg=None):
- if isinstance(first, str):
- first = stripText(first)
- if isinstance(second, str):
- second = stripText(second)
- return super().assertEqual(first, second, msg=msg)
-
- def pyToGLIF(self, py):
- py = stripText(py)
- glyph = Glyph()
- exec(py, {"glyph" : glyph, "pointPen" : glyph})
- glif = writeGlyphToString(glyph.name, glyphObject=glyph, drawPointsFunc=glyph.drawPoints, formatVersion=1, validate=True)
- # discard the first line containing the xml declaration
- return "\n".join(islice(glif.splitlines(), 1, None))
-
- def glifToPy(self, glif):
- glif = stripText(glif)
- glif = "<?xml version=\"1.0\"?>\n" + glif
- glyph = Glyph()
- readGlyphFromString(glif, glyphObject=glyph, pointPen=glyph, validate=True)
- return glyph.py()
-
- def testTopElement(self):
- # not glyph
- glif = """
+class TestGLIF1(unittest.TestCase):
+ def assertEqual(self, first, second, msg=None):
+ if isinstance(first, str):
+ first = stripText(first)
+ if isinstance(second, str):
+ second = stripText(second)
+ return super().assertEqual(first, second, msg=msg)
+
+ def pyToGLIF(self, py):
+ py = stripText(py)
+ glyph = Glyph()
+ exec(py, {"glyph": glyph, "pointPen": glyph})
+ glif = writeGlyphToString(
+ glyph.name,
+ glyphObject=glyph,
+ drawPointsFunc=glyph.drawPoints,
+ formatVersion=1,
+ validate=True,
+ )
+ # discard the first line containing the xml declaration
+ return "\n".join(islice(glif.splitlines(), 1, None))
+
+ def glifToPy(self, glif):
+ glif = stripText(glif)
+ glif = '<?xml version="1.0"?>\n' + glif
+ glyph = Glyph()
+ readGlyphFromString(glif, glyphObject=glyph, pointPen=glyph, validate=True)
+ return glyph.py()
+
+ def testTopElement(self):
+ # not glyph
+ glif = """
<notglyph name="a" format="1">
<outline>
</outline>
</notglyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testName_legal(self):
- # legal
- glif = """
+ def testName_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testName_empty(self):
- # empty
- glif = """
+ def testName_empty(self):
+ # empty
+ glif = """
<glyph name="" format="1">
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = ""
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testName_not_a_string(self):
- # not a string
- py = """
+ def testName_not_a_string(self):
+ # not a string
+ py = """
glyph.name = 1
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
- def testFormat_legal(self):
- # legal
- glif = """
+ def testFormat_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testFormat_wrong_number(self):
- # wrong number
- glif = """
+ def testFormat_wrong_number(self):
+ # wrong number
+ glif = """
<glyph name="a" format="-1">
<outline>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testFormat_not_an_int(self):
- # not an int
- glif = """
+ def testFormat_not_an_int(self):
+ # not an int
+ glif = """
<glyph name="a" format="A">
<outline>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testBogusGlyphStructure_unknown_element(self):
- # unknown element
- glif = """
+ def testBogusGlyphStructure_unknown_element(self):
+ # unknown element
+ glif = """
<glyph name="a" format="1">
<unknown />
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testBogusGlyphStructure_content(self):
- # content
- glif = """
+ def testBogusGlyphStructure_content(self):
+ # content
+ glif = """
<glyph name="a" format="1">
Hello World.
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAdvance_legal_width_and_height(self):
- # legal: width and height
- glif = """
+ def testAdvance_legal_width_and_height(self):
+ # legal: width and height
+ glif = """
<glyph name="a" format="1">
<advance height="200" width="100"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = 100
glyph.height = 200
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_legal_width_and_height_floats(self):
- # legal: width and height floats
- glif = """
+ def testAdvance_legal_width_and_height_floats(self):
+ # legal: width and height floats
+ glif = """
<glyph name="a" format="1">
<advance height="200.1" width="100.1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = 100.1
glyph.height = 200.1
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_legal_width(self):
- # legal: width
- glif = """
+ def testAdvance_legal_width(self):
+ # legal: width
+ glif = """
<glyph name="a" format="1">
<advance width="100"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = 100
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_legal_height(self):
- # legal: height
- glif = """
+ def testAdvance_legal_height(self):
+ # legal: height
+ glif = """
<glyph name="a" format="1">
<advance height="200"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.height = 200
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_illegal_width(self):
- # illegal: not a number
- glif = """
+ def testAdvance_illegal_width(self):
+ # illegal: not a number
+ glif = """
<glyph name="a" format="1">
<advance width="a"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = "a"
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAdvance_illegal_height(self):
- glif = """
+ def testAdvance_illegal_height(self):
+ glif = """
<glyph name="a" format="1">
<advance height="a"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.height = "a"
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testUnicodes_legal(self):
- # legal
- glif = """
+ def testUnicodes_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<unicode hex="0061"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.unicodes = [97]
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testUnicodes_legal_multiple(self):
- glif = """
+ def testUnicodes_legal_multiple(self):
+ glif = """
<glyph name="a" format="1">
<unicode hex="0062"/>
<unicode hex="0063"/>
@@ -265,33 +275,33 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.unicodes = [98, 99, 97]
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testUnicodes_illegal(self):
- # illegal
- glif = """
+ def testUnicodes_illegal(self):
+ # illegal
+ glif = """
<glyph name="a" format="1">
<unicode hex="1.1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "zzzzzz"
glyph.unicodes = ["1.1"]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testNote(self):
- glif = """
+ def testNote(self):
+ glif = """
<glyph name="a" format="1">
<note>
\U0001F4A9
@@ -300,17 +310,17 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.note = "💩"
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testLib_legal(self):
- glif = """
+ def testLib_legal(self):
+ glif = """
<glyph name="a" format="1">
<outline>
</outline>
@@ -338,150 +348,150 @@ class TestGLIF1(unittest.TestCase):
</lib>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.lib = {"dict" : {"hello" : "world"}, "float" : 2.5, "int" : 1, "list" : ["a", "b", 1, 2.5], "string" : "a"}
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testOutline_unknown_element(self):
- # unknown element
- glif = """
+ def testOutline_unknown_element(self):
+ # unknown element
+ glif = """
<glyph name="a" format="1">
<outline>
<unknown/>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testOutline_content(self):
- # content
- glif = """
+ def testOutline_content(self):
+ # content
+ glif = """
<glyph name="a" format="1">
<outline>
hello
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testComponent_legal(self):
- # legal
- glif = """
+ def testComponent_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, 5, 1, 4)])
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testComponent_illegal_no_base(self):
- # no base
- glif = """
+ def testComponent_illegal_no_base(self):
+ # no base
+ glif = """
<glyph name="a" format="1">
<outline>
<component xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testComponent_bogus_transformation(self):
- # bogus values in transformation
- glif = """
+ def testComponent_bogus_transformation(self):
+ # bogus values in transformation
+ glif = """
<glyph name="a" format="1">
<outline>
<component base="x" xScale="a" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", ("a", 3, 6, 5, 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="1">
<outline>
<component base="x" xScale="a" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, "a", 6, 5, 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="1">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="a" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, "a", 5, 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="1">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="a" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, "a", 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="1">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="a" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, 5, "a", 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="1">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="a"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, 5, 1, "a")])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testContour_legal_one_contour(self):
- # legal: one contour
- glif = """
+ def testContour_legal_one_contour(self):
+ # legal: one contour
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -489,19 +499,19 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testContour_legal_two_contours(self):
- # legal: two contours
- glif = """
+ def testContour_legal_two_contours(self):
+ # legal: two contours
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -515,7 +525,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, 2)], **{"segmentType" : "move", "smooth" : False})
@@ -526,14 +536,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(10, 20)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testContour_illegal_unkonwn_element(self):
- # unknown element
- glif = """
+ def testContour_illegal_unkonwn_element(self):
+ # unknown element
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -542,11 +552,11 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointCoordinates_legal_int(self):
- # legal: int
- glif = """
+ def testPointCoordinates_legal_int(self):
+ # legal: int
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -556,21 +566,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"name" : "this is here so that the contour isn't seen as an anchor", "segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointCoordinates_legal_float(self):
- # legal: float
- glif = """
+ def testPointCoordinates_legal_float(self):
+ # legal: float
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -580,21 +590,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1.1, -2.2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"name" : "this is here so that the contour isn't seen as an anchor", "segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointCoordinates_illegal_x(self):
- # illegal: string
- glif = """
+ def testPointCoordinates_illegal_x(self):
+ # illegal: string
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -604,19 +614,19 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[("a", 2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"name" : "this is here so that the contour isn't seen as an anchor", "segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointCoordinates_illegal_y(self):
- # legal: int
- glif = """
+ def testPointCoordinates_illegal_y(self):
+ # legal: int
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -626,19 +636,19 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, "a")], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"name" : "this is here so that the contour isn't seen as an anchor", "segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeMove_legal(self):
- # legal
- glif = """
+ def testPointTypeMove_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -648,21 +658,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeMove_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointTypeMove_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -672,21 +682,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : True})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeMove_illegal_not_at_start(self):
- # illegal: not at start
- glif = """
+ def testPointTypeMove_illegal_not_at_start(self):
+ # illegal: not at start
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -696,19 +706,19 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeLine_legal(self):
- # legal
- glif = """
+ def testPointTypeLine_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -718,21 +728,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeLine_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointTypeLine_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -742,21 +752,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "line", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeLine_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointTypeLine_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -766,21 +776,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : True})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal(self):
- # legal
- glif = """
+ def testPointTypeCurve_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -792,7 +802,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -801,14 +811,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointTypeCurve_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -819,7 +829,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
@@ -827,14 +837,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(65, 200)], **{"smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointTypeCurve_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -846,7 +856,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -855,14 +865,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : True})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_no_off_curves(self):
- # legal: no off-curves
- glif = """
+ def testPointTypeCurve_legal_no_off_curves(self):
+ # legal: no off-curves
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -872,21 +882,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_1_off_curve(self):
- # legal: 1 off-curve
- glif = """
+ def testPointTypeCurve_legal_1_off_curve(self):
+ # legal: 1 off-curve
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -897,7 +907,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -905,14 +915,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_illegal_3_off_curves(self):
- # illegal: 3 off-curves
- glif = """
+ def testPointTypeCurve_illegal_3_off_curves(self):
+ # illegal: 3 off-curves
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -925,7 +935,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -935,12 +945,12 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointQCurve_legal(self):
- # legal
- glif = """
+ def testPointQCurve_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -952,7 +962,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -961,14 +971,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointQCurve_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -979,7 +989,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
@@ -987,14 +997,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(65, 200)], **{"smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointQCurve_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1006,7 +1016,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1015,14 +1025,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : True})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_no_off_curves(self):
- # legal: no off-curves
- glif = """
+ def testPointQCurve_legal_no_off_curves(self):
+ # legal: no off-curves
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1032,21 +1042,21 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_one_off_curve(self):
- # legal: 1 off-curve
- glif = """
+ def testPointQCurve_legal_one_off_curve(self):
+ # legal: 1 off-curve
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1057,7 +1067,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1065,14 +1075,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_3_off_curves(self):
- # legal: 3 off-curves
- glif = """
+ def testPointQCurve_legal_3_off_curves(self):
+ # legal: 3 off-curves
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1085,7 +1095,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1095,14 +1105,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testSpecialCaseQCurve(self):
- # contour with no on curve
- glif = """
+ def testSpecialCaseQCurve(self):
+ # contour with no on curve
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1114,7 +1124,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"smooth" : False})
@@ -1123,14 +1133,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 0)], **{"smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeOffCurve_legal(self):
- # legal
- glif = """
+ def testPointTypeOffCurve_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1142,7 +1152,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1151,14 +1161,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeOffCurve_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointTypeOffCurve_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1169,7 +1179,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : False})
@@ -1177,14 +1187,14 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeOffCurve_illegal_before_move(self):
- # before move
- glif = """
+ def testPointTypeOffCurve_illegal_before_move(self):
+ # before move
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1194,19 +1204,19 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeOffCurve_illegal_before_line(self):
- # before line
- glif = """
+ def testPointTypeOffCurve_illegal_before_line(self):
+ # before line
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1216,19 +1226,19 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeOffCurve_illegal_smooth(self):
- # smooth=True
- glif = """
+ def testPointTypeOffCurve_illegal_smooth(self):
+ # smooth=True
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1238,20 +1248,20 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : True})
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testSinglePoint_legal_without_name(self):
- # legal
- # glif format 1 single point without a name was not an anchor
- glif = """
+ def testSinglePoint_legal_without_name(self):
+ # legal
+ # glif format 1 single point without a name was not an anchor
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1260,19 +1270,19 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, 2)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAnchor_legal_with_name(self):
- glif = """
+ def testAnchor_legal_with_name(self):
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1281,18 +1291,18 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.anchors = [{"name" : "test", "x" : 1, "y" : 2}]
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testOpenContourLooseOffCurves_legal(self):
- # a piece of software was writing this kind of structure
- glif = """
+ def testOpenContourLooseOffCurves_legal(self):
+ # a piece of software was writing this kind of structure
+ glif = """
<glyph name="a" format="1">
<outline>
<contour>
@@ -1305,7 +1315,7 @@ class TestGLIF1(unittest.TestCase):
</outline>
</glyph>
"""
- expectedPy = """
+ expectedPy = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, 2)], **{"segmentType" : "move", "smooth" : False})
@@ -1314,11 +1324,11 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(1, 2)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultPy = self.glifToPy(glif)
- self.assertEqual(resultPy, expectedPy)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(resultPy, expectedPy)
- def testOpenContourLooseOffCurves_illegal(self):
- py = """
+ def testOpenContourLooseOffCurves_illegal(self):
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, 2)], **{"segmentType" : "move", "smooth" : False})
@@ -1328,4 +1338,4 @@ class TestGLIF1(unittest.TestCase):
pointPen.addPoint(*[(1, 2)], **{"smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
diff --git a/Tests/ufoLib/GLIF2_test.py b/Tests/ufoLib/GLIF2_test.py
index ab9495db..d8c96d65 100644
--- a/Tests/ufoLib/GLIF2_test.py
+++ b/Tests/ufoLib/GLIF2_test.py
@@ -1,5 +1,9 @@
import unittest
-from fontTools.ufoLib.glifLib import GlifLibError, readGlyphFromString, writeGlyphToString
+from fontTools.ufoLib.glifLib import (
+ GlifLibError,
+ readGlyphFromString,
+ writeGlyphToString,
+)
from .testSupport import Glyph, stripText
from itertools import islice
@@ -7,256 +11,262 @@ from itertools import islice
# Test Cases
# ----------
-class TestGLIF2(unittest.TestCase):
- def assertEqual(self, first, second, msg=None):
- if isinstance(first, str):
- first = stripText(first)
- if isinstance(second, str):
- second = stripText(second)
- return super().assertEqual(first, second, msg=msg)
-
- def pyToGLIF(self, py):
- py = stripText(py)
- glyph = Glyph()
- exec(py, {"glyph" : glyph, "pointPen" : glyph})
- glif = writeGlyphToString(glyph.name, glyphObject=glyph, drawPointsFunc=glyph.drawPoints, formatVersion=2, validate=True)
- # discard the first line containing the xml declaration
- return "\n".join(islice(glif.splitlines(), 1, None))
-
- def glifToPy(self, glif):
- glif = stripText(glif)
- glif = "<?xml version=\"1.0\"?>\n" + glif
- glyph = Glyph()
- readGlyphFromString(glif, glyphObject=glyph, pointPen=glyph, validate=True)
- return glyph.py()
-
- def testTopElement(self):
- # not glyph
- glif = """
+class TestGLIF2(unittest.TestCase):
+ def assertEqual(self, first, second, msg=None):
+ if isinstance(first, str):
+ first = stripText(first)
+ if isinstance(second, str):
+ second = stripText(second)
+ return super().assertEqual(first, second, msg=msg)
+
+ def pyToGLIF(self, py):
+ py = stripText(py)
+ glyph = Glyph()
+ exec(py, {"glyph": glyph, "pointPen": glyph})
+ glif = writeGlyphToString(
+ glyph.name,
+ glyphObject=glyph,
+ drawPointsFunc=glyph.drawPoints,
+ formatVersion=2,
+ validate=True,
+ )
+ # discard the first line containing the xml declaration
+ return "\n".join(islice(glif.splitlines(), 1, None))
+
+ def glifToPy(self, glif):
+ glif = stripText(glif)
+ glif = '<?xml version="1.0"?>\n' + glif
+ glyph = Glyph()
+ readGlyphFromString(glif, glyphObject=glyph, pointPen=glyph, validate=True)
+ return glyph.py()
+
+ def testTopElement(self):
+ # not glyph
+ glif = """
<notglyph name="a" format="2">
<outline>
</outline>
</notglyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testName_legal(self):
- # legal
- glif = """
+ def testName_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testName_empty(self):
- # empty
- glif = """
+ def testName_empty(self):
+ # empty
+ glif = """
<glyph name="" format="2">
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = ""
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testName_not_a_string(self):
- # not a string
- py = """
+ def testName_not_a_string(self):
+ # not a string
+ py = """
glyph.name = 1
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
- def testFormat_legal(self):
- # legal
- glif = """
+ def testFormat_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testFormat_illegal_wrong_number(self):
- # wrong number
- glif = """
+ def testFormat_illegal_wrong_number(self):
+ # wrong number
+ glif = """
<glyph name="a" format="-1">
<outline>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testFormat_illegal_not_int(self):
- # not an int
- glif = """
+ def testFormat_illegal_not_int(self):
+ # not an int
+ glif = """
<glyph name="a" format="A">
<outline>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testBogusGlyphStructure_unknown_element(self):
- # unknown element
- glif = """
+ def testBogusGlyphStructure_unknown_element(self):
+ # unknown element
+ glif = """
<glyph name="a" format="2">
<unknown />
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testBogusGlyphStructure_content(self):
- # content
- glif = """
+ def testBogusGlyphStructure_content(self):
+ # content
+ glif = """
<glyph name="a" format="2">
Hello World.
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAdvance_legal_widht_and_height(self):
- # legal: width and height
- glif = """
+ def testAdvance_legal_widht_and_height(self):
+ # legal: width and height
+ glif = """
<glyph name="a" format="2">
<advance height="200" width="100"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = 100
glyph.height = 200
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_legal_width_and_height_floats(self):
- # legal: width and height floats
- glif = """
+ def testAdvance_legal_width_and_height_floats(self):
+ # legal: width and height floats
+ glif = """
<glyph name="a" format="2">
<advance height="200.1" width="100.1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = 100.1
glyph.height = 200.1
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_legal_width(self):
- # legal: width
- glif = """
+ def testAdvance_legal_width(self):
+ # legal: width
+ glif = """
<glyph name="a" format="2">
<advance width="100"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = 100
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_legal_height(self):
- # legal: height
- glif = """
+ def testAdvance_legal_height(self):
+ # legal: height
+ glif = """
<glyph name="a" format="2">
<advance height="200"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.height = 200
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAdvance_illegal_width(self):
- # illegal: not a number
- glif = """
+ def testAdvance_illegal_width(self):
+ # illegal: not a number
+ glif = """
<glyph name="a" format="2">
<advance width="a"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.width = "a"
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAdvance_illegal_height(self):
- glif = """
+ def testAdvance_illegal_height(self):
+ glif = """
<glyph name="a" format="2">
<advance height="a"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.height = "a"
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testUnicodes_legal(self):
- # legal
- glif = """
+ def testUnicodes_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<unicode hex="0061"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.unicodes = [97]
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testUnicodes_legal_multiple(self):
- glif = """
+ def testUnicodes_legal_multiple(self):
+ glif = """
<glyph name="a" format="2">
<unicode hex="0062"/>
<unicode hex="0063"/>
@@ -265,33 +275,33 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.unicodes = [98, 99, 97]
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testUnicodes_illegal(self):
- # illegal
- glif = """
+ def testUnicodes_illegal(self):
+ # illegal
+ glif = """
<glyph name="a" format="2">
<unicode hex="1.1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "zzzzzz"
glyph.unicodes = ["1.1"]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testNote(self):
- glif = """
+ def testNote(self):
+ glif = """
<glyph name="a" format="2">
<note>
hëllö
@@ -300,17 +310,17 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.note = "hëllö"
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testLib(self):
- glif = """
+ def testLib(self):
+ glif = """
<glyph name="a" format="2">
<outline>
</outline>
@@ -338,18 +348,18 @@ class TestGLIF2(unittest.TestCase):
</lib>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.lib = {"dict" : {"hello" : "world"}, "float" : 2.5, "int" : 1, "list" : ["a", "b", 1, 2.5], "string" : "a"}
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testGuidelines_legal(self):
- # legal
- glif = """
+ def testGuidelines_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<guideline x="1"/>
<guideline y="1"/>
@@ -362,143 +372,143 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"x" : 1}, {"y" : 1}, {"angle" : 0, "x" : 1, "y" : 1}, {"angle" : 360, "x" : 1, "y" : 1}, {"angle" : 45.5, "x" : 1.1, "y" : 1.1}, {"name" : "a", "x" : 1}, {"color" : "1,1,1,1", "x" : 1}]
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testGuidelines_illegal_x(self):
- # x not an int or float
- glif = """
+ def testGuidelines_illegal_x(self):
+ # x not an int or float
+ glif = """
<glyph name="a" format="2">
<guideline x="a" y="1" angle="45"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"angle" : 45, "x" : "a", "y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testGuidelines_illegal_y(self):
- # y not an int or float
- glif = """
+ def testGuidelines_illegal_y(self):
+ # y not an int or float
+ glif = """
<glyph name="a" format="2">
<guideline x="1" y="y" angle="45"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"angle" : 45, "x" : 1, "y" : "a"}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testGuidelines_illegal_angle(self):
- # angle not an int or float
- glif = """
+ def testGuidelines_illegal_angle(self):
+ # angle not an int or float
+ glif = """
<glyph name="a" format="2">
<guideline x="1" y="1" angle="a"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"angle" : "a", "x" : 1, "y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testGuidelines_illegal_x_missing(self):
- # x missing
- glif = """
+ def testGuidelines_illegal_x_missing(self):
+ # x missing
+ glif = """
<glyph name="a" format="2">
<guideline y="1" angle="45"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"angle" : 45, "y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testGuidelines_illegal_y_missing(self):
- # y missing
- glif = """
+ def testGuidelines_illegal_y_missing(self):
+ # y missing
+ glif = """
<glyph name="a" format="2">
<guideline x="1" angle="45"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"angle" : 45, "x" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testGuidelines_illegal_angle_missing(self):
- # angle missing
- glif = """
+ def testGuidelines_illegal_angle_missing(self):
+ # angle missing
+ glif = """
<glyph name="a" format="2">
<guideline x="1" y="1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"x" : 1, "y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testGuidelines_illegal_angle_out_of_range(self):
- # angle out of range
- glif = """
+ def testGuidelines_illegal_angle_out_of_range(self):
+ # angle out of range
+ glif = """
<glyph name="a" format="2">
<guideline x="1" y="1" angle="-1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"angle" : -1, "x" : "1", "y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<guideline x="1" y="1" angle="361"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"angle" : 361, "x" : "1", "y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAnchors_legal(self):
- # legal
- glif = """
+ def testAnchors_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<anchor x="1" y="2" name="test" color="1,0,0,1"/>
<anchor x="1" y="2"/>
@@ -506,363 +516,363 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.anchors = [{"color" : "1,0,0,1", "name" : "test", "x" : 1, "y" : 2}, {"x" : 1, "y" : 2}]
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testAnchors_illegal_x(self):
- # x not an int or float
- glif = """
+ def testAnchors_illegal_x(self):
+ # x not an int or float
+ glif = """
<glyph name="a" format="2">
<anchor x="a" y="1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.anchors = [{"x" : "a", "y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAnchors_illegal_y(self):
- # y not an int or float
- glif = """
+ def testAnchors_illegal_y(self):
+ # y not an int or float
+ glif = """
<glyph name="a" format="2">
<anchor x="1" y="a"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.anchors = [{"x" : 1, "y" : "a"}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAnchors_illegal_x_missing(self):
- # x missing
- glif = """
+ def testAnchors_illegal_x_missing(self):
+ # x missing
+ glif = """
<glyph name="a" format="2">
<anchor y="1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.anchors = [{"y" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testAnchors_illegal_y_missing(self):
- # y missing
- glif = """
+ def testAnchors_illegal_y_missing(self):
+ # y missing
+ glif = """
<glyph name="a" format="2">
<anchor x="1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.anchors = [{"x" : 1}]
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testImage_legal(self):
- # legal
- glif = """
+ def testImage_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4" color="1,1,1,1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"color" : "1,1,1,1", "fileName" : "test.png", "xOffset" : 1, "xScale" : 2, "xyScale" : 3, "yOffset" : 4, "yScale" : 5, "yxScale" : 6}
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testImage_legal_no_color_or_transformation(self):
- # legal: no color or transformation
- glif = """
+ def testImage_legal_no_color_or_transformation(self):
+ # legal: no color or transformation
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"fileName" : "test.png", "xOffset" : 0, "xScale" : 1, "xyScale" : 0, "yOffset" : 0, "yScale" : 1, "yxScale" : 0}
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testImage_illegal_no_file_name(self):
- # no file name
- glif = """
+ def testImage_illegal_no_file_name(self):
+ # no file name
+ glif = """
<glyph name="a" format="2">
<image xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4" color="1,1,1,1"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"color" : "1,1,1,1", "xOffset" : 1, "xScale" : 2, "xyScale" : 3, "yOffset" : 4, "yScale" : 5, "yxScale" : 6}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testImage_bogus_transformation(self):
- # bogus transformation
- glif = """
+ def testImage_bogus_transformation(self):
+ # bogus transformation
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" xScale="a" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"fileName" : "test.png", "xOffset" : 1, "xScale" : "a", "xyScale" : 3, "yOffset" : 4, "yScale" : 5, "yxScale" : 6}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" xScale="2" xyScale="a" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"fileName" : "test.png", "xOffset" : 1, "xScale" : 2, "xyScale" : "a", "yOffset" : 4, "yScale" : 5, "yxScale" : 6}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" xScale="2" xyScale="3" yxScale="a" yScale="5" xOffset="1" yOffset="4"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"fileName" : "test.png", "xOffset" : 1, "xScale" : 2, "xyScale" : 3, "yOffset" : 4, "yScale" : 5, "yxScale" : "a"}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" xScale="2" xyScale="3" yxScale="6" yScale="a" xOffset="1" yOffset="4"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"fileName" : "test.png", "xOffset" : 1, "xScale" : 2, "xyScale" : 3, "yOffset" : 4, "yScale" : "a", "yxScale" : 6}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="a" yOffset="4"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"fileName" : "test.png", "xOffset" : "a", "xScale" : 2, "xyScale" : 3, "yOffset" : 4, "yScale" : 5, "yxScale" : 6}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="a"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"fileName" : "test.png", "xOffset" : 1, "xScale" : 2, "xyScale" : 3, "yOffset" : "a", "yScale" : 5, "yxScale" : 6}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testImage_bogus_color(self):
- # bogus color
- glif = """
+ def testImage_bogus_color(self):
+ # bogus color
+ glif = """
<glyph name="a" format="2">
<image fileName="test.png" color="1,1,1,x"/>
<outline>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.image = {"color" : "1,1,1,x"}
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testOutline_unknown_element(self):
- # unknown element
- glif = """
+ def testOutline_unknown_element(self):
+ # unknown element
+ glif = """
<glyph name="a" format="2">
<outline>
<unknown/>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testOutline_content(self):
- # content
- glif = """
+ def testOutline_content(self):
+ # content
+ glif = """
<glyph name="a" format="2">
<outline>
hello
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testComponent_legal(self):
- # legal
- glif = """
+ def testComponent_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, 5, 1, 4)])
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testComponent_illegal_no_base(self):
- # no base
- glif = """
+ def testComponent_illegal_no_base(self):
+ # no base
+ glif = """
<glyph name="a" format="2">
<outline>
<component xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testComponent_illegal_bogus_transformation(self):
- # bogus values in transformation
- glif = """
+ def testComponent_illegal_bogus_transformation(self):
+ # bogus values in transformation
+ glif = """
<glyph name="a" format="2">
<outline>
<component base="x" xScale="a" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", ("a", 3, 6, 5, 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<outline>
<component base="x" xScale="a" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, "a", 6, 5, 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="a" yScale="5" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, "a", 5, 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="a" xOffset="1" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, "a", 1, 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="a" yOffset="4"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, 5, "a", 4)])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- glif = """
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ glif = """
<glyph name="a" format="2">
<outline>
<component base="x" xScale="2" xyScale="3" yxScale="6" yScale="5" xOffset="1" yOffset="a"/>
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.addComponent(*["x", (2, 3, 6, 5, 1, "a")])
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testContour_legal_one_contour(self):
- # legal: one contour
- glif = """
+ def testContour_legal_one_contour(self):
+ # legal: one contour
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -870,19 +880,19 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testContour_legal_two_contours(self):
- # legal: two contours
- glif = """
+ def testContour_legal_two_contours(self):
+ # legal: two contours
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -895,7 +905,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, 2)], **{"segmentType" : "move", "smooth" : False})
@@ -905,14 +915,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(10, 20)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testContour_illegal_unkonwn_element(self):
- # unknown element
- glif = """
+ def testContour_illegal_unkonwn_element(self):
+ # unknown element
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -921,10 +931,10 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testContourIdentifier(self):
- glif = """
+ def testContourIdentifier(self):
+ glif = """
<glyph name="a" format="2">
<outline>
<contour identifier="foo">
@@ -932,19 +942,19 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath(**{"identifier" : "foo"})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointCoordinates_legal_int(self):
- # legal: int
- glif = """
+ def testPointCoordinates_legal_int(self):
+ # legal: int
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -953,20 +963,20 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointCoordinates_legal_float(self):
- # legal: float
- glif = """
+ def testPointCoordinates_legal_float(self):
+ # legal: float
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -975,20 +985,20 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1.1, -2.2)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointCoordinates_illegal_x(self):
- # illegal: x as string
- glif = """
+ def testPointCoordinates_illegal_x(self):
+ # illegal: x as string
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -997,18 +1007,18 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[("a", 2)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointCoordinates_illegal_y(self):
- # illegal: y as string
- glif = """
+ def testPointCoordinates_illegal_y(self):
+ # illegal: y as string
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1017,18 +1027,18 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, "a")], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeMove_legal(self):
- # legal
- glif = """
+ def testPointTypeMove_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1038,21 +1048,21 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeMove_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointTypeMove_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1062,21 +1072,21 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : True})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeMove_illegal_not_at_start(self):
- # illegal: not at start
- glif = """
+ def testPointTypeMove_illegal_not_at_start(self):
+ # illegal: not at start
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1086,19 +1096,19 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeLine_legal(self):
- # legal
- glif = """
+ def testPointTypeLine_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1108,21 +1118,21 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeLine_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointTypeLine_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1132,21 +1142,21 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "line", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeLine_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointTypeLine_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1156,21 +1166,21 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(3, -4)], **{"segmentType" : "line", "smooth" : True})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal(self):
- # legal
- glif = """
+ def testPointTypeCurve_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1182,7 +1192,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1191,14 +1201,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointTypeCurve_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1209,7 +1219,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
@@ -1217,14 +1227,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(65, 200)], **{"smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointTypeCurve_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1236,7 +1246,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1245,14 +1255,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : True})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_no_off_curves(self):
- # legal: no off-curves
- glif = """
+ def testPointTypeCurve_legal_no_off_curves(self):
+ # legal: no off-curves
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1262,21 +1272,21 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_legal_1_off_curve(self):
- # legal: 1 off-curve
- glif = """
+ def testPointTypeCurve_legal_1_off_curve(self):
+ # legal: 1 off-curve
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1287,7 +1297,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1295,14 +1305,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeCurve_illegal_3_off_curves(self):
- # illegal: 3 off-curves
- glif = """
+ def testPointTypeCurve_illegal_3_off_curves(self):
+ # illegal: 3 off-curves
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1315,7 +1325,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1325,12 +1335,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointQCurve_legal(self):
- # legal
- glif = """
+ def testPointQCurve_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1342,7 +1352,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1351,14 +1361,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointQCurve_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1369,7 +1379,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
@@ -1377,14 +1387,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(65, 200)], **{"smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_smooth(self):
- # legal: smooth=True
- glif = """
+ def testPointQCurve_legal_smooth(self):
+ # legal: smooth=True
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1396,7 +1406,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1405,14 +1415,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : True})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_no_off_curves(self):
- # legal: no off-curves
- glif = """
+ def testPointQCurve_legal_no_off_curves(self):
+ # legal: no off-curves
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1422,21 +1432,21 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_one_off_curve(self):
- # legal: 1 off-curve
- glif = """
+ def testPointQCurve_legal_one_off_curve(self):
+ # legal: 1 off-curve
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1447,7 +1457,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1455,14 +1465,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointQCurve_legal_3_off_curves(self):
- # legal: 3 off-curves
- glif = """
+ def testPointQCurve_legal_3_off_curves(self):
+ # legal: 3 off-curves
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1475,7 +1485,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1485,14 +1495,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testSpecialCaseQCurve_legal_no_on_curve(self):
- # contour with no on curve
- glif = """
+ def testSpecialCaseQCurve_legal_no_on_curve(self):
+ # contour with no on curve
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1504,7 +1514,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"smooth" : False})
@@ -1513,14 +1523,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 0)], **{"smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeOffCurve_legal(self):
- # legal
- glif = """
+ def testPointTypeOffCurve_legal(self):
+ # legal
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1532,7 +1542,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
@@ -1541,14 +1551,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeOffCurve_legal_start_of_contour(self):
- # legal: start of contour
- glif = """
+ def testPointTypeOffCurve_legal_start_of_contour(self):
+ # legal: start of contour
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1559,7 +1569,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : False})
@@ -1567,14 +1577,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(100, 200)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testPointTypeOffCurve_illegal_before_move(self):
- # before move
- glif = """
+ def testPointTypeOffCurve_illegal_before_move(self):
+ # before move
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1584,19 +1594,19 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "move", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeOffCurve_illegal_before_line(self):
- # before line
- glif = """
+ def testPointTypeOffCurve_illegal_before_line(self):
+ # before line
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1606,19 +1616,19 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : False})
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "line", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testPointTypeOffCurve_illegal_smooth(self):
- # smooth=True
- glif = """
+ def testPointTypeOffCurve_illegal_smooth(self):
+ # smooth=True
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1628,18 +1638,18 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(0, 65)], **{"smooth" : True})
pointPen.addPoint(*[(0, 0)], **{"segmentType" : "curve", "smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testOpenContourLooseOffCurves(self):
- glif = """
+ def testOpenContourLooseOffCurves(self):
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1652,8 +1662,8 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- self.assertRaises(GlifLibError, self.glifToPy, glif)
- py = """
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, 2)], **{"segmentType" : "move", "smooth" : False})
@@ -1663,10 +1673,10 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(1, 2)], **{"smooth" : False})
pointPen.endPath()
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
- def testPointIdentifier(self):
- glif = """
+ def testPointIdentifier(self):
+ glif = """
<glyph name="a" format="2">
<outline>
<contour>
@@ -1678,7 +1688,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
pointPen.beginPath()
pointPen.addPoint(*[(1, -2)], **{"identifier" : "1", "segmentType" : "move", "smooth" : False})
@@ -1687,13 +1697,13 @@ class TestGLIF2(unittest.TestCase):
pointPen.addPoint(*[(1, -2)], **{"identifier" : "4", "segmentType" : "qcurve", "smooth" : False})
pointPen.endPath()
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testIdentifierConflict_legal_no_conflict(self):
- glif = """
+ def testIdentifierConflict_legal_no_conflict(self):
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -1714,7 +1724,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -1730,14 +1740,14 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- resultGlif = self.pyToGLIF(py)
- resultPy = self.glifToPy(glif)
- self.assertEqual(glif, resultGlif)
- self.assertEqual(py, resultPy)
+ resultGlif = self.pyToGLIF(py)
+ resultPy = self.glifToPy(glif)
+ self.assertEqual(glif, resultGlif)
+ self.assertEqual(py, resultPy)
- def testIdentifierConflict_point_point(self):
- # point - point
- glif = """
+ def testIdentifierConflict_point_point(self):
+ # point - point
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -1758,7 +1768,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -1774,12 +1784,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_point_contour(self):
- # point - contour
- glif = """
+ def testIdentifierConflict_point_contour(self):
+ # point - contour
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -1800,7 +1810,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -1816,12 +1826,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_point_component(self):
- # point - component
- glif = """
+ def testIdentifierConflict_point_component(self):
+ # point - component
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -1842,7 +1852,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -1858,12 +1868,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_point_guideline(self):
- # point - guideline
- glif = """
+ def testIdentifierConflict_point_guideline(self):
+ # point - guideline
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -1884,7 +1894,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -1900,12 +1910,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_point_anchor(self):
- # point - anchor
- glif = """
+ def testIdentifierConflict_point_anchor(self):
+ # point - anchor
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -1926,7 +1936,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -1942,12 +1952,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_contour_contour(self):
- # contour - contour
- glif = """
+ def testIdentifierConflict_contour_contour(self):
+ # contour - contour
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -1968,7 +1978,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -1984,12 +1994,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_contour_component(self):
- # contour - component
- glif = """
+ def testIdentifierConflict_contour_component(self):
+ # contour - component
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2010,7 +2020,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2026,12 +2036,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "contour1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_contour_guideline(self):
- # contour - guideline
- glif = """
+ def testIdentifierConflict_contour_guideline(self):
+ # contour - guideline
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="contour1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2052,7 +2062,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "contour1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2068,12 +2078,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_contour_anchor(self):
- # contour - anchor
- glif = """
+ def testIdentifierConflict_contour_anchor(self):
+ # contour - anchor
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2094,7 +2104,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2110,12 +2120,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_component_component(self):
- # component - component
- glif = """
+ def testIdentifierConflict_component_component(self):
+ # component - component
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2136,7 +2146,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2152,12 +2162,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_component_guideline(self):
- # component - guideline
- glif = """
+ def testIdentifierConflict_component_guideline(self):
+ # component - guideline
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="component1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2178,7 +2188,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "component1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2194,12 +2204,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_component_anchor(self):
- # component - anchor
- glif = """
+ def testIdentifierConflict_component_anchor(self):
+ # component - anchor
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2220,7 +2230,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2236,12 +2246,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "anchor1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_guideline_guideline(self):
- # guideline - guideline
- glif = """
+ def testIdentifierConflict_guideline_guideline(self):
+ # guideline - guideline
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline1"/>
@@ -2262,7 +2272,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline1", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2278,12 +2288,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_guideline_anchor(self):
- # guideline - anchor
- glif = """
+ def testIdentifierConflict_guideline_anchor(self):
+ # guideline - anchor
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="anchor1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2304,7 +2314,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "anchor1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor2", "x" : 0, "y" : 0}]
@@ -2320,12 +2330,12 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
- def testIdentifierConflict_anchor_anchor(self):
- # anchor - anchor
- glif = """
+ def testIdentifierConflict_anchor_anchor(self):
+ # anchor - anchor
+ glif = """
<glyph name="a" format="2">
<guideline x="0" identifier="guideline1"/>
<guideline x="0" identifier="guideline2"/>
@@ -2346,7 +2356,7 @@ class TestGLIF2(unittest.TestCase):
</outline>
</glyph>
"""
- py = """
+ py = """
glyph.name = "a"
glyph.guidelines = [{"identifier" : "guideline1", "x" : 0}, {"identifier" : "guideline2", "x" : 0}]
glyph.anchors = [{"identifier" : "anchor1", "x" : 0, "y" : 0}, {"identifier" : "anchor1", "x" : 0, "y" : 0}]
@@ -2362,5 +2372,5 @@ class TestGLIF2(unittest.TestCase):
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component1"})
pointPen.addComponent(*["x", (1, 1, 1, 1, 1, 1)], **{"identifier" : "component2"})
"""
- self.assertRaises(GlifLibError, self.pyToGLIF, py)
- self.assertRaises(GlifLibError, self.glifToPy, glif)
+ self.assertRaises(GlifLibError, self.pyToGLIF, py)
+ self.assertRaises(GlifLibError, self.glifToPy, glif)
diff --git a/Tests/ufoLib/UFO1_test.py b/Tests/ufoLib/UFO1_test.py
index 5feb045a..aad35229 100644
--- a/Tests/ufoLib/UFO1_test.py
+++ b/Tests/ufoLib/UFO1_test.py
@@ -8,143 +8,129 @@ from fontTools.ufoLib import plistlib
from .testSupport import fontInfoVersion1, fontInfoVersion2
-class TestInfoObject: pass
+class TestInfoObject:
+ pass
class ReadFontInfoVersion1TestCase(unittest.TestCase):
-
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- metaInfo = {
- "creator": "test",
- "formatVersion": 1
- }
- path = os.path.join(self.dstDir, "metainfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(metaInfo, f)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def _writeInfoToPlist(self, info):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(info, f)
-
- def testRead(self):
- originalData = dict(fontInfoVersion1)
- self._writeInfoToPlist(originalData)
- infoObject = TestInfoObject()
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(infoObject)
- for attr in dir(infoObject):
- if attr not in fontInfoVersion2:
- continue
- originalValue = fontInfoVersion2[attr]
- readValue = getattr(infoObject, attr)
- self.assertEqual(originalValue, readValue)
-
- def testFontStyleConversion(self):
- fontStyle1To2 = {
- 64 : "regular",
- 1 : "italic",
- 32 : "bold",
- 33 : "bold italic"
- }
- for old, new in list(fontStyle1To2.items()):
- info = dict(fontInfoVersion1)
- info["fontStyle"] = old
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- infoObject = TestInfoObject()
- reader.readInfo(infoObject)
- self.assertEqual(new, infoObject.styleMapStyleName)
-
- def testWidthNameConversion(self):
- widthName1To2 = {
- "Ultra-condensed" : 1,
- "Extra-condensed" : 2,
- "Condensed" : 3,
- "Semi-condensed" : 4,
- "Medium (normal)" : 5,
- "Semi-expanded" : 6,
- "Expanded" : 7,
- "Extra-expanded" : 8,
- "Ultra-expanded" : 9
- }
- for old, new in list(widthName1To2.items()):
- info = dict(fontInfoVersion1)
- info["widthName"] = old
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- infoObject = TestInfoObject()
- reader.readInfo(infoObject)
- self.assertEqual(new, infoObject.openTypeOS2WidthClass)
+ def setUp(self):
+ self.dstDir = tempfile.mktemp()
+ os.mkdir(self.dstDir)
+ metaInfo = {"creator": "test", "formatVersion": 1}
+ path = os.path.join(self.dstDir, "metainfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(metaInfo, f)
+
+ def tearDown(self):
+ shutil.rmtree(self.dstDir)
+
+ def _writeInfoToPlist(self, info):
+ path = os.path.join(self.dstDir, "fontinfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(info, f)
+
+ def testRead(self):
+ originalData = dict(fontInfoVersion1)
+ self._writeInfoToPlist(originalData)
+ infoObject = TestInfoObject()
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(infoObject)
+ for attr in dir(infoObject):
+ if attr not in fontInfoVersion2:
+ continue
+ originalValue = fontInfoVersion2[attr]
+ readValue = getattr(infoObject, attr)
+ self.assertEqual(originalValue, readValue)
+
+ def testFontStyleConversion(self):
+ fontStyle1To2 = {64: "regular", 1: "italic", 32: "bold", 33: "bold italic"}
+ for old, new in list(fontStyle1To2.items()):
+ info = dict(fontInfoVersion1)
+ info["fontStyle"] = old
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ infoObject = TestInfoObject()
+ reader.readInfo(infoObject)
+ self.assertEqual(new, infoObject.styleMapStyleName)
+
+ def testWidthNameConversion(self):
+ widthName1To2 = {
+ "Ultra-condensed": 1,
+ "Extra-condensed": 2,
+ "Condensed": 3,
+ "Semi-condensed": 4,
+ "Medium (normal)": 5,
+ "Semi-expanded": 6,
+ "Expanded": 7,
+ "Extra-expanded": 8,
+ "Ultra-expanded": 9,
+ }
+ for old, new in list(widthName1To2.items()):
+ info = dict(fontInfoVersion1)
+ info["widthName"] = old
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ infoObject = TestInfoObject()
+ reader.readInfo(infoObject)
+ self.assertEqual(new, infoObject.openTypeOS2WidthClass)
class WriteFontInfoVersion1TestCase(unittest.TestCase):
-
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.dstDir = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def makeInfoObject(self):
- infoObject = TestInfoObject()
- for attr, value in list(fontInfoVersion2.items()):
- setattr(infoObject, attr, value)
- return infoObject
-
- def readPlist(self):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- with open(path, "rb") as f:
- plist = plistlib.load(f)
- return plist
-
- def testWrite(self):
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=1)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- for attr, originalValue in list(fontInfoVersion1.items()):
- newValue = writtenData[attr]
- self.assertEqual(newValue, originalValue)
-
- def testFontStyleConversion(self):
- fontStyle1To2 = {
- 64 : "regular",
- 1 : "italic",
- 32 : "bold",
- 33 : "bold italic"
- }
- for old, new in list(fontStyle1To2.items()):
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = new
- writer = UFOWriter(self.dstDir, formatVersion=1)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- self.assertEqual(writtenData["fontStyle"], old)
-
- def testWidthNameConversion(self):
- widthName1To2 = {
- "Ultra-condensed" : 1,
- "Extra-condensed" : 2,
- "Condensed" : 3,
- "Semi-condensed" : 4,
- "Medium (normal)" : 5,
- "Semi-expanded" : 6,
- "Expanded" : 7,
- "Extra-expanded" : 8,
- "Ultra-expanded" : 9
- }
- for old, new in list(widthName1To2.items()):
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = new
- writer = UFOWriter(self.dstDir, formatVersion=1)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- self.assertEqual(writtenData["widthName"], old)
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.dstDir = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def makeInfoObject(self):
+ infoObject = TestInfoObject()
+ for attr, value in list(fontInfoVersion2.items()):
+ setattr(infoObject, attr, value)
+ return infoObject
+
+ def readPlist(self):
+ path = os.path.join(self.dstDir, "fontinfo.plist")
+ with open(path, "rb") as f:
+ plist = plistlib.load(f)
+ return plist
+
+ def testWrite(self):
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=1)
+ writer.writeInfo(infoObject)
+ writtenData = self.readPlist()
+ for attr, originalValue in list(fontInfoVersion1.items()):
+ newValue = writtenData[attr]
+ self.assertEqual(newValue, originalValue)
+
+ def testFontStyleConversion(self):
+ fontStyle1To2 = {64: "regular", 1: "italic", 32: "bold", 33: "bold italic"}
+ for old, new in list(fontStyle1To2.items()):
+ infoObject = self.makeInfoObject()
+ infoObject.styleMapStyleName = new
+ writer = UFOWriter(self.dstDir, formatVersion=1)
+ writer.writeInfo(infoObject)
+ writtenData = self.readPlist()
+ self.assertEqual(writtenData["fontStyle"], old)
+
+ def testWidthNameConversion(self):
+ widthName1To2 = {
+ "Ultra-condensed": 1,
+ "Extra-condensed": 2,
+ "Condensed": 3,
+ "Semi-condensed": 4,
+ "Medium (normal)": 5,
+ "Semi-expanded": 6,
+ "Expanded": 7,
+ "Extra-expanded": 8,
+ "Ultra-expanded": 9,
+ }
+ for old, new in list(widthName1To2.items()):
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WidthClass = new
+ writer = UFOWriter(self.dstDir, formatVersion=1)
+ writer.writeInfo(infoObject)
+ writtenData = self.readPlist()
+ self.assertEqual(writtenData["widthName"], old)
diff --git a/Tests/ufoLib/UFO2_test.py b/Tests/ufoLib/UFO2_test.py
index 68b4bafd..ccd20388 100644
--- a/Tests/ufoLib/UFO2_test.py
+++ b/Tests/ufoLib/UFO2_test.py
@@ -8,1405 +8,1605 @@ from fontTools.ufoLib import plistlib
from .testSupport import fontInfoVersion2
-class TestInfoObject: pass
+class TestInfoObject:
+ pass
class ReadFontInfoVersion2TestCase(unittest.TestCase):
+ def setUp(self):
+ self.dstDir = tempfile.mktemp()
+ os.mkdir(self.dstDir)
+ metaInfo = {"creator": "test", "formatVersion": 2}
+ path = os.path.join(self.dstDir, "metainfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(metaInfo, f)
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- metaInfo = {
- "creator": "test",
- "formatVersion": 2
- }
- path = os.path.join(self.dstDir, "metainfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(metaInfo, f)
+ def tearDown(self):
+ shutil.rmtree(self.dstDir)
- def tearDown(self):
- shutil.rmtree(self.dstDir)
+ def _writeInfoToPlist(self, info):
+ path = os.path.join(self.dstDir, "fontinfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(info, f)
- def _writeInfoToPlist(self, info):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(info, f)
+ def testRead(self):
+ originalData = dict(fontInfoVersion2)
+ self._writeInfoToPlist(originalData)
+ infoObject = TestInfoObject()
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(infoObject)
+ readData = {}
+ for attr in list(fontInfoVersion2.keys()):
+ readData[attr] = getattr(infoObject, attr)
+ self.assertEqual(originalData, readData)
- def testRead(self):
- originalData = dict(fontInfoVersion2)
- self._writeInfoToPlist(originalData)
- infoObject = TestInfoObject()
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(infoObject)
- readData = {}
- for attr in list(fontInfoVersion2.keys()):
- readData[attr] = getattr(infoObject, attr)
- self.assertEqual(originalData, readData)
+ def testGenericRead(self):
+ # familyName
+ info = dict(fontInfoVersion2)
+ info["familyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # styleName
+ info = dict(fontInfoVersion2)
+ info["styleName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # styleMapFamilyName
+ info = dict(fontInfoVersion2)
+ info["styleMapFamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # styleMapStyleName
+ ## not a string
+ info = dict(fontInfoVersion2)
+ info["styleMapStyleName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion2)
+ info["styleMapStyleName"] = "REGULAR"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # versionMajor
+ info = dict(fontInfoVersion2)
+ info["versionMajor"] = "1"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # versionMinor
+ info = dict(fontInfoVersion2)
+ info["versionMinor"] = "0"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # copyright
+ info = dict(fontInfoVersion2)
+ info["copyright"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # trademark
+ info = dict(fontInfoVersion2)
+ info["trademark"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # unitsPerEm
+ info = dict(fontInfoVersion2)
+ info["unitsPerEm"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # descender
+ info = dict(fontInfoVersion2)
+ info["descender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # xHeight
+ info = dict(fontInfoVersion2)
+ info["xHeight"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # capHeight
+ info = dict(fontInfoVersion2)
+ info["capHeight"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # ascender
+ info = dict(fontInfoVersion2)
+ info["ascender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # italicAngle
+ info = dict(fontInfoVersion2)
+ info["italicAngle"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- def testGenericRead(self):
- # familyName
- info = dict(fontInfoVersion2)
- info["familyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleName
- info = dict(fontInfoVersion2)
- info["styleName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleMapFamilyName
- info = dict(fontInfoVersion2)
- info["styleMapFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleMapStyleName
- ## not a string
- info = dict(fontInfoVersion2)
- info["styleMapStyleName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["styleMapStyleName"] = "REGULAR"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # versionMajor
- info = dict(fontInfoVersion2)
- info["versionMajor"] = "1"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # versionMinor
- info = dict(fontInfoVersion2)
- info["versionMinor"] = "0"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # copyright
- info = dict(fontInfoVersion2)
- info["copyright"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # trademark
- info = dict(fontInfoVersion2)
- info["trademark"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # unitsPerEm
- info = dict(fontInfoVersion2)
- info["unitsPerEm"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # descender
- info = dict(fontInfoVersion2)
- info["descender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # xHeight
- info = dict(fontInfoVersion2)
- info["xHeight"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # capHeight
- info = dict(fontInfoVersion2)
- info["capHeight"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # ascender
- info = dict(fontInfoVersion2)
- info["ascender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # italicAngle
- info = dict(fontInfoVersion2)
- info["italicAngle"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ def testHeadRead(self):
+ # openTypeHeadCreated
+ ## not a string
+ info = dict(fontInfoVersion2)
+ info["openTypeHeadCreated"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## invalid format
+ info = dict(fontInfoVersion2)
+ info["openTypeHeadCreated"] = "2000-Jan-01 00:00:00"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHeadLowestRecPPEM
+ info = dict(fontInfoVersion2)
+ info["openTypeHeadLowestRecPPEM"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHeadFlags
+ info = dict(fontInfoVersion2)
+ info["openTypeHeadFlags"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- def testHeadRead(self):
- # openTypeHeadCreated
- ## not a string
- info = dict(fontInfoVersion2)
- info["openTypeHeadCreated"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## invalid format
- info = dict(fontInfoVersion2)
- info["openTypeHeadCreated"] = "2000-Jan-01 00:00:00"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHeadLowestRecPPEM
- info = dict(fontInfoVersion2)
- info["openTypeHeadLowestRecPPEM"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHeadFlags
- info = dict(fontInfoVersion2)
- info["openTypeHeadFlags"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ def testHheaRead(self):
+ # openTypeHheaAscender
+ info = dict(fontInfoVersion2)
+ info["openTypeHheaAscender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaDescender
+ info = dict(fontInfoVersion2)
+ info["openTypeHheaDescender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaLineGap
+ info = dict(fontInfoVersion2)
+ info["openTypeHheaLineGap"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaCaretSlopeRise
+ info = dict(fontInfoVersion2)
+ info["openTypeHheaCaretSlopeRise"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaCaretSlopeRun
+ info = dict(fontInfoVersion2)
+ info["openTypeHheaCaretSlopeRun"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaCaretOffset
+ info = dict(fontInfoVersion2)
+ info["openTypeHheaCaretOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- def testHheaRead(self):
- # openTypeHheaAscender
- info = dict(fontInfoVersion2)
- info["openTypeHheaAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaDescender
- info = dict(fontInfoVersion2)
- info["openTypeHheaDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaLineGap
- info = dict(fontInfoVersion2)
- info["openTypeHheaLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretSlopeRise
- info = dict(fontInfoVersion2)
- info["openTypeHheaCaretSlopeRise"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretSlopeRun
- info = dict(fontInfoVersion2)
- info["openTypeHheaCaretSlopeRun"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretOffset
- info = dict(fontInfoVersion2)
- info["openTypeHheaCaretOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ def testNameRead(self):
+ # openTypeNameDesigner
+ info = dict(fontInfoVersion2)
+ info["openTypeNameDesigner"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameDesignerURL
+ info = dict(fontInfoVersion2)
+ info["openTypeNameDesignerURL"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameManufacturer
+ info = dict(fontInfoVersion2)
+ info["openTypeNameManufacturer"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameManufacturerURL
+ info = dict(fontInfoVersion2)
+ info["openTypeNameManufacturerURL"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameLicense
+ info = dict(fontInfoVersion2)
+ info["openTypeNameLicense"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameLicenseURL
+ info = dict(fontInfoVersion2)
+ info["openTypeNameLicenseURL"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameVersion
+ info = dict(fontInfoVersion2)
+ info["openTypeNameVersion"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameUniqueID
+ info = dict(fontInfoVersion2)
+ info["openTypeNameUniqueID"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameDescription
+ info = dict(fontInfoVersion2)
+ info["openTypeNameDescription"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNamePreferredFamilyName
+ info = dict(fontInfoVersion2)
+ info["openTypeNamePreferredFamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNamePreferredSubfamilyName
+ info = dict(fontInfoVersion2)
+ info["openTypeNamePreferredSubfamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameCompatibleFullName
+ info = dict(fontInfoVersion2)
+ info["openTypeNameCompatibleFullName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameSampleText
+ info = dict(fontInfoVersion2)
+ info["openTypeNameSampleText"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameWWSFamilyName
+ info = dict(fontInfoVersion2)
+ info["openTypeNameWWSFamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameWWSSubfamilyName
+ info = dict(fontInfoVersion2)
+ info["openTypeNameWWSSubfamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- def testNameRead(self):
- # openTypeNameDesigner
- info = dict(fontInfoVersion2)
- info["openTypeNameDesigner"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameDesignerURL
- info = dict(fontInfoVersion2)
- info["openTypeNameDesignerURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameManufacturer
- info = dict(fontInfoVersion2)
- info["openTypeNameManufacturer"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameManufacturerURL
- info = dict(fontInfoVersion2)
- info["openTypeNameManufacturerURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameLicense
- info = dict(fontInfoVersion2)
- info["openTypeNameLicense"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameLicenseURL
- info = dict(fontInfoVersion2)
- info["openTypeNameLicenseURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameVersion
- info = dict(fontInfoVersion2)
- info["openTypeNameVersion"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameUniqueID
- info = dict(fontInfoVersion2)
- info["openTypeNameUniqueID"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameDescription
- info = dict(fontInfoVersion2)
- info["openTypeNameDescription"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNamePreferredFamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNamePreferredFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNamePreferredSubfamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNamePreferredSubfamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameCompatibleFullName
- info = dict(fontInfoVersion2)
- info["openTypeNameCompatibleFullName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameSampleText
- info = dict(fontInfoVersion2)
- info["openTypeNameSampleText"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameWWSFamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNameWWSFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameWWSSubfamilyName
- info = dict(fontInfoVersion2)
- info["openTypeNameWWSSubfamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ def testOS2Read(self):
+ # openTypeOS2WidthClass
+ ## not an int
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2WidthClass"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out or range
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2WidthClass"] = 15
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2WeightClass
+ info = dict(fontInfoVersion2)
+ ## not an int
+ info["openTypeOS2WeightClass"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info["openTypeOS2WeightClass"] = -50
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2Selection
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2Selection"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2VendorID
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2VendorID"] = 1234
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2Panose
+ ## not an int
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too few values
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2Panose"] = [0, 1, 2, 3]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2FamilyClass
+ ## not an int
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2FamilyClass"] = [1, str(1)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too few values
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2FamilyClass"] = [1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2FamilyClass"] = [1, 1, 1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2FamilyClass"] = [1, 201]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2UnicodeRanges
+ ## not an int
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2UnicodeRanges"] = ["0"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2UnicodeRanges"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2CodePageRanges
+ ## not an int
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2CodePageRanges"] = ["0"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2CodePageRanges"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2TypoAscender
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2TypoAscender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2TypoDescender
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2TypoDescender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2TypoLineGap
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2TypoLineGap"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2WinAscent
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2WinAscent"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2WinDescent
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2WinDescent"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2Type
+ ## not an int
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2Type"] = ["1"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2Type"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptXSize
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SubscriptXSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptYSize
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SubscriptYSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptXOffset
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SubscriptXOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptYOffset
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SubscriptYOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptXSize
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SuperscriptXSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptYSize
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SuperscriptYSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptXOffset
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SuperscriptXOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptYOffset
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2SuperscriptYOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2StrikeoutSize
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2StrikeoutSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2StrikeoutPosition
+ info = dict(fontInfoVersion2)
+ info["openTypeOS2StrikeoutPosition"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- def testOS2Read(self):
- # openTypeOS2WidthClass
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2WidthClass"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out or range
- info = dict(fontInfoVersion2)
- info["openTypeOS2WidthClass"] = 15
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WeightClass
- info = dict(fontInfoVersion2)
- ## not an int
- info["openTypeOS2WeightClass"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info["openTypeOS2WeightClass"] = -50
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Selection
- info = dict(fontInfoVersion2)
- info["openTypeOS2Selection"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2VendorID
- info = dict(fontInfoVersion2)
- info["openTypeOS2VendorID"] = 1234
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Panose
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too few values
- info = dict(fontInfoVersion2)
- info["openTypeOS2Panose"] = [0, 1, 2, 3]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2FamilyClass
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1, str(1)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too few values
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1, 1, 1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2FamilyClass"] = [1, 201]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2UnicodeRanges
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2UnicodeRanges"] = ["0"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2UnicodeRanges"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2CodePageRanges
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2CodePageRanges"] = ["0"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2CodePageRanges"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoAscender
- info = dict(fontInfoVersion2)
- info["openTypeOS2TypoAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoDescender
- info = dict(fontInfoVersion2)
- info["openTypeOS2TypoDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoLineGap
- info = dict(fontInfoVersion2)
- info["openTypeOS2TypoLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WinAscent
- info = dict(fontInfoVersion2)
- info["openTypeOS2WinAscent"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WinDescent
- info = dict(fontInfoVersion2)
- info["openTypeOS2WinDescent"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Type
- ## not an int
- info = dict(fontInfoVersion2)
- info["openTypeOS2Type"] = ["1"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion2)
- info["openTypeOS2Type"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptXSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptXSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptYSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptYSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptXOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptXOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptYOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SubscriptYOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptXSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptXSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptYSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptYSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptXOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptXOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptYOffset
- info = dict(fontInfoVersion2)
- info["openTypeOS2SuperscriptYOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2StrikeoutSize
- info = dict(fontInfoVersion2)
- info["openTypeOS2StrikeoutSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2StrikeoutPosition
- info = dict(fontInfoVersion2)
- info["openTypeOS2StrikeoutPosition"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ def testVheaRead(self):
+ # openTypeVheaVertTypoAscender
+ info = dict(fontInfoVersion2)
+ info["openTypeVheaVertTypoAscender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaVertTypoDescender
+ info = dict(fontInfoVersion2)
+ info["openTypeVheaVertTypoDescender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaVertTypoLineGap
+ info = dict(fontInfoVersion2)
+ info["openTypeVheaVertTypoLineGap"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaCaretSlopeRise
+ info = dict(fontInfoVersion2)
+ info["openTypeVheaCaretSlopeRise"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaCaretSlopeRun
+ info = dict(fontInfoVersion2)
+ info["openTypeVheaCaretSlopeRun"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaCaretOffset
+ info = dict(fontInfoVersion2)
+ info["openTypeVheaCaretOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- def testVheaRead(self):
- # openTypeVheaVertTypoAscender
- info = dict(fontInfoVersion2)
- info["openTypeVheaVertTypoAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaVertTypoDescender
- info = dict(fontInfoVersion2)
- info["openTypeVheaVertTypoDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaVertTypoLineGap
- info = dict(fontInfoVersion2)
- info["openTypeVheaVertTypoLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretSlopeRise
- info = dict(fontInfoVersion2)
- info["openTypeVheaCaretSlopeRise"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretSlopeRun
- info = dict(fontInfoVersion2)
- info["openTypeVheaCaretSlopeRun"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretOffset
- info = dict(fontInfoVersion2)
- info["openTypeVheaCaretOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ def testFONDRead(self):
+ # macintoshFONDFamilyID
+ info = dict(fontInfoVersion2)
+ info["macintoshFONDFamilyID"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # macintoshFONDName
+ info = dict(fontInfoVersion2)
+ info["macintoshFONDName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- def testFONDRead(self):
- # macintoshFONDFamilyID
- info = dict(fontInfoVersion2)
- info["macintoshFONDFamilyID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # macintoshFONDName
- info = dict(fontInfoVersion2)
- info["macintoshFONDName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testPostscriptRead(self):
- # postscriptFontName
- info = dict(fontInfoVersion2)
- info["postscriptFontName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptFullName
- info = dict(fontInfoVersion2)
- info["postscriptFullName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptSlantAngle
- info = dict(fontInfoVersion2)
- info["postscriptSlantAngle"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptUniqueID
- info = dict(fontInfoVersion2)
- info["postscriptUniqueID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptUnderlineThickness
- info = dict(fontInfoVersion2)
- info["postscriptUnderlineThickness"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptUnderlinePosition
- info = dict(fontInfoVersion2)
- info["postscriptUnderlinePosition"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptIsFixedPitch
- info = dict(fontInfoVersion2)
- info["postscriptIsFixedPitch"] = 2
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueValues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptBlueValues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptBlueValues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptBlueValues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptOtherBlues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptOtherBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptOtherBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptFamilyBlues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptFamilyBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptFamilyBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptFamilyBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptFamilyOtherBlues
- ## not a list
- info = dict(fontInfoVersion2)
- info["postscriptFamilyOtherBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion2)
- info["postscriptFamilyOtherBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptFamilyOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptStemSnapH
- ## not list
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapH"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapH"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptStemSnapV
- ## not list
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapV"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion2)
- info["postscriptStemSnapV"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueFuzz
- info = dict(fontInfoVersion2)
- info["postscriptBlueFuzz"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueShift
- info = dict(fontInfoVersion2)
- info["postscriptBlueShift"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueScale
- info = dict(fontInfoVersion2)
- info["postscriptBlueScale"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptForceBold
- info = dict(fontInfoVersion2)
- info["postscriptForceBold"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptDefaultWidthX
- info = dict(fontInfoVersion2)
- info["postscriptDefaultWidthX"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptNominalWidthX
- info = dict(fontInfoVersion2)
- info["postscriptNominalWidthX"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptWeightName
- info = dict(fontInfoVersion2)
- info["postscriptWeightName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptDefaultCharacter
- info = dict(fontInfoVersion2)
- info["postscriptDefaultCharacter"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptWindowsCharacterSet
- info = dict(fontInfoVersion2)
- info["postscriptWindowsCharacterSet"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # macintoshFONDFamilyID
- info = dict(fontInfoVersion2)
- info["macintoshFONDFamilyID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # macintoshFONDName
- info = dict(fontInfoVersion2)
- info["macintoshFONDName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ def testPostscriptRead(self):
+ # postscriptFontName
+ info = dict(fontInfoVersion2)
+ info["postscriptFontName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # postscriptFullName
+ info = dict(fontInfoVersion2)
+ info["postscriptFullName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # postscriptSlantAngle
+ info = dict(fontInfoVersion2)
+ info["postscriptSlantAngle"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # postscriptUniqueID
+ info = dict(fontInfoVersion2)
+ info["postscriptUniqueID"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptUnderlineThickness
+ info = dict(fontInfoVersion2)
+ info["postscriptUnderlineThickness"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptUnderlinePosition
+ info = dict(fontInfoVersion2)
+ info["postscriptUnderlinePosition"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptIsFixedPitch
+ info = dict(fontInfoVersion2)
+ info["postscriptIsFixedPitch"] = 2
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueValues
+ ## not a list
+ info = dict(fontInfoVersion2)
+ info["postscriptBlueValues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion2)
+ info["postscriptBlueValues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["postscriptBlueValues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptOtherBlues
+ ## not a list
+ info = dict(fontInfoVersion2)
+ info["postscriptOtherBlues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion2)
+ info["postscriptOtherBlues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["postscriptOtherBlues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptFamilyBlues
+ ## not a list
+ info = dict(fontInfoVersion2)
+ info["postscriptFamilyBlues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion2)
+ info["postscriptFamilyBlues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["postscriptFamilyBlues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptFamilyOtherBlues
+ ## not a list
+ info = dict(fontInfoVersion2)
+ info["postscriptFamilyOtherBlues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion2)
+ info["postscriptFamilyOtherBlues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["postscriptFamilyOtherBlues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptStemSnapH
+ ## not list
+ info = dict(fontInfoVersion2)
+ info["postscriptStemSnapH"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["postscriptStemSnapH"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptStemSnapV
+ ## not list
+ info = dict(fontInfoVersion2)
+ info["postscriptStemSnapV"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion2)
+ info["postscriptStemSnapV"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueFuzz
+ info = dict(fontInfoVersion2)
+ info["postscriptBlueFuzz"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueShift
+ info = dict(fontInfoVersion2)
+ info["postscriptBlueShift"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueScale
+ info = dict(fontInfoVersion2)
+ info["postscriptBlueScale"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptForceBold
+ info = dict(fontInfoVersion2)
+ info["postscriptForceBold"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptDefaultWidthX
+ info = dict(fontInfoVersion2)
+ info["postscriptDefaultWidthX"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptNominalWidthX
+ info = dict(fontInfoVersion2)
+ info["postscriptNominalWidthX"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptWeightName
+ info = dict(fontInfoVersion2)
+ info["postscriptWeightName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptDefaultCharacter
+ info = dict(fontInfoVersion2)
+ info["postscriptDefaultCharacter"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptWindowsCharacterSet
+ info = dict(fontInfoVersion2)
+ info["postscriptWindowsCharacterSet"] = -1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # macintoshFONDFamilyID
+ info = dict(fontInfoVersion2)
+ info["macintoshFONDFamilyID"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # macintoshFONDName
+ info = dict(fontInfoVersion2)
+ info["macintoshFONDName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
class WriteFontInfoVersion2TestCase(unittest.TestCase):
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.dstDir = os.path.join(self.tempDir, "test.ufo")
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.dstDir = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
- def makeInfoObject(self):
- infoObject = TestInfoObject()
- for attr, value in list(fontInfoVersion2.items()):
- setattr(infoObject, attr, value)
- return infoObject
+ def makeInfoObject(self):
+ infoObject = TestInfoObject()
+ for attr, value in list(fontInfoVersion2.items()):
+ setattr(infoObject, attr, value)
+ return infoObject
- def readPlist(self):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- with open(path, "rb") as f:
- plist = plistlib.load(f)
- return plist
+ def readPlist(self):
+ path = os.path.join(self.dstDir, "fontinfo.plist")
+ with open(path, "rb") as f:
+ plist = plistlib.load(f)
+ return plist
- def testWrite(self):
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=2)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- for attr, originalValue in list(fontInfoVersion2.items()):
- newValue = writtenData[attr]
- self.assertEqual(newValue, originalValue)
+ def testWrite(self):
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ writer.writeInfo(infoObject)
+ writtenData = self.readPlist()
+ for attr, originalValue in list(fontInfoVersion2.items()):
+ newValue = writtenData[attr]
+ self.assertEqual(newValue, originalValue)
- def testGenericWrite(self):
- # familyName
- infoObject = self.makeInfoObject()
- infoObject.familyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # styleName
- infoObject = self.makeInfoObject()
- infoObject.styleName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # styleMapFamilyName
- infoObject = self.makeInfoObject()
- infoObject.styleMapFamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # styleMapStyleName
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = "REGULAR"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # versionMajor
- infoObject = self.makeInfoObject()
- infoObject.versionMajor = "1"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # versionMinor
- infoObject = self.makeInfoObject()
- infoObject.versionMinor = "0"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # copyright
- infoObject = self.makeInfoObject()
- infoObject.copyright = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # trademark
- infoObject = self.makeInfoObject()
- infoObject.trademark = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # unitsPerEm
- infoObject = self.makeInfoObject()
- infoObject.unitsPerEm = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # descender
- infoObject = self.makeInfoObject()
- infoObject.descender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # xHeight
- infoObject = self.makeInfoObject()
- infoObject.xHeight = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # capHeight
- infoObject = self.makeInfoObject()
- infoObject.capHeight = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # ascender
- infoObject = self.makeInfoObject()
- infoObject.ascender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # italicAngle
- infoObject = self.makeInfoObject()
- infoObject.italicAngle = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testGenericWrite(self):
+ # familyName
+ infoObject = self.makeInfoObject()
+ infoObject.familyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # styleName
+ infoObject = self.makeInfoObject()
+ infoObject.styleName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # styleMapFamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.styleMapFamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # styleMapStyleName
+ ## not a string
+ infoObject = self.makeInfoObject()
+ infoObject.styleMapStyleName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.styleMapStyleName = "REGULAR"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # versionMajor
+ infoObject = self.makeInfoObject()
+ infoObject.versionMajor = "1"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # versionMinor
+ infoObject = self.makeInfoObject()
+ infoObject.versionMinor = "0"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # copyright
+ infoObject = self.makeInfoObject()
+ infoObject.copyright = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # trademark
+ infoObject = self.makeInfoObject()
+ infoObject.trademark = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # unitsPerEm
+ infoObject = self.makeInfoObject()
+ infoObject.unitsPerEm = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # descender
+ infoObject = self.makeInfoObject()
+ infoObject.descender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # xHeight
+ infoObject = self.makeInfoObject()
+ infoObject.xHeight = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # capHeight
+ infoObject = self.makeInfoObject()
+ infoObject.capHeight = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # ascender
+ infoObject = self.makeInfoObject()
+ infoObject.ascender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # italicAngle
+ infoObject = self.makeInfoObject()
+ infoObject.italicAngle = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- def testHeadWrite(self):
- # openTypeHeadCreated
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadCreated = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## invalid format
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadCreated = "2000-Jan-01 00:00:00"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHeadLowestRecPPEM
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadLowestRecPPEM = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHeadFlags
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadFlags = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testHeadWrite(self):
+ # openTypeHeadCreated
+ ## not a string
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadCreated = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## invalid format
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadCreated = "2000-Jan-01 00:00:00"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeHeadLowestRecPPEM
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadLowestRecPPEM = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeHeadFlags
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadFlags = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- def testHheaWrite(self):
- # openTypeHheaAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaAscender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaDescender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaLineGap = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaCaretSlopeRise
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretSlopeRise = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaCaretSlopeRun
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretSlopeRun = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeHheaCaretOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testHheaWrite(self):
+ # openTypeHheaAscender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaAscender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeHheaDescender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaDescender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeHheaLineGap
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaLineGap = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeHheaCaretSlopeRise
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaCaretSlopeRise = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeHheaCaretSlopeRun
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaCaretSlopeRun = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeHheaCaretOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaCaretOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- def testNameWrite(self):
- # openTypeNameDesigner
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDesigner = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameDesignerURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDesignerURL = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameManufacturer
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameManufacturer = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameManufacturerURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameManufacturerURL = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameLicense
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameLicense = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameLicenseURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameLicenseURL = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameVersion
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameVersion = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameUniqueID
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameUniqueID = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameDescription
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDescription = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNamePreferredFamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNamePreferredFamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNamePreferredSubfamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNamePreferredSubfamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameCompatibleFullName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameCompatibleFullName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameSampleText
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameSampleText = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameWWSFamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameWWSFamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeNameWWSSubfamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameWWSSubfamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testNameWrite(self):
+ # openTypeNameDesigner
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameDesigner = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameDesignerURL
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameDesignerURL = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameManufacturer
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameManufacturer = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameManufacturerURL
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameManufacturerURL = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameLicense
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameLicense = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameLicenseURL
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameLicenseURL = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameVersion
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameVersion = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameUniqueID
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameUniqueID = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameDescription
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameDescription = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNamePreferredFamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNamePreferredFamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNamePreferredSubfamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNamePreferredSubfamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameCompatibleFullName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameCompatibleFullName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameSampleText
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameSampleText = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameWWSFamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameWWSFamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeNameWWSSubfamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameWWSSubfamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- def testOS2Write(self):
- # openTypeOS2WidthClass
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out or range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = 15
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2WeightClass
- infoObject = self.makeInfoObject()
- ## not an int
- infoObject.openTypeOS2WeightClass = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject.openTypeOS2WeightClass = -50
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2Selection
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Selection = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2VendorID
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2VendorID = 1234
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2Panose
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too few values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2FamilyClass
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [0, str(1)]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too few values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1, 1, 1]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1, 20]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2UnicodeRanges
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2UnicodeRanges = ["0"]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2UnicodeRanges = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2CodePageRanges
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2CodePageRanges = ["0"]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2CodePageRanges = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2TypoAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoAscender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2TypoDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoDescender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2TypoLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoLineGap = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2WinAscent
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinAscent = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2WinDescent
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinDescent = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2Type
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Type = ["1"]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Type = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptXSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptXSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptYSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptYSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptXOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptXOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SubscriptYOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptYOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptXSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptXSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptYSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptYSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptXOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptXOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2SuperscriptYOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptYOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2StrikeoutSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2StrikeoutSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeOS2StrikeoutPosition
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2StrikeoutPosition = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testOS2Write(self):
+ # openTypeOS2WidthClass
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WidthClass = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## out or range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WidthClass = 15
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2WeightClass
+ infoObject = self.makeInfoObject()
+ ## not an int
+ infoObject.openTypeOS2WeightClass = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## out of range
+ infoObject.openTypeOS2WeightClass = -50
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2Selection
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Selection = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2VendorID
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2VendorID = 1234
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2Panose
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too few values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Panose = [0, 1, 2, 3]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2FamilyClass
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [0, str(1)]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too few values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [1]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [1, 1, 1]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [1, 20]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2UnicodeRanges
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2UnicodeRanges = ["0"]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2UnicodeRanges = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2CodePageRanges
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2CodePageRanges = ["0"]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2CodePageRanges = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2TypoAscender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2TypoAscender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2TypoDescender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2TypoDescender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2TypoLineGap
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2TypoLineGap = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2WinAscent
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WinAscent = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2WinDescent
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WinDescent = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2Type
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Type = ["1"]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Type = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SubscriptXSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptXSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SubscriptYSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptYSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SubscriptXOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptXOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SubscriptYOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptYOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SuperscriptXSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptXSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SuperscriptYSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptYSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SuperscriptXOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptXOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2SuperscriptYOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptYOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2StrikeoutSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2StrikeoutSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeOS2StrikeoutPosition
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2StrikeoutPosition = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- def testVheaWrite(self):
- # openTypeVheaVertTypoAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoAscender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaVertTypoDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoDescender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaVertTypoLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoLineGap = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaCaretSlopeRise
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretSlopeRise = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaCaretSlopeRun
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretSlopeRun = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # openTypeVheaCaretOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testVheaWrite(self):
+ # openTypeVheaVertTypoAscender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaVertTypoAscender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeVheaVertTypoDescender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaVertTypoDescender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeVheaVertTypoLineGap
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaVertTypoLineGap = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeVheaCaretSlopeRise
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaCaretSlopeRise = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeVheaCaretSlopeRun
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaCaretSlopeRun = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # openTypeVheaCaretOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaCaretOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- def testFONDWrite(self):
- # macintoshFONDFamilyID
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDFamilyID = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # macintoshFONDName
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testFONDWrite(self):
+ # macintoshFONDFamilyID
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDFamilyID = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # macintoshFONDName
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- def testPostscriptWrite(self):
- # postscriptFontName
- infoObject = self.makeInfoObject()
- infoObject.postscriptFontName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptFullName
- infoObject = self.makeInfoObject()
- infoObject.postscriptFullName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptSlantAngle
- infoObject = self.makeInfoObject()
- infoObject.postscriptSlantAngle = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptUniqueID
- infoObject = self.makeInfoObject()
- infoObject.postscriptUniqueID = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptUnderlineThickness
- infoObject = self.makeInfoObject()
- infoObject.postscriptUnderlineThickness = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptUnderlinePosition
- infoObject = self.makeInfoObject()
- infoObject.postscriptUnderlinePosition = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptIsFixedPitch
- infoObject = self.makeInfoObject()
- infoObject.postscriptIsFixedPitch = 2
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueValues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptOtherBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptFamilyBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptFamilyOtherBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptStemSnapH
- ## not list
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapH = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapH = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptStemSnapV
- ## not list
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapV = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapV = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueFuzz
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueFuzz = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueShift
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueShift = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptBlueScale
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueScale = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptForceBold
- infoObject = self.makeInfoObject()
- infoObject.postscriptForceBold = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptDefaultWidthX
- infoObject = self.makeInfoObject()
- infoObject.postscriptDefaultWidthX = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptNominalWidthX
- infoObject = self.makeInfoObject()
- infoObject.postscriptNominalWidthX = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptWeightName
- infoObject = self.makeInfoObject()
- infoObject.postscriptWeightName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptDefaultCharacter
- infoObject = self.makeInfoObject()
- infoObject.postscriptDefaultCharacter = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # postscriptWindowsCharacterSet
- infoObject = self.makeInfoObject()
- infoObject.postscriptWindowsCharacterSet = -1
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # macintoshFONDFamilyID
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDFamilyID = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- # macintoshFONDName
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDName = 123
- writer = UFOWriter(self.dstDir, formatVersion=2)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ def testPostscriptWrite(self):
+ # postscriptFontName
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFontName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptFullName
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFullName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptSlantAngle
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptSlantAngle = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptUniqueID
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptUniqueID = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptUnderlineThickness
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptUnderlineThickness = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptUnderlinePosition
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptUnderlinePosition = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptIsFixedPitch
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptIsFixedPitch = 2
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptBlueValues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueValues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueValues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueValues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptOtherBlues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptOtherBlues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptOtherBlues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptOtherBlues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptFamilyBlues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyBlues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyBlues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyBlues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptFamilyOtherBlues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyOtherBlues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyOtherBlues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyOtherBlues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptStemSnapH
+ ## not list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapH = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapH = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptStemSnapV
+ ## not list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapV = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapV = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptBlueFuzz
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueFuzz = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptBlueShift
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueShift = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptBlueScale
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueScale = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptForceBold
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptForceBold = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptDefaultWidthX
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptDefaultWidthX = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptNominalWidthX
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptNominalWidthX = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptWeightName
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptWeightName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptDefaultCharacter
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptDefaultCharacter = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # postscriptWindowsCharacterSet
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptWindowsCharacterSet = -1
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # macintoshFONDFamilyID
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDFamilyID = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ # macintoshFONDName
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
diff --git a/Tests/ufoLib/UFO3_test.py b/Tests/ufoLib/UFO3_test.py
index c4218023..95a51c4d 100644
--- a/Tests/ufoLib/UFO3_test.py
+++ b/Tests/ufoLib/UFO3_test.py
@@ -9,4165 +9,4519 @@ from fontTools.misc import plistlib
from .testSupport import fontInfoVersion3
-class TestInfoObject: pass
+class TestInfoObject:
+ pass
# --------------
# fontinfo.plist
# --------------
-class ReadFontInfoVersion3TestCase(unittest.TestCase):
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
- metaInfo = {
- "creator": "test",
- "formatVersion": 3
- }
- path = os.path.join(self.dstDir, "metainfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(metaInfo, f)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def _writeInfoToPlist(self, info):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(info, f)
-
- def testRead(self):
- originalData = dict(fontInfoVersion3)
- self._writeInfoToPlist(originalData)
- infoObject = TestInfoObject()
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(infoObject)
- readData = {}
- for attr in list(fontInfoVersion3.keys()):
- readData[attr] = getattr(infoObject, attr)
- self.assertEqual(originalData, readData)
-
- def testGenericRead(self):
- # familyName
- info = dict(fontInfoVersion3)
- info["familyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleName
- info = dict(fontInfoVersion3)
- info["styleName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleMapFamilyName
- info = dict(fontInfoVersion3)
- info["styleMapFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # styleMapStyleName
- ## not a string
- info = dict(fontInfoVersion3)
- info["styleMapStyleName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion3)
- info["styleMapStyleName"] = "REGULAR"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # versionMajor
- info = dict(fontInfoVersion3)
- info["versionMajor"] = "1"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # versionMinor
- info = dict(fontInfoVersion3)
- info["versionMinor"] = "0"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["versionMinor"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # copyright
- info = dict(fontInfoVersion3)
- info["copyright"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # trademark
- info = dict(fontInfoVersion3)
- info["trademark"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # unitsPerEm
- info = dict(fontInfoVersion3)
- info["unitsPerEm"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["unitsPerEm"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["unitsPerEm"] = -1.0
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # descender
- info = dict(fontInfoVersion3)
- info["descender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # xHeight
- info = dict(fontInfoVersion3)
- info["xHeight"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # capHeight
- info = dict(fontInfoVersion3)
- info["capHeight"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # ascender
- info = dict(fontInfoVersion3)
- info["ascender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # italicAngle
- info = dict(fontInfoVersion3)
- info["italicAngle"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testGaspRead(self):
- # not a list
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # empty list
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = []
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- # not a dict
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = ["abc"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # dict not properly formatted
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = [dict(rangeMaxPPEM=0xFFFF, notTheRightKey=1)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = [dict(notTheRightKey=1, rangeGaspBehavior=[0])]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # not an int for ppem
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = [dict(rangeMaxPPEM="abc", rangeGaspBehavior=[0]), dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # not a list for behavior
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = [dict(rangeMaxPPEM=10, rangeGaspBehavior="abc"), dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # invalid behavior value
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = [dict(rangeMaxPPEM=10, rangeGaspBehavior=[-1]), dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # not sorted
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = [dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]), dict(rangeMaxPPEM=10, rangeGaspBehavior=[0])]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # no 0xFFFF
- info = dict(fontInfoVersion3)
- info["openTypeGaspRangeRecords"] = [dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]), dict(rangeMaxPPEM=20, rangeGaspBehavior=[0])]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
-
- def testHeadRead(self):
- # openTypeHeadCreated
- ## not a string
- info = dict(fontInfoVersion3)
- info["openTypeHeadCreated"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## invalid format
- info = dict(fontInfoVersion3)
- info["openTypeHeadCreated"] = "2000-Jan-01 00:00:00"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHeadLowestRecPPEM
- info = dict(fontInfoVersion3)
- info["openTypeHeadLowestRecPPEM"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeHeadLowestRecPPEM"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHeadFlags
- info = dict(fontInfoVersion3)
- info["openTypeHeadFlags"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testHheaRead(self):
- # openTypeHheaAscender
- info = dict(fontInfoVersion3)
- info["openTypeHheaAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaDescender
- info = dict(fontInfoVersion3)
- info["openTypeHheaDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaLineGap
- info = dict(fontInfoVersion3)
- info["openTypeHheaLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretSlopeRise
- info = dict(fontInfoVersion3)
- info["openTypeHheaCaretSlopeRise"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretSlopeRun
- info = dict(fontInfoVersion3)
- info["openTypeHheaCaretSlopeRun"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeHheaCaretOffset
- info = dict(fontInfoVersion3)
- info["openTypeHheaCaretOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testNameRead(self):
- # openTypeNameDesigner
- info = dict(fontInfoVersion3)
- info["openTypeNameDesigner"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameDesignerURL
- info = dict(fontInfoVersion3)
- info["openTypeNameDesignerURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameManufacturer
- info = dict(fontInfoVersion3)
- info["openTypeNameManufacturer"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameManufacturerURL
- info = dict(fontInfoVersion3)
- info["openTypeNameManufacturerURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameLicense
- info = dict(fontInfoVersion3)
- info["openTypeNameLicense"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameLicenseURL
- info = dict(fontInfoVersion3)
- info["openTypeNameLicenseURL"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameVersion
- info = dict(fontInfoVersion3)
- info["openTypeNameVersion"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameUniqueID
- info = dict(fontInfoVersion3)
- info["openTypeNameUniqueID"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameDescription
- info = dict(fontInfoVersion3)
- info["openTypeNameDescription"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNamePreferredFamilyName
- info = dict(fontInfoVersion3)
- info["openTypeNamePreferredFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNamePreferredSubfamilyName
- info = dict(fontInfoVersion3)
- info["openTypeNamePreferredSubfamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameCompatibleFullName
- info = dict(fontInfoVersion3)
- info["openTypeNameCompatibleFullName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameSampleText
- info = dict(fontInfoVersion3)
- info["openTypeNameSampleText"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameWWSFamilyName
- info = dict(fontInfoVersion3)
- info["openTypeNameWWSFamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameWWSSubfamilyName
- info = dict(fontInfoVersion3)
- info["openTypeNameWWSSubfamilyName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeNameRecords
- ## not a list
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## not a dict
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = ["abc"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## invalid dict structure
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [dict(foo="bar")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## incorrect keys
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record.", foo="bar")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(platformID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, languageID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, encodingID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1)
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## invalid values
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID="1", platformID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID="1", encodingID=1, languageID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, encodingID="1", languageID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, encodingID=1, languageID="1", string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string=1)
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## duplicate
- info = dict(fontInfoVersion3)
- info["openTypeNameRecords"] = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record."),
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
-
- def testOS2Read(self):
- # openTypeOS2WidthClass
- ## not an int
- info = dict(fontInfoVersion3)
- info["openTypeOS2WidthClass"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out or range
- info = dict(fontInfoVersion3)
- info["openTypeOS2WidthClass"] = 15
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WeightClass
- info = dict(fontInfoVersion3)
- ## not an int
- info["openTypeOS2WeightClass"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info["openTypeOS2WeightClass"] = -50
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Selection
- info = dict(fontInfoVersion3)
- info["openTypeOS2Selection"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2VendorID
- info = dict(fontInfoVersion3)
- info["openTypeOS2VendorID"] = 1234
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Panose
- ## not an int
- info = dict(fontInfoVersion3)
- info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## negative
- info = dict(fontInfoVersion3)
- info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, -9]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too few values
- info = dict(fontInfoVersion3)
- info["openTypeOS2Panose"] = [0, 1, 2, 3]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2FamilyClass
- ## not an int
- info = dict(fontInfoVersion3)
- info["openTypeOS2FamilyClass"] = [1, str(1)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too few values
- info = dict(fontInfoVersion3)
- info["openTypeOS2FamilyClass"] = [1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["openTypeOS2FamilyClass"] = [1, 1, 1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion3)
- info["openTypeOS2FamilyClass"] = [1, 201]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2UnicodeRanges
- ## not an int
- info = dict(fontInfoVersion3)
- info["openTypeOS2UnicodeRanges"] = ["0"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion3)
- info["openTypeOS2UnicodeRanges"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2CodePageRanges
- ## not an int
- info = dict(fontInfoVersion3)
- info["openTypeOS2CodePageRanges"] = ["0"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion3)
- info["openTypeOS2CodePageRanges"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoAscender
- info = dict(fontInfoVersion3)
- info["openTypeOS2TypoAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoDescender
- info = dict(fontInfoVersion3)
- info["openTypeOS2TypoDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2TypoLineGap
- info = dict(fontInfoVersion3)
- info["openTypeOS2TypoLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WinAscent
- info = dict(fontInfoVersion3)
- info["openTypeOS2WinAscent"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeOS2WinAscent"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2WinDescent
- info = dict(fontInfoVersion3)
- info["openTypeOS2WinDescent"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- info = dict(fontInfoVersion3)
- info["openTypeOS2WinDescent"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2Type
- ## not an int
- info = dict(fontInfoVersion3)
- info["openTypeOS2Type"] = ["1"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- ## out of range
- info = dict(fontInfoVersion3)
- info["openTypeOS2Type"] = [-1]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptXSize
- info = dict(fontInfoVersion3)
- info["openTypeOS2SubscriptXSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptYSize
- info = dict(fontInfoVersion3)
- info["openTypeOS2SubscriptYSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptXOffset
- info = dict(fontInfoVersion3)
- info["openTypeOS2SubscriptXOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SubscriptYOffset
- info = dict(fontInfoVersion3)
- info["openTypeOS2SubscriptYOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptXSize
- info = dict(fontInfoVersion3)
- info["openTypeOS2SuperscriptXSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptYSize
- info = dict(fontInfoVersion3)
- info["openTypeOS2SuperscriptYSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptXOffset
- info = dict(fontInfoVersion3)
- info["openTypeOS2SuperscriptXOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2SuperscriptYOffset
- info = dict(fontInfoVersion3)
- info["openTypeOS2SuperscriptYOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2StrikeoutSize
- info = dict(fontInfoVersion3)
- info["openTypeOS2StrikeoutSize"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeOS2StrikeoutPosition
- info = dict(fontInfoVersion3)
- info["openTypeOS2StrikeoutPosition"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testVheaRead(self):
- # openTypeVheaVertTypoAscender
- info = dict(fontInfoVersion3)
- info["openTypeVheaVertTypoAscender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaVertTypoDescender
- info = dict(fontInfoVersion3)
- info["openTypeVheaVertTypoDescender"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaVertTypoLineGap
- info = dict(fontInfoVersion3)
- info["openTypeVheaVertTypoLineGap"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretSlopeRise
- info = dict(fontInfoVersion3)
- info["openTypeVheaCaretSlopeRise"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretSlopeRun
- info = dict(fontInfoVersion3)
- info["openTypeVheaCaretSlopeRun"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # openTypeVheaCaretOffset
- info = dict(fontInfoVersion3)
- info["openTypeVheaCaretOffset"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testFONDRead(self):
- # macintoshFONDFamilyID
- info = dict(fontInfoVersion3)
- info["macintoshFONDFamilyID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # macintoshFONDName
- info = dict(fontInfoVersion3)
- info["macintoshFONDName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
-
- def testPostscriptRead(self):
- # postscriptFontName
- info = dict(fontInfoVersion3)
- info["postscriptFontName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptFullName
- info = dict(fontInfoVersion3)
- info["postscriptFullName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptSlantAngle
- info = dict(fontInfoVersion3)
- info["postscriptSlantAngle"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
- # postscriptUniqueID
- info = dict(fontInfoVersion3)
- info["postscriptUniqueID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptUnderlineThickness
- info = dict(fontInfoVersion3)
- info["postscriptUnderlineThickness"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptUnderlinePosition
- info = dict(fontInfoVersion3)
- info["postscriptUnderlinePosition"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptIsFixedPitch
- info = dict(fontInfoVersion3)
- info["postscriptIsFixedPitch"] = 2
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueValues
- ## not a list
- info = dict(fontInfoVersion3)
- info["postscriptBlueValues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion3)
- info["postscriptBlueValues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["postscriptBlueValues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptOtherBlues
- ## not a list
- info = dict(fontInfoVersion3)
- info["postscriptOtherBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion3)
- info["postscriptOtherBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["postscriptOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptFamilyBlues
- ## not a list
- info = dict(fontInfoVersion3)
- info["postscriptFamilyBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion3)
- info["postscriptFamilyBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["postscriptFamilyBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptFamilyOtherBlues
- ## not a list
- info = dict(fontInfoVersion3)
- info["postscriptFamilyOtherBlues"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## uneven value count
- info = dict(fontInfoVersion3)
- info["postscriptFamilyOtherBlues"] = [500]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["postscriptFamilyOtherBlues"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptStemSnapH
- ## not list
- info = dict(fontInfoVersion3)
- info["postscriptStemSnapH"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["postscriptStemSnapH"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptStemSnapV
- ## not list
- info = dict(fontInfoVersion3)
- info["postscriptStemSnapV"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many values
- info = dict(fontInfoVersion3)
- info["postscriptStemSnapV"] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueFuzz
- info = dict(fontInfoVersion3)
- info["postscriptBlueFuzz"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueShift
- info = dict(fontInfoVersion3)
- info["postscriptBlueShift"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptBlueScale
- info = dict(fontInfoVersion3)
- info["postscriptBlueScale"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptForceBold
- info = dict(fontInfoVersion3)
- info["postscriptForceBold"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptDefaultWidthX
- info = dict(fontInfoVersion3)
- info["postscriptDefaultWidthX"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptNominalWidthX
- info = dict(fontInfoVersion3)
- info["postscriptNominalWidthX"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptWeightName
- info = dict(fontInfoVersion3)
- info["postscriptWeightName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptDefaultCharacter
- info = dict(fontInfoVersion3)
- info["postscriptDefaultCharacter"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # postscriptWindowsCharacterSet
- info = dict(fontInfoVersion3)
- info["postscriptWindowsCharacterSet"] = -1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # macintoshFONDFamilyID
- info = dict(fontInfoVersion3)
- info["macintoshFONDFamilyID"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # macintoshFONDName
- info = dict(fontInfoVersion3)
- info["macintoshFONDName"] = 123
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
-
- def testWOFFRead(self):
- # woffMajorVersion
- info = dict(fontInfoVersion3)
- info["woffMajorVersion"] = 1.0
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["woffMajorVersion"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # woffMinorVersion
- info = dict(fontInfoVersion3)
- info["woffMinorVersion"] = 1.0
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["woffMinorVersion"] = "abc"
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # woffMetadataUniqueID
- ## none
- info = dict(fontInfoVersion3)
- del info["woffMetadataUniqueID"]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## not a dict
- info = dict(fontInfoVersion3)
- info["woffMetadataUniqueID"] = 1
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## unknown key
- info = dict(fontInfoVersion3)
- info["woffMetadataUniqueID"] = dict(id="foo", notTheRightKey=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## no id
- info = dict(fontInfoVersion3)
- info["woffMetadataUniqueID"] = dict()
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## not a string for id
- info = dict(fontInfoVersion3)
- info["woffMetadataUniqueID"] = dict(id=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## empty string
- info = dict(fontInfoVersion3)
- info["woffMetadataUniqueID"] = dict(id="")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- # woffMetadataVendor
- ## no name
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(url="foo")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## name not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name=1, url="foo")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## name an empty string
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="", url="foo")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## no URL
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="foo")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="foo", url=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## url empty string
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="foo", url="")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## have dir
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="foo", url="bar", dir="ltr")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="foo", url="bar", dir="rtl")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## dir not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="foo", url="bar", dir=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir not ltr or rtl
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = dict(name="foo", url="bar", dir="utd")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## have class
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = {"name" : "foo", "url" : "bar", "class" : "hello"}
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## class not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = {"name" : "foo", "url" : "bar", "class" : 1}
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## class empty string
- info = dict(fontInfoVersion3)
- info["woffMetadataVendor"] = {"name" : "foo", "url" : "bar", "class" : ""}
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- # woffMetadataCredits
- ## no credits attribute
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = {}
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## unknown attribute
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(name="foo")], notTheRightKey=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## not a list
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits="abc")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## no elements in credits
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## credit not a dict
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=["abc"])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## unknown key
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(name="foo", notTheRightKey=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## no name
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(url="foo")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## name not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(name=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(name="foo", url=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## role not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(name="foo", role=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(name="foo", dir=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir not ltr or rtl
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[dict(name="foo", dir="utd")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## class not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCredits"] = dict(credits=[{"name" : "foo", "class" : 1}])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # woffMetadataDescription
- ## no url
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(text="foo")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(text="foo")], url=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## no text
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(url="foo")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text not a list
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text="abc")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item not a dict
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=["abc"])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item unknown key
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(text="foo", notTheRightKey=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item missing text
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(language="foo")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(text=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(text="foo", url=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## language not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(text="foo", language=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir not ltr or rtl
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[dict(text="foo", dir="utd")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## class not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataDescription"] = dict(text=[{"text" : "foo", "class" : 1}])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # woffMetadataLicense
- ## no url
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text="foo")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text="foo")], url=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## id not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text="foo")], id=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## no text
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(url="foo")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## text not a list
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text="abc")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item not a dict
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=["abc"])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item unknown key
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text="foo", notTheRightKey=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item missing text
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(language="foo")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text="foo", url=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## language not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text="foo", language=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir not ltr or rtl
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[dict(text="foo", dir="utd")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## class not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicense"] = dict(text=[{"text" : "foo", "class" : 1}])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # woffMetadataCopyright
- ## unknown attribute
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[dict(text="foo")], notTheRightKey=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## no text
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict()
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text not a list
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text="abc")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item not a dict
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=["abc"])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item unknown key
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[dict(text="foo", notTheRightKey=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item missing text
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[dict(language="foo")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[dict(text=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[dict(text="foo", url=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## language not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[dict(text="foo", language=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir not ltr or rtl
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[dict(text="foo", dir="utd")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## class not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataCopyright"] = dict(text=[{"text" : "foo", "class" : 1}])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # woffMetadataTrademark
- ## unknown attribute
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[dict(text="foo")], notTheRightKey=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## no text
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict()
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text not a list
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text="abc")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item not a dict
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=["abc"])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item unknown key
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[dict(text="foo", notTheRightKey=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text item missing text
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[dict(language="foo")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## text not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[dict(text=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## url not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[dict(text="foo", url=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## language not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[dict(text="foo", language=1)])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir not ltr or rtl
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[dict(text="foo", dir="utd")])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## class not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataTrademark"] = dict(text=[{"text" : "foo", "class" : 1}])
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # woffMetadataLicensee
- ## no name
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = dict()
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## unknown attribute
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = dict(name="foo", notTheRightKey=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## name not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = dict(name=1)
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## dir options
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = dict(name="foo", dir="ltr")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = dict(name="foo", dir="rtl")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## dir not ltr or rtl
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = dict(name="foo", dir="utd")
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## have class
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = {"name" : "foo", "class" : "hello"}
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- reader.readInfo(TestInfoObject())
- ## class not a string
- info = dict(fontInfoVersion3)
- info["woffMetadataLicensee"] = {"name" : "foo", "class" : 1}
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
-
- def testGuidelinesRead(self):
- # x
- ## not an int or float
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x="1")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # y
- ## not an int or float
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(y="1")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # angle
- ## < 0
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, y=0, angle=-1)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## > 360
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, y=0, angle=361)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # name
- ## not a string
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, name=1)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # color
- ## not a string
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color=1)]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## not enough commas
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1 0, 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1 0 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1 0 0 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## not enough parts
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color=", 0, 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1, , 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1, 0, , 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1, 0, 0, ")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color=", , , ")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## not a number in all positions
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="r, 1, 1, 1")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1, g, 1, 1")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1, 1, b, 1")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1, 1, 1, a")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## too many parts
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="1, 0, 0, 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## < 0 in each position
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="-1, 0, 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="0, -1, 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="0, 0, -1, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="0, 0, 0, -1")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- ## > 1 in each position
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="2, 0, 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="0, 2, 0, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="0, 0, 2, 0")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, color="0, 0, 0, 2")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
- # identifier
- ## duplicate
- info = dict(fontInfoVersion3)
- info["guidelines"] = [dict(x=0, identifier="guide1"), dict(y=0, identifier="guide1")]
- self._writeInfoToPlist(info)
- reader = UFOReader(self.dstDir, validate=True)
- self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+class ReadFontInfoVersion3TestCase(unittest.TestCase):
+ def setUp(self):
+ self.dstDir = tempfile.mktemp()
+ os.mkdir(self.dstDir)
+ metaInfo = {"creator": "test", "formatVersion": 3}
+ path = os.path.join(self.dstDir, "metainfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(metaInfo, f)
+
+ def tearDown(self):
+ shutil.rmtree(self.dstDir)
+
+ def _writeInfoToPlist(self, info):
+ path = os.path.join(self.dstDir, "fontinfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(info, f)
+
+ def testRead(self):
+ originalData = dict(fontInfoVersion3)
+ self._writeInfoToPlist(originalData)
+ infoObject = TestInfoObject()
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(infoObject)
+ readData = {}
+ for attr in list(fontInfoVersion3.keys()):
+ readData[attr] = getattr(infoObject, attr)
+ self.assertEqual(originalData, readData)
+
+ def testGenericRead(self):
+ # familyName
+ info = dict(fontInfoVersion3)
+ info["familyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # styleName
+ info = dict(fontInfoVersion3)
+ info["styleName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # styleMapFamilyName
+ info = dict(fontInfoVersion3)
+ info["styleMapFamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # styleMapStyleName
+ ## not a string
+ info = dict(fontInfoVersion3)
+ info["styleMapStyleName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion3)
+ info["styleMapStyleName"] = "REGULAR"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # versionMajor
+ info = dict(fontInfoVersion3)
+ info["versionMajor"] = "1"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # versionMinor
+ info = dict(fontInfoVersion3)
+ info["versionMinor"] = "0"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["versionMinor"] = -1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # copyright
+ info = dict(fontInfoVersion3)
+ info["copyright"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # trademark
+ info = dict(fontInfoVersion3)
+ info["trademark"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # unitsPerEm
+ info = dict(fontInfoVersion3)
+ info["unitsPerEm"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["unitsPerEm"] = -1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["unitsPerEm"] = -1.0
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # descender
+ info = dict(fontInfoVersion3)
+ info["descender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # xHeight
+ info = dict(fontInfoVersion3)
+ info["xHeight"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # capHeight
+ info = dict(fontInfoVersion3)
+ info["capHeight"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # ascender
+ info = dict(fontInfoVersion3)
+ info["ascender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # italicAngle
+ info = dict(fontInfoVersion3)
+ info["italicAngle"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+
+ def testGaspRead(self):
+ # not a list
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # empty list
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = []
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ # not a dict
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = ["abc"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # dict not properly formatted
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = [dict(rangeMaxPPEM=0xFFFF, notTheRightKey=1)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = [
+ dict(notTheRightKey=1, rangeGaspBehavior=[0])
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # not an int for ppem
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = [
+ dict(rangeMaxPPEM="abc", rangeGaspBehavior=[0]),
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # not a list for behavior
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = [
+ dict(rangeMaxPPEM=10, rangeGaspBehavior="abc"),
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # invalid behavior value
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = [
+ dict(rangeMaxPPEM=10, rangeGaspBehavior=[-1]),
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # not sorted
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = [
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]),
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # no 0xFFFF
+ info = dict(fontInfoVersion3)
+ info["openTypeGaspRangeRecords"] = [
+ dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]),
+ dict(rangeMaxPPEM=20, rangeGaspBehavior=[0]),
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+
+ def testHeadRead(self):
+ # openTypeHeadCreated
+ ## not a string
+ info = dict(fontInfoVersion3)
+ info["openTypeHeadCreated"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## invalid format
+ info = dict(fontInfoVersion3)
+ info["openTypeHeadCreated"] = "2000-Jan-01 00:00:00"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHeadLowestRecPPEM
+ info = dict(fontInfoVersion3)
+ info["openTypeHeadLowestRecPPEM"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeHeadLowestRecPPEM"] = -1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHeadFlags
+ info = dict(fontInfoVersion3)
+ info["openTypeHeadFlags"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+
+ def testHheaRead(self):
+ # openTypeHheaAscender
+ info = dict(fontInfoVersion3)
+ info["openTypeHheaAscender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaDescender
+ info = dict(fontInfoVersion3)
+ info["openTypeHheaDescender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaLineGap
+ info = dict(fontInfoVersion3)
+ info["openTypeHheaLineGap"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaCaretSlopeRise
+ info = dict(fontInfoVersion3)
+ info["openTypeHheaCaretSlopeRise"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaCaretSlopeRun
+ info = dict(fontInfoVersion3)
+ info["openTypeHheaCaretSlopeRun"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeHheaCaretOffset
+ info = dict(fontInfoVersion3)
+ info["openTypeHheaCaretOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+
+ def testNameRead(self):
+ # openTypeNameDesigner
+ info = dict(fontInfoVersion3)
+ info["openTypeNameDesigner"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameDesignerURL
+ info = dict(fontInfoVersion3)
+ info["openTypeNameDesignerURL"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameManufacturer
+ info = dict(fontInfoVersion3)
+ info["openTypeNameManufacturer"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameManufacturerURL
+ info = dict(fontInfoVersion3)
+ info["openTypeNameManufacturerURL"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameLicense
+ info = dict(fontInfoVersion3)
+ info["openTypeNameLicense"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameLicenseURL
+ info = dict(fontInfoVersion3)
+ info["openTypeNameLicenseURL"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameVersion
+ info = dict(fontInfoVersion3)
+ info["openTypeNameVersion"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameUniqueID
+ info = dict(fontInfoVersion3)
+ info["openTypeNameUniqueID"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameDescription
+ info = dict(fontInfoVersion3)
+ info["openTypeNameDescription"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNamePreferredFamilyName
+ info = dict(fontInfoVersion3)
+ info["openTypeNamePreferredFamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNamePreferredSubfamilyName
+ info = dict(fontInfoVersion3)
+ info["openTypeNamePreferredSubfamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameCompatibleFullName
+ info = dict(fontInfoVersion3)
+ info["openTypeNameCompatibleFullName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameSampleText
+ info = dict(fontInfoVersion3)
+ info["openTypeNameSampleText"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameWWSFamilyName
+ info = dict(fontInfoVersion3)
+ info["openTypeNameWWSFamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameWWSSubfamilyName
+ info = dict(fontInfoVersion3)
+ info["openTypeNameWWSSubfamilyName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeNameRecords
+ ## not a list
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## not a dict
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = ["abc"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## invalid dict structure
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [dict(foo="bar")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## incorrect keys
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ foo="bar",
+ )
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(platformID=1, encodingID=1, languageID=1, string="Name Record.")
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(nameID=1, encodingID=1, languageID=1, string="Name Record.")
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(nameID=1, platformID=1, languageID=1, string="Name Record.")
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(nameID=1, platformID=1, encodingID=1, string="Name Record.")
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(nameID=1, platformID=1, encodingID=1, languageID=1)
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## invalid values
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(
+ nameID="1",
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ )
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(
+ nameID=1,
+ platformID="1",
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ )
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID="1",
+ languageID=1,
+ string="Name Record.",
+ )
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID="1",
+ string="Name Record.",
+ )
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(nameID=1, platformID=1, encodingID=1, languageID=1, string=1)
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## duplicate
+ info = dict(fontInfoVersion3)
+ info["openTypeNameRecords"] = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ ),
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ ),
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+
+ def testOS2Read(self):
+ # openTypeOS2WidthClass
+ ## not an int
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2WidthClass"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out or range
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2WidthClass"] = 15
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2WeightClass
+ info = dict(fontInfoVersion3)
+ ## not an int
+ info["openTypeOS2WeightClass"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info["openTypeOS2WeightClass"] = -50
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2Selection
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2Selection"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2VendorID
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2VendorID"] = 1234
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2Panose
+ ## not an int
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## negative
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, -9]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too few values
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2Panose"] = [0, 1, 2, 3]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2Panose"] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2FamilyClass
+ ## not an int
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2FamilyClass"] = [1, str(1)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too few values
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2FamilyClass"] = [1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2FamilyClass"] = [1, 1, 1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2FamilyClass"] = [1, 201]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2UnicodeRanges
+ ## not an int
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2UnicodeRanges"] = ["0"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2UnicodeRanges"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2CodePageRanges
+ ## not an int
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2CodePageRanges"] = ["0"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2CodePageRanges"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2TypoAscender
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2TypoAscender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2TypoDescender
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2TypoDescender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2TypoLineGap
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2TypoLineGap"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2WinAscent
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2WinAscent"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2WinAscent"] = -1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2WinDescent
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2WinDescent"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2WinDescent"] = -1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2Type
+ ## not an int
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2Type"] = ["1"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ ## out of range
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2Type"] = [-1]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptXSize
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SubscriptXSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptYSize
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SubscriptYSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptXOffset
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SubscriptXOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SubscriptYOffset
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SubscriptYOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptXSize
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SuperscriptXSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptYSize
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SuperscriptYSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptXOffset
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SuperscriptXOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2SuperscriptYOffset
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2SuperscriptYOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2StrikeoutSize
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2StrikeoutSize"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeOS2StrikeoutPosition
+ info = dict(fontInfoVersion3)
+ info["openTypeOS2StrikeoutPosition"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+
+ def testVheaRead(self):
+ # openTypeVheaVertTypoAscender
+ info = dict(fontInfoVersion3)
+ info["openTypeVheaVertTypoAscender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaVertTypoDescender
+ info = dict(fontInfoVersion3)
+ info["openTypeVheaVertTypoDescender"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaVertTypoLineGap
+ info = dict(fontInfoVersion3)
+ info["openTypeVheaVertTypoLineGap"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaCaretSlopeRise
+ info = dict(fontInfoVersion3)
+ info["openTypeVheaCaretSlopeRise"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaCaretSlopeRun
+ info = dict(fontInfoVersion3)
+ info["openTypeVheaCaretSlopeRun"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # openTypeVheaCaretOffset
+ info = dict(fontInfoVersion3)
+ info["openTypeVheaCaretOffset"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+
+ def testFONDRead(self):
+ # macintoshFONDFamilyID
+ info = dict(fontInfoVersion3)
+ info["macintoshFONDFamilyID"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # macintoshFONDName
+ info = dict(fontInfoVersion3)
+ info["macintoshFONDName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+
+ def testPostscriptRead(self):
+ # postscriptFontName
+ info = dict(fontInfoVersion3)
+ info["postscriptFontName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # postscriptFullName
+ info = dict(fontInfoVersion3)
+ info["postscriptFullName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # postscriptSlantAngle
+ info = dict(fontInfoVersion3)
+ info["postscriptSlantAngle"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, info=TestInfoObject())
+ # postscriptUniqueID
+ info = dict(fontInfoVersion3)
+ info["postscriptUniqueID"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptUnderlineThickness
+ info = dict(fontInfoVersion3)
+ info["postscriptUnderlineThickness"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptUnderlinePosition
+ info = dict(fontInfoVersion3)
+ info["postscriptUnderlinePosition"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptIsFixedPitch
+ info = dict(fontInfoVersion3)
+ info["postscriptIsFixedPitch"] = 2
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueValues
+ ## not a list
+ info = dict(fontInfoVersion3)
+ info["postscriptBlueValues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion3)
+ info["postscriptBlueValues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["postscriptBlueValues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptOtherBlues
+ ## not a list
+ info = dict(fontInfoVersion3)
+ info["postscriptOtherBlues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion3)
+ info["postscriptOtherBlues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["postscriptOtherBlues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptFamilyBlues
+ ## not a list
+ info = dict(fontInfoVersion3)
+ info["postscriptFamilyBlues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion3)
+ info["postscriptFamilyBlues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["postscriptFamilyBlues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptFamilyOtherBlues
+ ## not a list
+ info = dict(fontInfoVersion3)
+ info["postscriptFamilyOtherBlues"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## uneven value count
+ info = dict(fontInfoVersion3)
+ info["postscriptFamilyOtherBlues"] = [500]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["postscriptFamilyOtherBlues"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptStemSnapH
+ ## not list
+ info = dict(fontInfoVersion3)
+ info["postscriptStemSnapH"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["postscriptStemSnapH"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptStemSnapV
+ ## not list
+ info = dict(fontInfoVersion3)
+ info["postscriptStemSnapV"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many values
+ info = dict(fontInfoVersion3)
+ info["postscriptStemSnapV"] = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueFuzz
+ info = dict(fontInfoVersion3)
+ info["postscriptBlueFuzz"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueShift
+ info = dict(fontInfoVersion3)
+ info["postscriptBlueShift"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptBlueScale
+ info = dict(fontInfoVersion3)
+ info["postscriptBlueScale"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptForceBold
+ info = dict(fontInfoVersion3)
+ info["postscriptForceBold"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptDefaultWidthX
+ info = dict(fontInfoVersion3)
+ info["postscriptDefaultWidthX"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptNominalWidthX
+ info = dict(fontInfoVersion3)
+ info["postscriptNominalWidthX"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptWeightName
+ info = dict(fontInfoVersion3)
+ info["postscriptWeightName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptDefaultCharacter
+ info = dict(fontInfoVersion3)
+ info["postscriptDefaultCharacter"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # postscriptWindowsCharacterSet
+ info = dict(fontInfoVersion3)
+ info["postscriptWindowsCharacterSet"] = -1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # macintoshFONDFamilyID
+ info = dict(fontInfoVersion3)
+ info["macintoshFONDFamilyID"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # macintoshFONDName
+ info = dict(fontInfoVersion3)
+ info["macintoshFONDName"] = 123
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+
+ def testWOFFRead(self):
+ # woffMajorVersion
+ info = dict(fontInfoVersion3)
+ info["woffMajorVersion"] = 1.0
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["woffMajorVersion"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # woffMinorVersion
+ info = dict(fontInfoVersion3)
+ info["woffMinorVersion"] = 1.0
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["woffMinorVersion"] = "abc"
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # woffMetadataUniqueID
+ ## none
+ info = dict(fontInfoVersion3)
+ del info["woffMetadataUniqueID"]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## not a dict
+ info = dict(fontInfoVersion3)
+ info["woffMetadataUniqueID"] = 1
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## unknown key
+ info = dict(fontInfoVersion3)
+ info["woffMetadataUniqueID"] = dict(id="foo", notTheRightKey=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## no id
+ info = dict(fontInfoVersion3)
+ info["woffMetadataUniqueID"] = dict()
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## not a string for id
+ info = dict(fontInfoVersion3)
+ info["woffMetadataUniqueID"] = dict(id=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## empty string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataUniqueID"] = dict(id="")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ # woffMetadataVendor
+ ## no name
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(url="foo")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## name not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name=1, url="foo")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## name an empty string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="", url="foo")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## no URL
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="foo")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="foo", url=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## url empty string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="foo", url="")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## have dir
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="foo", url="bar", dir="ltr")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="foo", url="bar", dir="rtl")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## dir not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="foo", url="bar", dir=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir not ltr or rtl
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = dict(name="foo", url="bar", dir="utd")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## have class
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = {"name": "foo", "url": "bar", "class": "hello"}
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## class not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = {"name": "foo", "url": "bar", "class": 1}
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## class empty string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataVendor"] = {"name": "foo", "url": "bar", "class": ""}
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ # woffMetadataCredits
+ ## no credits attribute
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = {}
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## unknown attribute
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(name="foo")], notTheRightKey=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## not a list
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits="abc")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## no elements in credits
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## credit not a dict
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=["abc"])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## unknown key
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(name="foo", notTheRightKey=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## no name
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(url="foo")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## name not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(name=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(name="foo", url=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## role not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(name="foo", role=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(name="foo", dir=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir not ltr or rtl
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[dict(name="foo", dir="utd")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## class not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCredits"] = dict(credits=[{"name": "foo", "class": 1}])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # woffMetadataDescription
+ ## no url
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[dict(text="foo")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[dict(text="foo")], url=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## no text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(url="foo")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text not a list
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text="abc")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item not a dict
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=["abc"])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item unknown key
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(
+ text=[dict(text="foo", notTheRightKey=1)]
+ )
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item missing text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[dict(language="foo")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[dict(text=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[dict(text="foo", url=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## language not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[dict(text="foo", language=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir not ltr or rtl
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[dict(text="foo", dir="utd")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## class not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataDescription"] = dict(text=[{"text": "foo", "class": 1}])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # woffMetadataLicense
+ ## no url
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text="foo")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text="foo")], url=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## id not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text="foo")], id=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## no text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(url="foo")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## text not a list
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text="abc")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item not a dict
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=["abc"])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item unknown key
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text="foo", notTheRightKey=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item missing text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(language="foo")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text="foo", url=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## language not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text="foo", language=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir not ltr or rtl
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[dict(text="foo", dir="utd")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## class not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicense"] = dict(text=[{"text": "foo", "class": 1}])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # woffMetadataCopyright
+ ## unknown attribute
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[dict(text="foo")], notTheRightKey=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## no text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict()
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text not a list
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text="abc")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item not a dict
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=["abc"])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item unknown key
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[dict(text="foo", notTheRightKey=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item missing text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[dict(language="foo")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[dict(text=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[dict(text="foo", url=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## language not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[dict(text="foo", language=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir not ltr or rtl
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[dict(text="foo", dir="utd")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## class not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataCopyright"] = dict(text=[{"text": "foo", "class": 1}])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # woffMetadataTrademark
+ ## unknown attribute
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[dict(text="foo")], notTheRightKey=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## no text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict()
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text not a list
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text="abc")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item not a dict
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=["abc"])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item unknown key
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[dict(text="foo", notTheRightKey=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text item missing text
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[dict(language="foo")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## text not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[dict(text=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## url not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[dict(text="foo", url=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## language not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[dict(text="foo", language=1)])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir not ltr or rtl
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[dict(text="foo", dir="utd")])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## class not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataTrademark"] = dict(text=[{"text": "foo", "class": 1}])
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # woffMetadataLicensee
+ ## no name
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = dict()
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## unknown attribute
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = dict(name="foo", notTheRightKey=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## name not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = dict(name=1)
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## dir options
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = dict(name="foo", dir="ltr")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = dict(name="foo", dir="rtl")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## dir not ltr or rtl
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = dict(name="foo", dir="utd")
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## have class
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = {"name": "foo", "class": "hello"}
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ reader.readInfo(TestInfoObject())
+ ## class not a string
+ info = dict(fontInfoVersion3)
+ info["woffMetadataLicensee"] = {"name": "foo", "class": 1}
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+
+ def testGuidelinesRead(self):
+ # x
+ ## not an int or float
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x="1")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # y
+ ## not an int or float
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(y="1")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # angle
+ ## < 0
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, y=0, angle=-1)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## > 360
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, y=0, angle=361)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # name
+ ## not a string
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, name=1)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # color
+ ## not a string
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color=1)]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## not enough commas
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1 0, 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1 0 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1 0 0 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## not enough parts
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color=", 0, 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1, , 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1, 0, , 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1, 0, 0, ")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color=", , , ")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## not a number in all positions
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="r, 1, 1, 1")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1, g, 1, 1")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1, 1, b, 1")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1, 1, 1, a")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## too many parts
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="1, 0, 0, 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## < 0 in each position
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="-1, 0, 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="0, -1, 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="0, 0, -1, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="0, 0, 0, -1")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ ## > 1 in each position
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="2, 0, 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="0, 2, 0, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="0, 0, 2, 0")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [dict(x=0, color="0, 0, 0, 2")]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
+ # identifier
+ ## duplicate
+ info = dict(fontInfoVersion3)
+ info["guidelines"] = [
+ dict(x=0, identifier="guide1"),
+ dict(y=0, identifier="guide1"),
+ ]
+ self._writeInfoToPlist(info)
+ reader = UFOReader(self.dstDir, validate=True)
+ self.assertRaises(UFOLibError, reader.readInfo, TestInfoObject())
class WriteFontInfoVersion3TestCase(unittest.TestCase):
-
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.dstDir = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def tearDownUFO(self):
- if os.path.exists(self.dstDir):
- shutil.rmtree(self.dstDir)
-
- def makeInfoObject(self):
- infoObject = TestInfoObject()
- for attr, value in list(fontInfoVersion3.items()):
- setattr(infoObject, attr, value)
- return infoObject
-
- def readPlist(self):
- path = os.path.join(self.dstDir, "fontinfo.plist")
- with open(path, "rb") as f:
- plist = plistlib.load(f)
- return plist
-
- def testWrite(self):
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- writtenData = self.readPlist()
- for attr, originalValue in list(fontInfoVersion3.items()):
- newValue = writtenData[attr]
- self.assertEqual(newValue, originalValue)
- self.tearDownUFO()
-
- def testGenericWrite(self):
- # familyName
- infoObject = self.makeInfoObject()
- infoObject.familyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # styleName
- infoObject = self.makeInfoObject()
- infoObject.styleName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # styleMapFamilyName
- infoObject = self.makeInfoObject()
- infoObject.styleMapFamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # styleMapStyleName
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.styleMapStyleName = "REGULAR"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # versionMajor
- infoObject = self.makeInfoObject()
- infoObject.versionMajor = "1"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # versionMinor
- infoObject = self.makeInfoObject()
- infoObject.versionMinor = "0"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # copyright
- infoObject = self.makeInfoObject()
- infoObject.copyright = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # trademark
- infoObject = self.makeInfoObject()
- infoObject.trademark = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # unitsPerEm
- infoObject = self.makeInfoObject()
- infoObject.unitsPerEm = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # descender
- infoObject = self.makeInfoObject()
- infoObject.descender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # xHeight
- infoObject = self.makeInfoObject()
- infoObject.xHeight = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # capHeight
- infoObject = self.makeInfoObject()
- infoObject.capHeight = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # ascender
- infoObject = self.makeInfoObject()
- infoObject.ascender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # italicAngle
- infoObject = self.makeInfoObject()
- infoObject.italicAngle = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testGaspWrite(self):
- # not a list
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # empty list
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = []
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- # not a dict
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = ["abc"]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # dict not properly formatted
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = [dict(rangeMaxPPEM=0xFFFF, notTheRightKey=1)]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = [dict(notTheRightKey=1, rangeGaspBehavior=[0])]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # not an int for ppem
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = [dict(rangeMaxPPEM="abc", rangeGaspBehavior=[0]), dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # not a list for behavior
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = [dict(rangeMaxPPEM=10, rangeGaspBehavior="abc"), dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # invalid behavior value
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = [dict(rangeMaxPPEM=10, rangeGaspBehavior=[-1]), dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # not sorted
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = [dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]), dict(rangeMaxPPEM=10, rangeGaspBehavior=[0])]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # no 0xFFFF
- infoObject = self.makeInfoObject()
- infoObject.openTypeGaspRangeRecords = [dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]), dict(rangeMaxPPEM=20, rangeGaspBehavior=[0])]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
-
- def testHeadWrite(self):
- # openTypeHeadCreated
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadCreated = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## invalid format
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadCreated = "2000-Jan-01 00:00:00"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeHeadLowestRecPPEM
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadLowestRecPPEM = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeHeadFlags
- infoObject = self.makeInfoObject()
- infoObject.openTypeHeadFlags = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testHheaWrite(self):
- # openTypeHheaAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaAscender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeHheaDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaDescender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeHheaLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaLineGap = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeHheaCaretSlopeRise
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretSlopeRise = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeHheaCaretSlopeRun
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretSlopeRun = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeHheaCaretOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeHheaCaretOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testNameWrite(self):
- # openTypeNameDesigner
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDesigner = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameDesignerURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDesignerURL = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameManufacturer
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameManufacturer = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameManufacturerURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameManufacturerURL = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameLicense
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameLicense = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameLicenseURL
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameLicenseURL = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameVersion
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameVersion = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameUniqueID
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameUniqueID = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameDescription
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameDescription = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNamePreferredFamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNamePreferredFamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNamePreferredSubfamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNamePreferredSubfamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameCompatibleFullName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameCompatibleFullName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameSampleText
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameSampleText = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameWWSFamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameWWSFamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameWWSSubfamilyName
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameWWSSubfamilyName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeNameRecords
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## not a dict
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = ["abc"]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## invalid dict structure
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [dict(foo="bar")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## incorrect keys
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record.", foo="bar")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(platformID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, languageID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, encodingID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1)
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## invalid values
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID="1", platformID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID="1", encodingID=1, languageID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, encodingID="1", languageID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, encodingID=1, languageID="1", string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string=1)
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## duplicate
- infoObject = self.makeInfoObject()
- infoObject.openTypeNameRecords = [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record."),
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record.")
- ]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
-
- def testOS2Write(self):
- # openTypeOS2WidthClass
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## out or range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WidthClass = 15
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2WeightClass
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WeightClass = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WeightClass = -50
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2Selection
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Selection = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2VendorID
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2VendorID = 1234
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2Panose
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too few values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2FamilyClass
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [0, str(1)]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too few values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1, 1, 1]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2FamilyClass = [1, 20]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2UnicodeRanges
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2UnicodeRanges = ["0"]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2UnicodeRanges = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2CodePageRanges
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2CodePageRanges = ["0"]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2CodePageRanges = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2TypoAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoAscender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2TypoDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoDescender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2TypoLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2TypoLineGap = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2WinAscent
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinAscent = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinAscent = -1
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2WinDescent
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinDescent = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2WinDescent = -1
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2Type
- ## not an int
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Type = ["1"]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## out of range
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2Type = [-1]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SubscriptXSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptXSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SubscriptYSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptYSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SubscriptXOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptXOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SubscriptYOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SubscriptYOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SuperscriptXSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptXSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SuperscriptYSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptYSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SuperscriptXOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptXOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2SuperscriptYOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2SuperscriptYOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2StrikeoutSize
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2StrikeoutSize = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeOS2StrikeoutPosition
- infoObject = self.makeInfoObject()
- infoObject.openTypeOS2StrikeoutPosition = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testVheaWrite(self):
- # openTypeVheaVertTypoAscender
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoAscender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeVheaVertTypoDescender
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoDescender = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeVheaVertTypoLineGap
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaVertTypoLineGap = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeVheaCaretSlopeRise
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretSlopeRise = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeVheaCaretSlopeRun
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretSlopeRun = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # openTypeVheaCaretOffset
- infoObject = self.makeInfoObject()
- infoObject.openTypeVheaCaretOffset = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testFONDWrite(self):
- # macintoshFONDFamilyID
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDFamilyID = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # macintoshFONDName
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testPostscriptWrite(self):
- # postscriptFontName
- infoObject = self.makeInfoObject()
- infoObject.postscriptFontName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptFullName
- infoObject = self.makeInfoObject()
- infoObject.postscriptFullName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptSlantAngle
- infoObject = self.makeInfoObject()
- infoObject.postscriptSlantAngle = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptUniqueID
- infoObject = self.makeInfoObject()
- infoObject.postscriptUniqueID = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptUnderlineThickness
- infoObject = self.makeInfoObject()
- infoObject.postscriptUnderlineThickness = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptUnderlinePosition
- infoObject = self.makeInfoObject()
- infoObject.postscriptUnderlinePosition = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptIsFixedPitch
- infoObject = self.makeInfoObject()
- infoObject.postscriptIsFixedPitch = 2
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptBlueValues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueValues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptOtherBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptFamilyBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptFamilyOtherBlues
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## uneven value count
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = [500]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptFamilyOtherBlues = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptStemSnapH
- ## not list
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapH = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapH = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptStemSnapV
- ## not list
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapV = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many values
- infoObject = self.makeInfoObject()
- infoObject.postscriptStemSnapV = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptBlueFuzz
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueFuzz = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptBlueShift
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueShift = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptBlueScale
- infoObject = self.makeInfoObject()
- infoObject.postscriptBlueScale = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptForceBold
- infoObject = self.makeInfoObject()
- infoObject.postscriptForceBold = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptDefaultWidthX
- infoObject = self.makeInfoObject()
- infoObject.postscriptDefaultWidthX = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptNominalWidthX
- infoObject = self.makeInfoObject()
- infoObject.postscriptNominalWidthX = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptWeightName
- infoObject = self.makeInfoObject()
- infoObject.postscriptWeightName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptDefaultCharacter
- infoObject = self.makeInfoObject()
- infoObject.postscriptDefaultCharacter = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # postscriptWindowsCharacterSet
- infoObject = self.makeInfoObject()
- infoObject.postscriptWindowsCharacterSet = -1
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # macintoshFONDFamilyID
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDFamilyID = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # macintoshFONDName
- infoObject = self.makeInfoObject()
- infoObject.macintoshFONDName = 123
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testWOFFWrite(self):
- # woffMajorVersion
- infoObject = self.makeInfoObject()
- infoObject.woffMajorVersion = 1.0
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.woffMajorVersion = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # woffMinorVersion
- infoObject = self.makeInfoObject()
- infoObject.woffMinorVersion = 1.0
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.woffMinorVersion = "abc"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # woffMetadataUniqueID
- ## none
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataUniqueID = None
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## not a dict
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataUniqueID = 1
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## unknown key
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataUniqueID = dict(id="foo", notTheRightKey=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## no id
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataUniqueID = dict()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## not a string for id
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataUniqueID = dict(id=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## empty string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataUniqueID = dict(id="")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- # woffMetadataVendor
- ## no name
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(url="foo")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## name not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name=1, url="foo")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## name an empty string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="", url="foo")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## no URL
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="foo")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="foo", url=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## url empty string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="foo", url="")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## have dir
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir="ltr")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir="rtl")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## dir not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir not ltr or rtl
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir="utd")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## have class
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = {"name" : "foo", "url" : "bar", "class" : "hello"}
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## class not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = {"name" : "foo", "url" : "bar", "class" : 1}
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## class empty string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataVendor = {"name" : "foo", "url" : "bar", "class" : ""}
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- # woffMetadataCredits
- ## no credits attribute
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = {}
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## unknown attribute
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(name="foo")], notTheRightKey=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## not a list
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits="abc")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## no elements in credits
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## credit not a dict
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=["abc"])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## unknown key
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", notTheRightKey=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## no name
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(url="foo")])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## name not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(name=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", url=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## role not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", role=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", dir=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir not ltr or rtl
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", dir="utd")])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## class not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCredits = dict(credits=[{"name" : "foo", "class" : 1}])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # woffMetadataDescription
- ## no url
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(text="foo")])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(text="foo")], url=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## no text
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(url="foo")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text not a list
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text="abc")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item not a dict
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=["abc"])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item unknown key
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(text="foo", notTheRightKey=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item missing text
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(language="foo")])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(text=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(text="foo", url=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## language not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(text="foo", language=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir not ltr or rtl
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[dict(text="foo", dir="utd")])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## class not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataDescription = dict(text=[{"text" : "foo", "class" : 1}])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # woffMetadataLicense
- ## no url
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicense = dict(text=[dict(text="foo")])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicense = dict(text=[dict(text="foo")], url=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## id not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicense = dict(text=[dict(text="foo")], id=1)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## no text
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicense = dict(url="foo")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## text not a list
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicense = dict(text="abc")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item not a dict
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicense = dict(text=["abc"])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item unknown key
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicense = dict(text=[dict(text="foo", notTheRightKey=1)])
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item missing text
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicense = dict(text=[dict(language="foo")])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicense = dict(text=[dict(text=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicense = dict(text=[dict(text="foo", url=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## language not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicense = dict(text=[dict(text="foo", language=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir not ltr or rtl
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicense = dict(text=[dict(text="foo", dir="utd")])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## class not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicense = dict(text=[{"text" : "foo", "class" : 1}])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # woffMetadataCopyright
- ## unknown attribute
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=[dict(text="foo")], notTheRightKey=1)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## no text
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict()
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text not a list
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text="abc")
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item not a dict
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=["abc"])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item unknown key
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=[dict(text="foo", notTheRightKey=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item missing text
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataCopyright = dict(text=[dict(language="foo")])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=[dict(text=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=[dict(text="foo", url=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## language not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=[dict(text="foo", language=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir not ltr or rtl
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=[dict(text="foo", dir="utd")])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## class not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataCopyright = dict(text=[{"text" : "foo", "class" : 1}])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # woffMetadataTrademark
- ## unknown attribute
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[dict(text="foo")], notTheRightKey=1)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## no text
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict()
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text not a list
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text="abc")
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item not a dict
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=["abc"])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item unknown key
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[dict(text="foo", notTheRightKey=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text item missing text
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[dict(language="foo")])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## text not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[dict(text=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## url not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[dict(text="foo", url=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## language not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[dict(text="foo", language=1)])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir not ltr or rtl
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[dict(text="foo", dir="utd")])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## class not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataTrademark = dict(text=[{"text" : "foo", "class" : 1}])
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # woffMetadataLicensee
- ## no name
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicensee = dict()
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## unknown attribute
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicensee = dict(name="foo", notTheRightKey=1)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## name not a string
- infoObject = self.makeInfoObject()
- writer = UFOWriter(self.dstDir, formatVersion=3)
- infoObject.woffMetadataLicensee = dict(name=1)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## dir options
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicensee = dict(name="foo", dir="ltr")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicensee = dict(name="foo", dir="rtl")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## dir not ltr or rtl
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicensee = dict(name="foo", dir="utd")
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## have class
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicensee = {"name" : "foo", "class" : "hello"}
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeInfo(infoObject)
- self.tearDownUFO()
- ## class not a string
- infoObject = self.makeInfoObject()
- infoObject.woffMetadataLicensee = {"name" : "foo", "class" : 1}
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
-
- def testGuidelinesWrite(self):
- # x
- ## not an int or float
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x="1")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # y
- ## not an int or float
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(y="1")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # angle
- ## < 0
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, y=0, angle=-1)]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## > 360
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, y=0, angle=361)]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # name
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, name=1)]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # color
- ## not a string
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color=1)]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## not enough commas
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1 0, 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1 0 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1 0 0 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## not enough parts
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color=", 0, 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1, , 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1, 0, , 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1, 0, 0, ")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color=", , , ")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## not a number in all positions
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="r, 1, 1, 1")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1, g, 1, 1")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1, 1, b, 1")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1, 1, 1, a")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## too many parts
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="1, 0, 0, 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## < 0 in each position
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="-1, 0, 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="0, -1, 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="0, 0, -1, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="0, 0, 0, -1")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## > 1 in each position
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="2, 0, 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="0, 2, 0, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="0, 0, 2, 0")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, color="0, 0, 0, 2")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- # identifier
- ## duplicate
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, identifier="guide1"), dict(y=0, identifier="guide1")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## below min
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, identifier="\0x1F")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
- ## above max
- infoObject = self.makeInfoObject()
- infoObject.guidelines = [dict(x=0, identifier="\0x7F")]
- writer = UFOWriter(self.dstDir, formatVersion=3)
- self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
- self.tearDownUFO()
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.dstDir = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def tearDownUFO(self):
+ if os.path.exists(self.dstDir):
+ shutil.rmtree(self.dstDir)
+
+ def makeInfoObject(self):
+ infoObject = TestInfoObject()
+ for attr, value in list(fontInfoVersion3.items()):
+ setattr(infoObject, attr, value)
+ return infoObject
+
+ def readPlist(self):
+ path = os.path.join(self.dstDir, "fontinfo.plist")
+ with open(path, "rb") as f:
+ plist = plistlib.load(f)
+ return plist
+
+ def testWrite(self):
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ writtenData = self.readPlist()
+ for attr, originalValue in list(fontInfoVersion3.items()):
+ newValue = writtenData[attr]
+ self.assertEqual(newValue, originalValue)
+ self.tearDownUFO()
+
+ def testGenericWrite(self):
+ # familyName
+ infoObject = self.makeInfoObject()
+ infoObject.familyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # styleName
+ infoObject = self.makeInfoObject()
+ infoObject.styleName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # styleMapFamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.styleMapFamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # styleMapStyleName
+ ## not a string
+ infoObject = self.makeInfoObject()
+ infoObject.styleMapStyleName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.styleMapStyleName = "REGULAR"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # versionMajor
+ infoObject = self.makeInfoObject()
+ infoObject.versionMajor = "1"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # versionMinor
+ infoObject = self.makeInfoObject()
+ infoObject.versionMinor = "0"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # copyright
+ infoObject = self.makeInfoObject()
+ infoObject.copyright = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # trademark
+ infoObject = self.makeInfoObject()
+ infoObject.trademark = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # unitsPerEm
+ infoObject = self.makeInfoObject()
+ infoObject.unitsPerEm = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # descender
+ infoObject = self.makeInfoObject()
+ infoObject.descender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # xHeight
+ infoObject = self.makeInfoObject()
+ infoObject.xHeight = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # capHeight
+ infoObject = self.makeInfoObject()
+ infoObject.capHeight = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # ascender
+ infoObject = self.makeInfoObject()
+ infoObject.ascender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # italicAngle
+ infoObject = self.makeInfoObject()
+ infoObject.italicAngle = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testGaspWrite(self):
+ # not a list
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # empty list
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = []
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ # not a dict
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = ["abc"]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # dict not properly formatted
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = [
+ dict(rangeMaxPPEM=0xFFFF, notTheRightKey=1)
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = [
+ dict(notTheRightKey=1, rangeGaspBehavior=[0])
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # not an int for ppem
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = [
+ dict(rangeMaxPPEM="abc", rangeGaspBehavior=[0]),
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # not a list for behavior
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = [
+ dict(rangeMaxPPEM=10, rangeGaspBehavior="abc"),
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # invalid behavior value
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = [
+ dict(rangeMaxPPEM=10, rangeGaspBehavior=[-1]),
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # not sorted
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = [
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]),
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # no 0xFFFF
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeGaspRangeRecords = [
+ dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]),
+ dict(rangeMaxPPEM=20, rangeGaspBehavior=[0]),
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+
+ def testHeadWrite(self):
+ # openTypeHeadCreated
+ ## not a string
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadCreated = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## invalid format
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadCreated = "2000-Jan-01 00:00:00"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeHeadLowestRecPPEM
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadLowestRecPPEM = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeHeadFlags
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHeadFlags = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testHheaWrite(self):
+ # openTypeHheaAscender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaAscender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeHheaDescender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaDescender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeHheaLineGap
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaLineGap = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeHheaCaretSlopeRise
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaCaretSlopeRise = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeHheaCaretSlopeRun
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaCaretSlopeRun = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeHheaCaretOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeHheaCaretOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testNameWrite(self):
+ # openTypeNameDesigner
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameDesigner = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameDesignerURL
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameDesignerURL = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameManufacturer
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameManufacturer = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameManufacturerURL
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameManufacturerURL = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameLicense
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameLicense = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameLicenseURL
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameLicenseURL = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameVersion
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameVersion = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameUniqueID
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameUniqueID = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameDescription
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameDescription = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNamePreferredFamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNamePreferredFamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNamePreferredSubfamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNamePreferredSubfamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameCompatibleFullName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameCompatibleFullName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameSampleText
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameSampleText = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameWWSFamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameWWSFamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameWWSSubfamilyName
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameWWSSubfamilyName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeNameRecords
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## not a dict
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = ["abc"]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## invalid dict structure
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [dict(foo="bar")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## incorrect keys
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ foo="bar",
+ )
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(platformID=1, encodingID=1, languageID=1, string="Name Record.")
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(nameID=1, encodingID=1, languageID=1, string="Name Record.")
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(nameID=1, platformID=1, languageID=1, string="Name Record.")
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(nameID=1, platformID=1, encodingID=1, string="Name Record.")
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(nameID=1, platformID=1, encodingID=1, languageID=1)
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## invalid values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(
+ nameID="1",
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ )
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(
+ nameID=1,
+ platformID="1",
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ )
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID="1",
+ languageID=1,
+ string="Name Record.",
+ )
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID="1",
+ string="Name Record.",
+ )
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(nameID=1, platformID=1, encodingID=1, languageID=1, string=1)
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## duplicate
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeNameRecords = [
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ ),
+ dict(
+ nameID=1,
+ platformID=1,
+ encodingID=1,
+ languageID=1,
+ string="Name Record.",
+ ),
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+
+ def testOS2Write(self):
+ # openTypeOS2WidthClass
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WidthClass = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## out or range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WidthClass = 15
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2WeightClass
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WeightClass = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WeightClass = -50
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2Selection
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Selection = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2VendorID
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2VendorID = 1234
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2Panose
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, str(9)]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too few values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Panose = [0, 1, 2, 3]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Panose = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2FamilyClass
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [0, str(1)]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too few values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [1]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [1, 1, 1]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2FamilyClass = [1, 20]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2UnicodeRanges
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2UnicodeRanges = ["0"]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2UnicodeRanges = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2CodePageRanges
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2CodePageRanges = ["0"]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2CodePageRanges = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2TypoAscender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2TypoAscender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2TypoDescender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2TypoDescender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2TypoLineGap
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2TypoLineGap = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2WinAscent
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WinAscent = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WinAscent = -1
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2WinDescent
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WinDescent = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2WinDescent = -1
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2Type
+ ## not an int
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Type = ["1"]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## out of range
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2Type = [-1]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SubscriptXSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptXSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SubscriptYSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptYSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SubscriptXOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptXOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SubscriptYOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SubscriptYOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SuperscriptXSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptXSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SuperscriptYSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptYSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SuperscriptXOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptXOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2SuperscriptYOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2SuperscriptYOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2StrikeoutSize
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2StrikeoutSize = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeOS2StrikeoutPosition
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeOS2StrikeoutPosition = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testVheaWrite(self):
+ # openTypeVheaVertTypoAscender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaVertTypoAscender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeVheaVertTypoDescender
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaVertTypoDescender = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeVheaVertTypoLineGap
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaVertTypoLineGap = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeVheaCaretSlopeRise
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaCaretSlopeRise = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeVheaCaretSlopeRun
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaCaretSlopeRun = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # openTypeVheaCaretOffset
+ infoObject = self.makeInfoObject()
+ infoObject.openTypeVheaCaretOffset = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testFONDWrite(self):
+ # macintoshFONDFamilyID
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDFamilyID = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # macintoshFONDName
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testPostscriptWrite(self):
+ # postscriptFontName
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFontName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptFullName
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFullName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptSlantAngle
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptSlantAngle = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptUniqueID
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptUniqueID = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptUnderlineThickness
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptUnderlineThickness = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptUnderlinePosition
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptUnderlinePosition = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptIsFixedPitch
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptIsFixedPitch = 2
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptBlueValues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueValues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueValues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueValues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptOtherBlues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptOtherBlues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptOtherBlues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptOtherBlues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptFamilyBlues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyBlues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyBlues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyBlues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptFamilyOtherBlues
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyOtherBlues = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## uneven value count
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyOtherBlues = [500]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptFamilyOtherBlues = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptStemSnapH
+ ## not list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapH = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapH = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptStemSnapV
+ ## not list
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapV = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many values
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptStemSnapV = [
+ 10,
+ 20,
+ 30,
+ 40,
+ 50,
+ 60,
+ 70,
+ 80,
+ 90,
+ 100,
+ 110,
+ 120,
+ 130,
+ 140,
+ 150,
+ 160,
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptBlueFuzz
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueFuzz = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptBlueShift
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueShift = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptBlueScale
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptBlueScale = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptForceBold
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptForceBold = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptDefaultWidthX
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptDefaultWidthX = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptNominalWidthX
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptNominalWidthX = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptWeightName
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptWeightName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptDefaultCharacter
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptDefaultCharacter = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # postscriptWindowsCharacterSet
+ infoObject = self.makeInfoObject()
+ infoObject.postscriptWindowsCharacterSet = -1
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # macintoshFONDFamilyID
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDFamilyID = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # macintoshFONDName
+ infoObject = self.makeInfoObject()
+ infoObject.macintoshFONDName = 123
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testWOFFWrite(self):
+ # woffMajorVersion
+ infoObject = self.makeInfoObject()
+ infoObject.woffMajorVersion = 1.0
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.woffMajorVersion = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # woffMinorVersion
+ infoObject = self.makeInfoObject()
+ infoObject.woffMinorVersion = 1.0
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.woffMinorVersion = "abc"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # woffMetadataUniqueID
+ ## none
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataUniqueID = None
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## not a dict
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataUniqueID = 1
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## unknown key
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataUniqueID = dict(id="foo", notTheRightKey=1)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## no id
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataUniqueID = dict()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## not a string for id
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataUniqueID = dict(id=1)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## empty string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataUniqueID = dict(id="")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ # woffMetadataVendor
+ ## no name
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(url="foo")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## name not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name=1, url="foo")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## name an empty string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="", url="foo")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## no URL
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="foo")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="foo", url=1)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## url empty string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="foo", url="")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## have dir
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir="ltr")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir="rtl")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## dir not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir=1)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir not ltr or rtl
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = dict(name="foo", url="bar", dir="utd")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## have class
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = {"name": "foo", "url": "bar", "class": "hello"}
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## class not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = {"name": "foo", "url": "bar", "class": 1}
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## class empty string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataVendor = {"name": "foo", "url": "bar", "class": ""}
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ # woffMetadataCredits
+ ## no credits attribute
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = {}
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## unknown attribute
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(
+ credits=[dict(name="foo")], notTheRightKey=1
+ )
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## not a list
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits="abc")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## no elements in credits
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## credit not a dict
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=["abc"])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## unknown key
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(
+ credits=[dict(name="foo", notTheRightKey=1)]
+ )
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## no name
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[dict(url="foo")])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## name not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[dict(name=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", url=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## role not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", role=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", dir=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir not ltr or rtl
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[dict(name="foo", dir="utd")])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## class not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCredits = dict(credits=[{"name": "foo", "class": 1}])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # woffMetadataDescription
+ ## no url
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[dict(text="foo")])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[dict(text="foo")], url=1)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## no text
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(url="foo")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text not a list
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text="abc")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item not a dict
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=["abc"])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item unknown key
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(
+ text=[dict(text="foo", notTheRightKey=1)]
+ )
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item missing text
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[dict(language="foo")])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[dict(text=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[dict(text="foo", url=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## language not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[dict(text="foo", language=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir not ltr or rtl
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[dict(text="foo", dir="utd")])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## class not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataDescription = dict(text=[{"text": "foo", "class": 1}])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # woffMetadataLicense
+ ## no url
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicense = dict(text=[dict(text="foo")])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicense = dict(text=[dict(text="foo")], url=1)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## id not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicense = dict(text=[dict(text="foo")], id=1)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## no text
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicense = dict(url="foo")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## text not a list
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicense = dict(text="abc")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item not a dict
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicense = dict(text=["abc"])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item unknown key
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicense = dict(text=[dict(text="foo", notTheRightKey=1)])
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item missing text
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicense = dict(text=[dict(language="foo")])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicense = dict(text=[dict(text=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicense = dict(text=[dict(text="foo", url=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## language not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicense = dict(text=[dict(text="foo", language=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir not ltr or rtl
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicense = dict(text=[dict(text="foo", dir="utd")])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## class not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicense = dict(text=[{"text": "foo", "class": 1}])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # woffMetadataCopyright
+ ## unknown attribute
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(
+ text=[dict(text="foo")], notTheRightKey=1
+ )
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## no text
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict()
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text not a list
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(text="abc")
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item not a dict
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(text=["abc"])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item unknown key
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(
+ text=[dict(text="foo", notTheRightKey=1)]
+ )
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item missing text
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataCopyright = dict(text=[dict(language="foo")])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(text=[dict(text=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(text=[dict(text="foo", url=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## language not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(text=[dict(text="foo", language=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir not ltr or rtl
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(text=[dict(text="foo", dir="utd")])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## class not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataCopyright = dict(text=[{"text": "foo", "class": 1}])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # woffMetadataTrademark
+ ## unknown attribute
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(
+ text=[dict(text="foo")], notTheRightKey=1
+ )
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## no text
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict()
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text not a list
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text="abc")
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item not a dict
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text=["abc"])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item unknown key
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(
+ text=[dict(text="foo", notTheRightKey=1)]
+ )
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text item missing text
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text=[dict(language="foo")])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## text not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text=[dict(text=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## url not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text=[dict(text="foo", url=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## language not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text=[dict(text="foo", language=1)])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir not ltr or rtl
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text=[dict(text="foo", dir="utd")])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## class not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataTrademark = dict(text=[{"text": "foo", "class": 1}])
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # woffMetadataLicensee
+ ## no name
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicensee = dict()
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## unknown attribute
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicensee = dict(name="foo", notTheRightKey=1)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## name not a string
+ infoObject = self.makeInfoObject()
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ infoObject.woffMetadataLicensee = dict(name=1)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## dir options
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicensee = dict(name="foo", dir="ltr")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicensee = dict(name="foo", dir="rtl")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## dir not ltr or rtl
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicensee = dict(name="foo", dir="utd")
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## have class
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicensee = {"name": "foo", "class": "hello"}
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeInfo(infoObject)
+ self.tearDownUFO()
+ ## class not a string
+ infoObject = self.makeInfoObject()
+ infoObject.woffMetadataLicensee = {"name": "foo", "class": 1}
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+
+ def testGuidelinesWrite(self):
+ # x
+ ## not an int or float
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x="1")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # y
+ ## not an int or float
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(y="1")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # angle
+ ## < 0
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, y=0, angle=-1)]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## > 360
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, y=0, angle=361)]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # name
+ ## not a string
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, name=1)]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # color
+ ## not a string
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color=1)]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## not enough commas
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1 0, 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1 0 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1 0 0 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## not enough parts
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color=", 0, 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1, , 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1, 0, , 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1, 0, 0, ")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color=", , , ")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## not a number in all positions
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="r, 1, 1, 1")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1, g, 1, 1")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1, 1, b, 1")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1, 1, 1, a")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## too many parts
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="1, 0, 0, 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## < 0 in each position
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="-1, 0, 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="0, -1, 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="0, 0, -1, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="0, 0, 0, -1")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## > 1 in each position
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="2, 0, 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="0, 2, 0, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="0, 0, 2, 0")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, color="0, 0, 0, 2")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ # identifier
+ ## duplicate
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [
+ dict(x=0, identifier="guide1"),
+ dict(y=0, identifier="guide1"),
+ ]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## below min
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, identifier="\0x1F")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
+ ## above max
+ infoObject = self.makeInfoObject()
+ infoObject.guidelines = [dict(x=0, identifier="\0x7F")]
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ self.assertRaises(UFOLibError, writer.writeInfo, info=infoObject)
+ self.tearDownUFO()
# ------
# layers
# ------
-class UFO3ReadLayersTestCase(unittest.TestCase):
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.ufoPath = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def makeUFO(self, metaInfo=None, layerContents=None):
- self.clearUFO()
- if not os.path.exists(self.ufoPath):
- os.mkdir(self.ufoPath)
- # metainfo.plist
- if metaInfo is None:
- metaInfo = dict(creator="test", formatVersion=3)
- path = os.path.join(self.ufoPath, "metainfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(metaInfo, f)
- # layers
- if layerContents is None:
- layerContents = [
- ("public.default", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2"),
- ]
- if layerContents:
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- else:
- layerContents = [("", "glyphs")]
- for name, directory in layerContents:
- glyphsPath = os.path.join(self.ufoPath, directory)
- os.mkdir(glyphsPath)
- contents = dict(a="a.glif")
- path = os.path.join(glyphsPath, "contents.plist")
- with open(path, "wb") as f:
- plistlib.dump(contents, f)
- path = os.path.join(glyphsPath, "a.glif")
- with open(path, "w") as f:
- f.write(" ")
-
- def clearUFO(self):
- if os.path.exists(self.ufoPath):
- shutil.rmtree(self.ufoPath)
-
- # valid
-
- def testValidRead(self):
- # UFO 1
- self.makeUFO(
- metaInfo=dict(creator="test", formatVersion=1),
- layerContents=dict()
- )
- reader = UFOReader(self.ufoPath, validate=True)
- reader.getGlyphSet()
- # UFO 2
- self.makeUFO(
- metaInfo=dict(creator="test", formatVersion=2),
- layerContents=dict()
- )
- reader = UFOReader(self.ufoPath, validate=True)
- reader.getGlyphSet()
- # UFO 3
- self.makeUFO()
- reader = UFOReader(self.ufoPath, validate=True)
- reader.getGlyphSet()
-
- # missing layer contents
-
- def testMissingLayerContents(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # layer contents invalid format
-
- def testInvalidLayerContentsFormat(self):
- # bogus
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- with open(path, "w") as f:
- f.write("test")
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
- # dict
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = {
- "public.default" : "glyphs",
- "layer 1" : "glyphs.layer 1",
- "layer 2" : "glyphs.layer 2",
- }
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # layer contents invalid name format
-
- def testInvalidLayerContentsNameFormat(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- (1, "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # layer contents invalid directory format
-
- def testInvalidLayerContentsDirectoryFormat(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", 1),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # directory listed in contents not on disk
-
- def testLayerContentsHasMissingDirectory(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.doesnotexist"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # # directory on disk not listed in contents
- # XXX should this raise an error?
- #
- # def testLayerContentsHasMissingDirectory(self):
- # self.makeUFO()
- # path = os.path.join(self.ufoPath, "layercontents.plist")
- # os.remove(path)
- # layerContents = [
- # ("public.foregound", "glyphs"),
- # ("layer 1", "glyphs.layer 2")
- # ]
- # with open(path, "wb") as f:
- # plistlib.dump(layerContents, f)
- # reader = UFOReader(self.ufoPath, validate=True)
- # with self.assertRaises(UFOLibError):
- # reader.getGlyphSet()
-
- # no default layer on disk
-
- def testMissingDefaultLayer(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # duplicate layer name
-
- def testDuplicateLayerName(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 1", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # directory referenced by two layer names
-
- def testDuplicateLayerDirectory(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 1")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- self.assertRaises(UFOLibError, reader.getGlyphSet)
-
- # default without a name
-
- def testDefaultLayerNoName(self):
- # get the glyph set
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- reader.getGlyphSet()
-
- # default with a name
-
- def testDefaultLayerName(self):
- # get the name
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("custom name", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- expected = layerContents[0][0]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- result = reader.getDefaultLayerName()
- self.assertEqual(expected, result)
- # get the glyph set
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("custom name", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- reader.getGlyphSet(expected)
-
- # layer order
-
- def testLayerOrder(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- expected = [name for (name, directory) in layerContents]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- result = reader.getLayerNames()
- self.assertEqual(expected, result)
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("layer 1", "glyphs.layer 1"),
- ("public.foregound", "glyphs"),
- ("layer 2", "glyphs.layer 2")
- ]
- expected = [name for (name, directory) in layerContents]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- result = reader.getLayerNames()
- self.assertEqual(expected, result)
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("layer 2", "glyphs.layer 2"),
- ("layer 1", "glyphs.layer 1"),
- ("public.foregound", "glyphs")
- ]
- expected = [name for (name, directory) in layerContents]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- reader = UFOReader(self.ufoPath, validate=True)
- result = reader.getLayerNames()
- self.assertEqual(expected, result)
+class UFO3ReadLayersTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.ufoPath = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def makeUFO(self, metaInfo=None, layerContents=None):
+ self.clearUFO()
+ if not os.path.exists(self.ufoPath):
+ os.mkdir(self.ufoPath)
+ # metainfo.plist
+ if metaInfo is None:
+ metaInfo = dict(creator="test", formatVersion=3)
+ path = os.path.join(self.ufoPath, "metainfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(metaInfo, f)
+ # layers
+ if layerContents is None:
+ layerContents = [
+ ("public.default", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ if layerContents:
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ else:
+ layerContents = [("", "glyphs")]
+ for name, directory in layerContents:
+ glyphsPath = os.path.join(self.ufoPath, directory)
+ os.mkdir(glyphsPath)
+ contents = dict(a="a.glif")
+ path = os.path.join(glyphsPath, "contents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(contents, f)
+ path = os.path.join(glyphsPath, "a.glif")
+ with open(path, "w") as f:
+ f.write(" ")
+
+ def clearUFO(self):
+ if os.path.exists(self.ufoPath):
+ shutil.rmtree(self.ufoPath)
+
+ # valid
+
+ def testValidRead(self):
+ # UFO 1
+ self.makeUFO(
+ metaInfo=dict(creator="test", formatVersion=1), layerContents=dict()
+ )
+ reader = UFOReader(self.ufoPath, validate=True)
+ reader.getGlyphSet()
+ # UFO 2
+ self.makeUFO(
+ metaInfo=dict(creator="test", formatVersion=2), layerContents=dict()
+ )
+ reader = UFOReader(self.ufoPath, validate=True)
+ reader.getGlyphSet()
+ # UFO 3
+ self.makeUFO()
+ reader = UFOReader(self.ufoPath, validate=True)
+ reader.getGlyphSet()
+
+ # missing layer contents
+
+ def testMissingLayerContents(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # layer contents invalid format
+
+ def testInvalidLayerContentsFormat(self):
+ # bogus
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ with open(path, "w") as f:
+ f.write("test")
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+ # dict
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = {
+ "public.default": "glyphs",
+ "layer 1": "glyphs.layer 1",
+ "layer 2": "glyphs.layer 2",
+ }
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # layer contents invalid name format
+
+ def testInvalidLayerContentsNameFormat(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ (1, "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # layer contents invalid directory format
+
+ def testInvalidLayerContentsDirectoryFormat(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", 1),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # directory listed in contents not on disk
+
+ def testLayerContentsHasMissingDirectory(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.doesnotexist"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # # directory on disk not listed in contents
+ # XXX should this raise an error?
+ #
+ # def testLayerContentsHasMissingDirectory(self):
+ # self.makeUFO()
+ # path = os.path.join(self.ufoPath, "layercontents.plist")
+ # os.remove(path)
+ # layerContents = [
+ # ("public.foregound", "glyphs"),
+ # ("layer 1", "glyphs.layer 2")
+ # ]
+ # with open(path, "wb") as f:
+ # plistlib.dump(layerContents, f)
+ # reader = UFOReader(self.ufoPath, validate=True)
+ # with self.assertRaises(UFOLibError):
+ # reader.getGlyphSet()
+
+ # no default layer on disk
+
+ def testMissingDefaultLayer(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [("layer 1", "glyphs.layer 1"), ("layer 2", "glyphs.layer 2")]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # duplicate layer name
+
+ def testDuplicateLayerName(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 1", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # directory referenced by two layer names
+
+ def testDuplicateLayerDirectory(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 1"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ self.assertRaises(UFOLibError, reader.getGlyphSet)
+
+ # default without a name
+
+ def testDefaultLayerNoName(self):
+ # get the glyph set
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ reader.getGlyphSet()
+
+ # default with a name
+
+ def testDefaultLayerName(self):
+ # get the name
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("custom name", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ expected = layerContents[0][0]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ result = reader.getDefaultLayerName()
+ self.assertEqual(expected, result)
+ # get the glyph set
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("custom name", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ reader.getGlyphSet(expected)
+
+ # layer order
+
+ def testLayerOrder(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ expected = [name for (name, directory) in layerContents]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ result = reader.getLayerNames()
+ self.assertEqual(expected, result)
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("layer 1", "glyphs.layer 1"),
+ ("public.foregound", "glyphs"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ expected = [name for (name, directory) in layerContents]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ result = reader.getLayerNames()
+ self.assertEqual(expected, result)
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("layer 2", "glyphs.layer 2"),
+ ("layer 1", "glyphs.layer 1"),
+ ("public.foregound", "glyphs"),
+ ]
+ expected = [name for (name, directory) in layerContents]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ reader = UFOReader(self.ufoPath, validate=True)
+ result = reader.getLayerNames()
+ self.assertEqual(expected, result)
class UFO3WriteLayersTestCase(unittest.TestCase):
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.ufoPath = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def makeUFO(self, metaInfo=None, layerContents=None):
+ self.clearUFO()
+ if not os.path.exists(self.ufoPath):
+ os.mkdir(self.ufoPath)
+ # metainfo.plist
+ if metaInfo is None:
+ metaInfo = dict(creator="test", formatVersion=3)
+ path = os.path.join(self.ufoPath, "metainfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(metaInfo, f)
+ # layers
+ if layerContents is None:
+ layerContents = [
+ ("public.default", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ if layerContents:
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ else:
+ layerContents = [("", "glyphs")]
+ for name, directory in layerContents:
+ glyphsPath = os.path.join(self.ufoPath, directory)
+ os.mkdir(glyphsPath)
+ contents = dict(a="a.glif")
+ path = os.path.join(glyphsPath, "contents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(contents, f)
+ path = os.path.join(glyphsPath, "a.glif")
+ with open(path, "w") as f:
+ f.write(" ")
+
+ def clearUFO(self):
+ if os.path.exists(self.ufoPath):
+ shutil.rmtree(self.ufoPath)
+
+ # __init__: missing layer contents
+
+ def testMissingLayerContents(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: layer contents invalid format
+
+ def testInvalidLayerContentsFormat(self):
+ # bogus
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ with open(path, "w") as f:
+ f.write("test")
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+ # dict
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = {
+ "public.default": "glyphs",
+ "layer 1": "glyphs.layer 1",
+ "layer 2": "glyphs.layer 2",
+ }
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: layer contents invalid name format
+
+ def testInvalidLayerContentsNameFormat(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ (1, "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: layer contents invalid directory format
+
+ def testInvalidLayerContentsDirectoryFormat(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", 1),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: directory listed in contents not on disk
+
+ def testLayerContentsHasMissingDirectory(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.doesnotexist"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: no default layer on disk
+
+ def testMissingDefaultLayer(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [("layer 1", "glyphs.layer 1"), ("layer 2", "glyphs.layer 2")]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: duplicate layer name
+
+ def testDuplicateLayerName(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 1", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: directory referenced by two layer names
+
+ def testDuplicateLayerDirectory(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 1"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
+
+ # __init__: default without a name
+
+ def testDefaultLayerNoName(self):
+ # get the glyph set
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("public.foregound", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ writer = UFOWriter(self.ufoPath)
+
+ # __init__: default with a name
+
+ def testDefaultLayerName(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ os.remove(path)
+ layerContents = [
+ ("custom name", "glyphs"),
+ ("layer 1", "glyphs.layer 1"),
+ ("layer 2", "glyphs.layer 2"),
+ ]
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ writer = UFOWriter(self.ufoPath)
+
+ # __init__: up convert 1 > 3
+
+ def testUpConvert1To3(self):
+ self.makeUFO(
+ metaInfo=dict(creator="test", formatVersion=1), layerContents=dict()
+ )
+ writer = UFOWriter(self.ufoPath)
+ writer.writeLayerContents(["public.default"])
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [["public.default", "glyphs"]]
+ self.assertEqual(expected, result)
+
+ # __init__: up convert 2 > 3
+
+ def testUpConvert2To3(self):
+ self.makeUFO(
+ metaInfo=dict(creator="test", formatVersion=2), layerContents=dict()
+ )
+ writer = UFOWriter(self.ufoPath)
+ writer.writeLayerContents(["public.default"])
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [["public.default", "glyphs"]]
+ self.assertEqual(expected, result)
+
+ # __init__: down convert 3 > 1
+
+ def testDownConvert3To1(self):
+ self.makeUFO()
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath, formatVersion=1)
+
+ # __init__: down convert 3 > 2
+
+ def testDownConvert3To2(self):
+ self.makeUFO()
+ self.assertRaises(UFOLibError, UFOWriter, self.ufoPath, formatVersion=2)
+
+ # get glyph sets
+
+ def testGetGlyphSets(self):
+ self.makeUFO()
+ # hack contents.plist
+ path = os.path.join(self.ufoPath, "glyphs.layer 1", "contents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(dict(b="a.glif"), f)
+ path = os.path.join(self.ufoPath, "glyphs.layer 2", "contents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(dict(c="a.glif"), f)
+ # now test
+ writer = UFOWriter(self.ufoPath)
+ # default
+ expected = ["a"]
+ result = list(writer.getGlyphSet().keys())
+ self.assertEqual(expected, result)
+ # layer 1
+ expected = ["b"]
+ result = list(writer.getGlyphSet("layer 1", defaultLayer=False).keys())
+ self.assertEqual(expected, result)
+ # layer 2
+ expected = ["c"]
+ 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):
+ self.clearUFO()
+ writer = UFOWriter(self.ufoPath)
+ writer.getGlyphSet()
+ writer.writeLayerContents(["public.default"])
+ # directory
+ path = os.path.join(self.ufoPath, "glyphs")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ # layer contents
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [["public.default", "glyphs"]]
+ self.assertEqual(expected, result)
+
+ def testNewFontThreeLayers(self):
+ self.clearUFO()
+ writer = UFOWriter(self.ufoPath)
+ writer.getGlyphSet("layer 1", defaultLayer=False)
+ writer.getGlyphSet()
+ writer.getGlyphSet("layer 2", defaultLayer=False)
+ writer.writeLayerContents(["layer 1", "public.default", "layer 2"])
+ # directories
+ path = os.path.join(self.ufoPath, "glyphs")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 1")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 2")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ # layer contents
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [
+ ["layer 1", "glyphs.layer 1"],
+ ["public.default", "glyphs"],
+ ["layer 2", "glyphs.layer 2"],
+ ]
+ self.assertEqual(expected, result)
+
+ # add a layer to an existing font
+
+ def testAddLayerToExistingFont(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ writer.getGlyphSet("layer 3", defaultLayer=False)
+ writer.writeLayerContents(["public.default", "layer 1", "layer 2", "layer 3"])
+ # directories
+ path = os.path.join(self.ufoPath, "glyphs")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 1")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 2")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 3")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ # layer contents
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [
+ ["public.default", "glyphs"],
+ ["layer 1", "glyphs.layer 1"],
+ ["layer 2", "glyphs.layer 2"],
+ ["layer 3", "glyphs.layer 3"],
+ ]
+ self.assertEqual(expected, result)
+
+ # rename valid name
+
+ def testRenameLayer(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ writer.renameGlyphSet("layer 1", "layer 3")
+ writer.writeLayerContents(["public.default", "layer 3", "layer 2"])
+ # directories
+ path = os.path.join(self.ufoPath, "glyphs")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 1")
+ exists = os.path.exists(path)
+ self.assertEqual(False, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 2")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 3")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ # layer contents
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [
+ ["public.default", "glyphs"],
+ ["layer 3", "glyphs.layer 3"],
+ ["layer 2", "glyphs.layer 2"],
+ ]
+ self.assertEqual(expected, result)
+
+ def testRenameLayerDefault(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ writer.renameGlyphSet("public.default", "layer xxx")
+ writer.renameGlyphSet("layer 1", "layer 1", defaultLayer=True)
+ writer.writeLayerContents(["layer xxx", "layer 1", "layer 2"])
+ path = os.path.join(self.ufoPath, "glyphs")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 1")
+ exists = os.path.exists(path)
+ self.assertEqual(False, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 2")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer xxx")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ # layer contents
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [
+ ["layer xxx", "glyphs.layer xxx"],
+ ["layer 1", "glyphs"],
+ ["layer 2", "glyphs.layer 2"],
+ ]
+ self.assertEqual(expected, result)
+
+ # rename duplicate name
+
+ def testRenameLayerDuplicateName(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ self.assertRaises(UFOLibError, writer.renameGlyphSet, "layer 1", "layer 2")
+
+ # rename unknown layer
+
+ def testRenameLayerUnknownName(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ self.assertRaises(
+ UFOLibError, writer.renameGlyphSet, "does not exist", "layer 2"
+ )
+
+ # remove valid layer
+
+ def testRemoveLayer(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ writer.deleteGlyphSet("layer 1")
+ writer.writeLayerContents(["public.default", "layer 2"])
+ # directories
+ path = os.path.join(self.ufoPath, "glyphs")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 1")
+ exists = os.path.exists(path)
+ self.assertEqual(False, exists)
+ path = os.path.join(self.ufoPath, "glyphs.layer 2")
+ exists = os.path.exists(path)
+ self.assertEqual(True, exists)
+ # layer contents
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [["public.default", "glyphs"], ["layer 2", "glyphs.layer 2"]]
+ self.assertEqual(expected, result)
+
+ # remove default layer
+
+ def testRemoveDefaultLayer(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ writer.deleteGlyphSet("public.default")
+ writer.writeLayerContents(["layer 1", "layer 2"])
+ # directories
+ path = os.path.join(self.ufoPath, "glyphs")
+ self.assertEqual(False, os.path.exists(path))
+ path = os.path.join(self.ufoPath, "glyphs.layer 1")
+ self.assertEqual(True, os.path.exists(path))
+ path = os.path.join(self.ufoPath, "glyphs.layer 2")
+ self.assertEqual(True, os.path.exists(path))
+ # layer contents
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ expected = [["layer 1", "glyphs.layer 1"], ["layer 2", "glyphs.layer 2"]]
+ self.assertEqual(expected, result)
+
+ # remove unknown layer
+
+ def testRemoveDefaultLayer2(self):
+ self.makeUFO()
+ writer = UFOWriter(self.ufoPath)
+ self.assertRaises(UFOLibError, writer.deleteGlyphSet, "does not exist")
+
+ def testWriteAsciiLayerOrder(self):
+ self.makeUFO(
+ layerContents=[
+ ["public.default", "glyphs"],
+ ["layer 1", "glyphs.layer 1"],
+ ["layer 2", "glyphs.layer 2"],
+ ]
+ )
+ writer = UFOWriter(self.ufoPath)
+ 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)
+ expected = [
+ ["public.default", "glyphs"],
+ ["layer 2", "glyphs.layer 2"],
+ ["layer 1", "glyphs.layer 1"],
+ ]
+ self.assertEqual(expected, result)
+ for layerName, _ in result:
+ assert isinstance(layerName, str)
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.ufoPath = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def makeUFO(self, metaInfo=None, layerContents=None):
- self.clearUFO()
- if not os.path.exists(self.ufoPath):
- os.mkdir(self.ufoPath)
- # metainfo.plist
- if metaInfo is None:
- metaInfo = dict(creator="test", formatVersion=3)
- path = os.path.join(self.ufoPath, "metainfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(metaInfo, f)
- # layers
- if layerContents is None:
- layerContents = [
- ("public.default", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2"),
- ]
- if layerContents:
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- else:
- layerContents = [("", "glyphs")]
- for name, directory in layerContents:
- glyphsPath = os.path.join(self.ufoPath, directory)
- os.mkdir(glyphsPath)
- contents = dict(a="a.glif")
- path = os.path.join(glyphsPath, "contents.plist")
- with open(path, "wb") as f:
- plistlib.dump(contents, f)
- path = os.path.join(glyphsPath, "a.glif")
- with open(path, "w") as f:
- f.write(" ")
-
- def clearUFO(self):
- if os.path.exists(self.ufoPath):
- shutil.rmtree(self.ufoPath)
-
- # __init__: missing layer contents
-
- def testMissingLayerContents(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: layer contents invalid format
-
- def testInvalidLayerContentsFormat(self):
- # bogus
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- with open(path, "w") as f:
- f.write("test")
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
- # dict
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = {
- "public.default" : "glyphs",
- "layer 1" : "glyphs.layer 1",
- "layer 2" : "glyphs.layer 2",
- }
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: layer contents invalid name format
-
- def testInvalidLayerContentsNameFormat(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- (1, "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: layer contents invalid directory format
-
- def testInvalidLayerContentsDirectoryFormat(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", 1),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: directory listed in contents not on disk
-
- def testLayerContentsHasMissingDirectory(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.doesnotexist"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: no default layer on disk
-
- def testMissingDefaultLayer(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: duplicate layer name
-
- def testDuplicateLayerName(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 1", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: directory referenced by two layer names
-
- def testDuplicateLayerDirectory(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 1")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath)
-
- # __init__: default without a name
-
- def testDefaultLayerNoName(self):
- # get the glyph set
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("public.foregound", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- writer = UFOWriter(self.ufoPath)
-
- # __init__: default with a name
-
- def testDefaultLayerName(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "layercontents.plist")
- os.remove(path)
- layerContents = [
- ("custom name", "glyphs"),
- ("layer 1", "glyphs.layer 1"),
- ("layer 2", "glyphs.layer 2")
- ]
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- writer = UFOWriter(self.ufoPath)
-
- # __init__: up convert 1 > 3
-
- def testUpConvert1To3(self):
- self.makeUFO(
- metaInfo=dict(creator="test", formatVersion=1),
- layerContents=dict()
- )
- writer = UFOWriter(self.ufoPath)
- writer.writeLayerContents(["public.default"])
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [["public.default", "glyphs"]]
- self.assertEqual(expected, result)
-
- # __init__: up convert 2 > 3
-
- def testUpConvert2To3(self):
- self.makeUFO(
- metaInfo=dict(creator="test", formatVersion=2),
- layerContents=dict()
- )
- writer = UFOWriter(self.ufoPath)
- writer.writeLayerContents(["public.default"])
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [["public.default", "glyphs"]]
- self.assertEqual(expected, result)
-
- # __init__: down convert 3 > 1
-
- def testDownConvert3To1(self):
- self.makeUFO()
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath, formatVersion=1)
-
- # __init__: down convert 3 > 2
-
- def testDownConvert3To2(self):
- self.makeUFO()
- self.assertRaises(UFOLibError, UFOWriter, self.ufoPath, formatVersion=2)
-
- # get glyph sets
-
- def testGetGlyphSets(self):
- self.makeUFO()
- # hack contents.plist
- path = os.path.join(self.ufoPath, "glyphs.layer 1", "contents.plist")
- with open(path, "wb") as f:
- plistlib.dump(dict(b="a.glif"), f)
- path = os.path.join(self.ufoPath, "glyphs.layer 2", "contents.plist")
- with open(path, "wb") as f:
- plistlib.dump(dict(c="a.glif"), f)
- # now test
- writer = UFOWriter(self.ufoPath)
- # default
- expected = ["a"]
- result = list(writer.getGlyphSet().keys())
- self.assertEqual(expected, result)
- # layer 1
- expected = ["b"]
- result = list(writer.getGlyphSet("layer 1", defaultLayer=False).keys())
- self.assertEqual(expected, result)
- # layer 2
- expected = ["c"]
- 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):
- self.clearUFO()
- writer = UFOWriter(self.ufoPath)
- writer.getGlyphSet()
- writer.writeLayerContents(["public.default"])
- # directory
- path = os.path.join(self.ufoPath, "glyphs")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- # layer contents
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [["public.default", "glyphs"]]
- self.assertEqual(expected, result)
-
- def testNewFontThreeLayers(self):
- self.clearUFO()
- writer = UFOWriter(self.ufoPath)
- writer.getGlyphSet("layer 1", defaultLayer=False)
- writer.getGlyphSet()
- writer.getGlyphSet("layer 2", defaultLayer=False)
- writer.writeLayerContents(["layer 1", "public.default", "layer 2"])
- # directories
- path = os.path.join(self.ufoPath, "glyphs")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 1")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 2")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- # layer contents
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [["layer 1", "glyphs.layer 1"], ["public.default", "glyphs"], ["layer 2", "glyphs.layer 2"]]
- self.assertEqual(expected, result)
-
- # add a layer to an existing font
-
- def testAddLayerToExistingFont(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- writer.getGlyphSet("layer 3", defaultLayer=False)
- writer.writeLayerContents(["public.default", "layer 1", "layer 2", "layer 3"])
- # directories
- path = os.path.join(self.ufoPath, "glyphs")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 1")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 2")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 3")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- # layer contents
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [['public.default', 'glyphs'], ['layer 1', 'glyphs.layer 1'], ['layer 2', 'glyphs.layer 2'], ["layer 3", "glyphs.layer 3"]]
- self.assertEqual(expected, result)
-
- # rename valid name
-
- def testRenameLayer(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- writer.renameGlyphSet("layer 1", "layer 3")
- writer.writeLayerContents(["public.default", "layer 3", "layer 2"])
- # directories
- path = os.path.join(self.ufoPath, "glyphs")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 1")
- exists = os.path.exists(path)
- self.assertEqual(False, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 2")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 3")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- # layer contents
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [['public.default', 'glyphs'], ['layer 3', 'glyphs.layer 3'], ['layer 2', 'glyphs.layer 2']]
- self.assertEqual(expected, result)
-
- def testRenameLayerDefault(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- writer.renameGlyphSet("public.default", "layer xxx")
- writer.renameGlyphSet("layer 1", "layer 1", defaultLayer=True)
- writer.writeLayerContents(["layer xxx", "layer 1", "layer 2"])
- path = os.path.join(self.ufoPath, "glyphs")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 1")
- exists = os.path.exists(path)
- self.assertEqual(False, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 2")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer xxx")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- # layer contents
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [['layer xxx', 'glyphs.layer xxx'], ['layer 1', 'glyphs'], ['layer 2', 'glyphs.layer 2']]
- self.assertEqual(expected, result)
-
- # rename duplicate name
-
- def testRenameLayerDuplicateName(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- self.assertRaises(UFOLibError, writer.renameGlyphSet, "layer 1", "layer 2")
-
- # rename unknown layer
-
- def testRenameLayerUnknownName(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- self.assertRaises(UFOLibError, writer.renameGlyphSet, "does not exist", "layer 2")
-
- # remove valid layer
-
- def testRemoveLayer(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- writer.deleteGlyphSet("layer 1")
- writer.writeLayerContents(["public.default", "layer 2"])
- # directories
- path = os.path.join(self.ufoPath, "glyphs")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 1")
- exists = os.path.exists(path)
- self.assertEqual(False, exists)
- path = os.path.join(self.ufoPath, "glyphs.layer 2")
- exists = os.path.exists(path)
- self.assertEqual(True, exists)
- # layer contents
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [["public.default", "glyphs"], ["layer 2", "glyphs.layer 2"]]
- self.assertEqual(expected, result)
-
- # remove default layer
-
- def testRemoveDefaultLayer(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- writer.deleteGlyphSet("public.default")
- writer.writeLayerContents(["layer 1", "layer 2"])
- # directories
- path = os.path.join(self.ufoPath, "glyphs")
- self.assertEqual(False, os.path.exists(path))
- path = os.path.join(self.ufoPath, "glyphs.layer 1")
- self.assertEqual(True, os.path.exists(path))
- path = os.path.join(self.ufoPath, "glyphs.layer 2")
- self.assertEqual(True, os.path.exists(path))
- # layer contents
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- expected = [["layer 1", "glyphs.layer 1"], ["layer 2", "glyphs.layer 2"]]
- self.assertEqual(expected, result)
-
- # remove unknown layer
-
- def testRemoveDefaultLayer2(self):
- self.makeUFO()
- writer = UFOWriter(self.ufoPath)
- self.assertRaises(UFOLibError, writer.deleteGlyphSet, "does not exist")
-
- def testWriteAsciiLayerOrder(self):
- self.makeUFO(
- layerContents=[
- ["public.default", "glyphs"],
- ["layer 1", "glyphs.layer 1"],
- ["layer 2", "glyphs.layer 2"],
- ]
- )
- writer = UFOWriter(self.ufoPath)
- 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)
- expected = [
- ["public.default", "glyphs"],
- ["layer 2", "glyphs.layer 2"],
- ["layer 1", "glyphs.layer 1"],
- ]
- self.assertEqual(expected, result)
- for layerName, _ in result:
- assert isinstance(layerName, str)
# -----
# /data
@@ -4175,539 +4529,549 @@ class UFO3WriteLayersTestCase(unittest.TestCase):
class UFO3ReadDataTestCase(unittest.TestCase):
-
- def getFontPath(self):
- testdata = os.path.join(os.path.dirname(__file__), "testdata")
- return os.path.join(testdata, "UFO3-Read Data.ufo")
-
- def testUFOReaderDataDirectoryListing(self):
- reader = UFOReader(self.getFontPath())
- found = reader.getDataDirectoryListing()
- expected = [
- 'org.unifiedfontobject.directory/bar/lol.txt',
- 'org.unifiedfontobject.directory/foo.txt',
- 'org.unifiedfontobject.file.txt'
- ]
- self.assertEqual(set(found), set(expected))
-
- def testUFOReaderBytesFromPath(self):
- reader = UFOReader(self.getFontPath())
- found = reader.readBytesFromPath("data/org.unifiedfontobject.file.txt")
- expected = b"file.txt"
- self.assertEqual(found, expected)
- found = reader.readBytesFromPath("data/org.unifiedfontobject.directory/bar/lol.txt")
- expected = b"lol.txt"
- self.assertEqual(found, expected)
- found = reader.readBytesFromPath("data/org.unifiedfontobject.doesNotExist")
- expected = None
- self.assertEqual(found, expected)
-
- def testUFOReaderReadFileFromPath(self):
- reader = UFOReader(self.getFontPath())
- fileObject = reader.getReadFileForPath("data/org.unifiedfontobject.file.txt")
- self.assertNotEqual(fileObject, None)
- hasRead = hasattr(fileObject, "read")
- self.assertEqual(hasRead, True)
- fileObject.close()
- 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)
+ def getFontPath(self):
+ testdata = os.path.join(os.path.dirname(__file__), "testdata")
+ return os.path.join(testdata, "UFO3-Read Data.ufo")
+
+ def testUFOReaderDataDirectoryListing(self):
+ reader = UFOReader(self.getFontPath())
+ found = reader.getDataDirectoryListing()
+ expected = [
+ "org.unifiedfontobject.directory/bar/lol.txt",
+ "org.unifiedfontobject.directory/foo.txt",
+ "org.unifiedfontobject.file.txt",
+ ]
+ self.assertEqual(set(found), set(expected))
+
+ def testUFOReaderBytesFromPath(self):
+ reader = UFOReader(self.getFontPath())
+ found = reader.readBytesFromPath("data/org.unifiedfontobject.file.txt")
+ expected = b"file.txt"
+ self.assertEqual(found, expected)
+ found = reader.readBytesFromPath(
+ "data/org.unifiedfontobject.directory/bar/lol.txt"
+ )
+ expected = b"lol.txt"
+ self.assertEqual(found, expected)
+ found = reader.readBytesFromPath("data/org.unifiedfontobject.doesNotExist")
+ expected = None
+ self.assertEqual(found, expected)
+
+ def testUFOReaderReadFileFromPath(self):
+ reader = UFOReader(self.getFontPath())
+ fileObject = reader.getReadFileForPath("data/org.unifiedfontobject.file.txt")
+ self.assertNotEqual(fileObject, None)
+ hasRead = hasattr(fileObject, "read")
+ self.assertEqual(hasRead, True)
+ fileObject.close()
+ 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):
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.dstDir = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def tearDownUFO(self):
+ if os.path.exists(self.dstDir):
+ shutil.rmtree(self.dstDir)
+
+ def testUFOWriterWriteBytesToPath(self):
+ # basic file
+ path = "data/org.unifiedfontobject.writebytesbasicfile.txt"
+ testBytes = b"test"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeBytesToPath(path, testBytes)
+ path = os.path.join(self.dstDir, path)
+ self.assertEqual(os.path.exists(path), True)
+ with open(path, "rb") as f:
+ written = f.read()
+ self.assertEqual(testBytes, written)
+ self.tearDownUFO()
+ # basic file with unicode text
+ path = "data/org.unifiedfontobject.writebytesbasicunicodefile.txt"
+ text = b"t\xeb\xdft"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeBytesToPath(path, text)
+ path = os.path.join(self.dstDir, path)
+ self.assertEqual(os.path.exists(path), True)
+ with open(path, "rb") as f:
+ written = f.read()
+ self.assertEqual(text, written)
+ self.tearDownUFO()
+ # basic directory
+ path = "data/org.unifiedfontobject.writebytesdirectory/level1/level2/file.txt"
+ testBytes = b"test"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeBytesToPath(path, testBytes)
+ path = os.path.join(self.dstDir, path)
+ self.assertEqual(os.path.exists(path), True)
+ with open(path, "rb") as f:
+ written = f.read()
+ self.assertEqual(testBytes, written)
+ self.tearDownUFO()
+
+ def testUFOWriterWriteFileToPath(self):
+ # basic file
+ path = "data/org.unifiedfontobject.getwritefile.txt"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ fileObject = writer.getFileObjectForPath(path)
+ self.assertNotEqual(fileObject, None)
+ hasRead = hasattr(fileObject, "read")
+ self.assertEqual(hasRead, True)
+ fileObject.close()
+ self.tearDownUFO()
+
+ def testUFOWriterRemoveFile(self):
+ path1 = "data/org.unifiedfontobject.removefile/level1/level2/file1.txt"
+ path2 = "data/org.unifiedfontobject.removefile/level1/level2/file2.txt"
+ path3 = "data/org.unifiedfontobject.removefile/level1/file3.txt"
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.writeBytesToPath(path1, b"test")
+ writer.writeBytesToPath(path2, b"test")
+ writer.writeBytesToPath(path3, b"test")
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path1)), True)
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path2)), True)
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), True)
+ writer.removeFileForPath(path1)
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path1)), False)
+ self.assertEqual(
+ os.path.exists(os.path.dirname(os.path.join(self.dstDir, path1))), True
+ )
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path2)), True)
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), True)
+ writer.removeFileForPath(path2)
+ self.assertEqual(
+ os.path.exists(os.path.dirname(os.path.join(self.dstDir, path1))), False
+ )
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path2)), False)
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), True)
+ writer.removeFileForPath(path3)
+ self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), False)
+ self.assertEqual(
+ os.path.exists(os.path.dirname(os.path.join(self.dstDir, path2))), False
+ )
+ self.assertEqual(
+ os.path.exists(
+ os.path.join(self.dstDir, "data/org.unifiedfontobject.removefile")
+ ),
+ False,
+ )
+ self.assertRaises(
+ UFOLibError,
+ writer.removeFileForPath,
+ path="data/org.unifiedfontobject.doesNotExist.txt",
+ )
+ self.tearDownUFO()
+
+ def testUFOWriterCopy(self):
+ sourceDir = self.dstDir.replace(".ufo", "") + "-copy source" + ".ufo"
+ dataPath = "data/org.unifiedfontobject.copy/level1/level2/file1.txt"
+ writer = UFOWriter(sourceDir, formatVersion=3)
+ writer.writeBytesToPath(dataPath, b"test")
+ # copy a file
+ reader = UFOReader(sourceDir)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ writer.copyFromReader(reader, dataPath, dataPath)
+ path = os.path.join(self.dstDir, dataPath)
+ self.assertEqual(os.path.exists(path), True)
+ self.tearDownUFO()
+ # copy a directory
+ reader = UFOReader(sourceDir)
+ writer = UFOWriter(self.dstDir, formatVersion=3)
+ p = "data/org.unifiedfontobject.copy"
+ writer.copyFromReader(reader, p, p)
+ path = os.path.join(self.dstDir, dataPath)
+ self.assertEqual(os.path.exists(path), True)
+ self.tearDownUFO()
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.dstDir = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def tearDownUFO(self):
- if os.path.exists(self.dstDir):
- shutil.rmtree(self.dstDir)
-
- def testUFOWriterWriteBytesToPath(self):
- # basic file
- path = "data/org.unifiedfontobject.writebytesbasicfile.txt"
- testBytes = b"test"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeBytesToPath(path, testBytes)
- path = os.path.join(self.dstDir, path)
- self.assertEqual(os.path.exists(path), True)
- with open(path, "rb") as f:
- written = f.read()
- self.assertEqual(testBytes, written)
- self.tearDownUFO()
- # basic file with unicode text
- path = "data/org.unifiedfontobject.writebytesbasicunicodefile.txt"
- text = b"t\xeb\xdft"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeBytesToPath(path, text)
- path = os.path.join(self.dstDir, path)
- self.assertEqual(os.path.exists(path), True)
- with open(path, "rb") as f:
- written = f.read()
- self.assertEqual(text, written)
- self.tearDownUFO()
- # basic directory
- path = "data/org.unifiedfontobject.writebytesdirectory/level1/level2/file.txt"
- testBytes = b"test"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeBytesToPath(path, testBytes)
- path = os.path.join(self.dstDir, path)
- self.assertEqual(os.path.exists(path), True)
- with open(path, "rb") as f:
- written = f.read()
- self.assertEqual(testBytes, written)
- self.tearDownUFO()
-
- def testUFOWriterWriteFileToPath(self):
- # basic file
- path = "data/org.unifiedfontobject.getwritefile.txt"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- fileObject = writer.getFileObjectForPath(path)
- self.assertNotEqual(fileObject, None)
- hasRead = hasattr(fileObject, "read")
- self.assertEqual(hasRead, True)
- fileObject.close()
- self.tearDownUFO()
-
- def testUFOWriterRemoveFile(self):
- path1 = "data/org.unifiedfontobject.removefile/level1/level2/file1.txt"
- path2 = "data/org.unifiedfontobject.removefile/level1/level2/file2.txt"
- path3 = "data/org.unifiedfontobject.removefile/level1/file3.txt"
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.writeBytesToPath(path1, b"test")
- writer.writeBytesToPath(path2, b"test")
- writer.writeBytesToPath(path3, b"test")
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path1)), True)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path2)), True)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), True)
- writer.removeFileForPath(path1)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path1)), False)
- self.assertEqual(os.path.exists(os.path.dirname(os.path.join(self.dstDir, path1))), True)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path2)), True)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), True)
- writer.removeFileForPath(path2)
- self.assertEqual(os.path.exists(os.path.dirname(os.path.join(self.dstDir, path1))), False)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path2)), False)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), True)
- writer.removeFileForPath(path3)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, path3)), False)
- self.assertEqual(os.path.exists(os.path.dirname(os.path.join(self.dstDir, path2))), False)
- self.assertEqual(os.path.exists(os.path.join(self.dstDir, "data/org.unifiedfontobject.removefile")), False)
- self.assertRaises(UFOLibError, writer.removeFileForPath, path="data/org.unifiedfontobject.doesNotExist.txt")
- self.tearDownUFO()
-
- def testUFOWriterCopy(self):
- sourceDir = self.dstDir.replace(".ufo", "") + "-copy source" + ".ufo"
- dataPath = "data/org.unifiedfontobject.copy/level1/level2/file1.txt"
- writer = UFOWriter(sourceDir, formatVersion=3)
- writer.writeBytesToPath(dataPath, b"test")
- # copy a file
- reader = UFOReader(sourceDir)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- writer.copyFromReader(reader, dataPath, dataPath)
- path = os.path.join(self.dstDir, dataPath)
- self.assertEqual(os.path.exists(path), True)
- self.tearDownUFO()
- # copy a directory
- reader = UFOReader(sourceDir)
- writer = UFOWriter(self.dstDir, formatVersion=3)
- p = "data/org.unifiedfontobject.copy"
- writer.copyFromReader(reader, p, p)
- path = os.path.join(self.dstDir, dataPath)
- self.assertEqual(os.path.exists(path), True)
- self.tearDownUFO()
# ---------------
# layerinfo.plist
# ---------------
-class TestLayerInfoObject:
- color = guidelines = lib = None
+class TestLayerInfoObject:
+ color = guidelines = lib = None
class UFO3ReadLayerInfoTestCase(unittest.TestCase):
-
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.ufoPath = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def makeUFO(self, formatVersion=3, layerInfo=None):
- self.clearUFO()
- if not os.path.exists(self.ufoPath):
- os.mkdir(self.ufoPath)
- # metainfo.plist
- metaInfo = dict(creator="test", formatVersion=formatVersion)
- path = os.path.join(self.ufoPath, "metainfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(metaInfo, f)
- # layercontents.plist
- layerContents = [("public.default", "glyphs")]
- path = os.path.join(self.ufoPath, "layercontents.plist")
- with open(path, "wb") as f:
- plistlib.dump(layerContents, f)
- # glyphs
- glyphsPath = os.path.join(self.ufoPath, "glyphs")
- os.mkdir(glyphsPath)
- contents = dict(a="a.glif")
- path = os.path.join(glyphsPath, "contents.plist")
- with open(path, "wb") as f:
- plistlib.dump(contents, f)
- path = os.path.join(glyphsPath, "a.glif")
- with open(path, "w") as f:
- f.write(" ")
- # layerinfo.plist
- if layerInfo is None:
- layerInfo = dict(
- color="0,0,0,1",
- lib={"foo" : "bar"}
- )
- path = os.path.join(glyphsPath, "layerinfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(layerInfo, f)
-
- def clearUFO(self):
- if os.path.exists(self.ufoPath):
- shutil.rmtree(self.ufoPath)
-
- def testValidLayerInfo(self):
- self.makeUFO()
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- info = TestLayerInfoObject()
- glyphSet.readLayerInfo(info)
- expectedColor = "0,0,0,1"
- self.assertEqual(expectedColor, info.color)
- expectedLib = {"foo": "bar"}
- self.assertEqual(expectedLib, info.lib)
-
- def testMissingLayerInfo(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
- os.remove(path)
- # read
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- info = TestLayerInfoObject()
- glyphSet.readLayerInfo(info)
- self.assertEqual(None, info.color)
- self.assertEqual(None, info.guidelines)
- self.assertEqual(None, info.lib)
-
- def testBogusLayerInfo(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
- os.remove(path)
- with open(path, "w") as f:
- f.write("test")
- # read
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- info = TestLayerInfoObject()
- self.assertRaises(UFOLibError, glyphSet.readLayerInfo, info)
-
- def testInvalidFormatLayerInfo(self):
- self.makeUFO()
- path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
- info = [("color", "0,0,0,0")]
- with open(path, "wb") as f:
- plistlib.dump(info, f)
- # read
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- info = TestLayerInfoObject()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, info)
-
- def testColor(self):
- ## not a string
- info = {}
- info["color"] = 1
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- ## not enough commas
- info = {}
- info["color"] = "1 0, 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1 0 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1 0 0 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- ## not enough parts
- info = {}
- info["color"] = ", 0, 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1, , 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1, 0, , 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1, 0, 0, "
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = ", , , "
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- ## not a number in all positions
- info = {}
- info["color"] = "r, 1, 1, 1"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1, g, 1, 1"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1, 1, b, 1"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "1, 1, 1, a"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- ## too many parts
- info = {}
- info["color"] = "1, 0, 0, 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- ## < 0 in each position
- info = {}
- info["color"] = "-1, 0, 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "0, -1, 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "0, 0, -1, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "0, 0, 0, -1"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- ## > 1 in each position
- info = {}
- info["color"] = "2, 0, 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "0, 2, 0, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "0, 0, 2, 0"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
- info = {}
- info["color"] = "0, 0, 0, 2"
- self.makeUFO(layerInfo=info)
- reader = UFOReader(self.ufoPath, validate=True)
- glyphSet = reader.getGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.ufoPath = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def makeUFO(self, formatVersion=3, layerInfo=None):
+ self.clearUFO()
+ if not os.path.exists(self.ufoPath):
+ os.mkdir(self.ufoPath)
+ # metainfo.plist
+ metaInfo = dict(creator="test", formatVersion=formatVersion)
+ path = os.path.join(self.ufoPath, "metainfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(metaInfo, f)
+ # layercontents.plist
+ layerContents = [("public.default", "glyphs")]
+ path = os.path.join(self.ufoPath, "layercontents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(layerContents, f)
+ # glyphs
+ glyphsPath = os.path.join(self.ufoPath, "glyphs")
+ os.mkdir(glyphsPath)
+ contents = dict(a="a.glif")
+ path = os.path.join(glyphsPath, "contents.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(contents, f)
+ path = os.path.join(glyphsPath, "a.glif")
+ with open(path, "w") as f:
+ f.write(" ")
+ # layerinfo.plist
+ if layerInfo is None:
+ layerInfo = dict(color="0,0,0,1", lib={"foo": "bar"})
+ path = os.path.join(glyphsPath, "layerinfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(layerInfo, f)
+
+ def clearUFO(self):
+ if os.path.exists(self.ufoPath):
+ shutil.rmtree(self.ufoPath)
+
+ def testValidLayerInfo(self):
+ self.makeUFO()
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ info = TestLayerInfoObject()
+ glyphSet.readLayerInfo(info)
+ expectedColor = "0,0,0,1"
+ self.assertEqual(expectedColor, info.color)
+ expectedLib = {"foo": "bar"}
+ self.assertEqual(expectedLib, info.lib)
+
+ def testMissingLayerInfo(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
+ os.remove(path)
+ # read
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ info = TestLayerInfoObject()
+ glyphSet.readLayerInfo(info)
+ self.assertEqual(None, info.color)
+ self.assertEqual(None, info.guidelines)
+ self.assertEqual(None, info.lib)
+
+ def testBogusLayerInfo(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
+ os.remove(path)
+ with open(path, "w") as f:
+ f.write("test")
+ # read
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ info = TestLayerInfoObject()
+ self.assertRaises(UFOLibError, glyphSet.readLayerInfo, info)
+
+ def testInvalidFormatLayerInfo(self):
+ self.makeUFO()
+ path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
+ info = [("color", "0,0,0,0")]
+ with open(path, "wb") as f:
+ plistlib.dump(info, f)
+ # read
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ info = TestLayerInfoObject()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, info)
+
+ def testColor(self):
+ ## not a string
+ info = {}
+ info["color"] = 1
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ ## not enough commas
+ info = {}
+ info["color"] = "1 0, 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1 0 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1 0 0 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ ## not enough parts
+ info = {}
+ info["color"] = ", 0, 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1, , 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1, 0, , 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1, 0, 0, "
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = ", , , "
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ ## not a number in all positions
+ info = {}
+ info["color"] = "r, 1, 1, 1"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1, g, 1, 1"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1, 1, b, 1"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "1, 1, 1, a"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ ## too many parts
+ info = {}
+ info["color"] = "1, 0, 0, 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ ## < 0 in each position
+ info = {}
+ info["color"] = "-1, 0, 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "0, -1, 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "0, 0, -1, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "0, 0, 0, -1"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ ## > 1 in each position
+ info = {}
+ info["color"] = "2, 0, 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "0, 2, 0, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "0, 0, 2, 0"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
+ info = {}
+ info["color"] = "0, 0, 0, 2"
+ self.makeUFO(layerInfo=info)
+ reader = UFOReader(self.ufoPath, validate=True)
+ glyphSet = reader.getGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.readLayerInfo, TestLayerInfoObject())
class UFO3WriteLayerInfoTestCase(unittest.TestCase):
-
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.ufoPath = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def makeGlyphSet(self):
- self.clearUFO()
- writer = UFOWriter(self.ufoPath)
- return writer.getGlyphSet()
-
- def clearUFO(self):
- if os.path.exists(self.ufoPath):
- shutil.rmtree(self.ufoPath)
-
- def testValidWrite(self):
- expected = dict(
- color="0,0,0,1",
- lib={"foo" : "bar"}
- )
- info = TestLayerInfoObject()
- info.color = expected["color"]
- info.lib = expected["lib"]
- glyphSet = self.makeGlyphSet()
- glyphSet.writeLayerInfo(info)
- path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
- with open(path, "rb") as f:
- result = plistlib.load(f)
- self.assertEqual(expected, result)
-
- def testColor(self):
- ## not a string
- info = TestLayerInfoObject()
- info.color = 1
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- ## not enough commas
- info = TestLayerInfoObject()
- info.color = "1 0, 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1 0 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1 0 0 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- ## not enough parts
- info = TestLayerInfoObject()
- info.color = ", 0, 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1, , 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1, 0, , 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1, 0, 0, "
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = ", , , "
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- ## not a number in all positions
- info = TestLayerInfoObject()
- info.color = "r, 1, 1, 1"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1, g, 1, 1"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1, 1, b, 1"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "1, 1, 1, a"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- ## too many parts
- info = TestLayerInfoObject()
- info.color = "1, 0, 0, 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- ## < 0 in each position
- info = TestLayerInfoObject()
- info.color = "-1, 0, 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "0, -1, 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "0, 0, -1, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "0, 0, 0, -1"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- ## > 1 in each position
- info = TestLayerInfoObject()
- info.color = "2, 0, 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "0, 2, 0, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "0, 0, 2, 0"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
- info = TestLayerInfoObject()
- info.color = "0, 0, 0, 2"
- glyphSet = self.makeGlyphSet()
- self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.ufoPath = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def makeGlyphSet(self):
+ self.clearUFO()
+ writer = UFOWriter(self.ufoPath)
+ return writer.getGlyphSet()
+
+ def clearUFO(self):
+ if os.path.exists(self.ufoPath):
+ shutil.rmtree(self.ufoPath)
+
+ def testValidWrite(self):
+ expected = dict(color="0,0,0,1", lib={"foo": "bar"})
+ info = TestLayerInfoObject()
+ info.color = expected["color"]
+ info.lib = expected["lib"]
+ glyphSet = self.makeGlyphSet()
+ glyphSet.writeLayerInfo(info)
+ path = os.path.join(self.ufoPath, "glyphs", "layerinfo.plist")
+ with open(path, "rb") as f:
+ result = plistlib.load(f)
+ self.assertEqual(expected, result)
+
+ def testColor(self):
+ ## not a string
+ info = TestLayerInfoObject()
+ info.color = 1
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ ## not enough commas
+ info = TestLayerInfoObject()
+ info.color = "1 0, 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1 0 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1 0 0 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ ## not enough parts
+ info = TestLayerInfoObject()
+ info.color = ", 0, 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1, , 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1, 0, , 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1, 0, 0, "
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = ", , , "
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ ## not a number in all positions
+ info = TestLayerInfoObject()
+ info.color = "r, 1, 1, 1"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1, g, 1, 1"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1, 1, b, 1"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "1, 1, 1, a"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ ## too many parts
+ info = TestLayerInfoObject()
+ info.color = "1, 0, 0, 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ ## < 0 in each position
+ info = TestLayerInfoObject()
+ info.color = "-1, 0, 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "0, -1, 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "0, 0, -1, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "0, 0, 0, -1"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ ## > 1 in each position
+ info = TestLayerInfoObject()
+ info.color = "2, 0, 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "0, 2, 0, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "0, 0, 2, 0"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
+ info = TestLayerInfoObject()
+ info.color = "0, 0, 0, 2"
+ glyphSet = self.makeGlyphSet()
+ self.assertRaises(GlifLibError, glyphSet.writeLayerInfo, info)
diff --git a/Tests/ufoLib/UFOConversion_test.py b/Tests/ufoLib/UFOConversion_test.py
index 98a08121..5519cef6 100644
--- a/Tests/ufoLib/UFOConversion_test.py
+++ b/Tests/ufoLib/UFOConversion_test.py
@@ -11,357 +11,314 @@ from .testSupport import expectedFontInfo1To2Conversion, expectedFontInfo2To1Con
# the format version 1 lib.plist contains some data
# that these tests shouldn't be concerned about.
removeFromFormatVersion1Lib = [
- "org.robofab.opentype.classes",
- "org.robofab.opentype.features",
- "org.robofab.opentype.featureorder",
- "org.robofab.postScriptHintData"
+ "org.robofab.opentype.classes",
+ "org.robofab.opentype.features",
+ "org.robofab.opentype.featureorder",
+ "org.robofab.postScriptHintData",
]
class ConversionFunctionsTestCase(unittest.TestCase):
-
- def tearDown(self):
- path = self.getFontPath("TestFont1 (UFO1) converted.ufo")
- if os.path.exists(path):
- shutil.rmtree(path)
- path = self.getFontPath("TestFont1 (UFO2) converted.ufo")
- if os.path.exists(path):
- shutil.rmtree(path)
-
- def getFontPath(self, fileName):
- testdata = os.path.join(os.path.dirname(__file__), "testdata")
- return os.path.join(testdata, fileName)
-
- def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures):
- # result
- metainfoPath1 = os.path.join(path1, "metainfo.plist")
- fontinfoPath1 = os.path.join(path1, "fontinfo.plist")
- kerningPath1 = os.path.join(path1, "kerning.plist")
- groupsPath1 = os.path.join(path1, "groups.plist")
- libPath1 = os.path.join(path1, "lib.plist")
- featuresPath1 = os.path.join(path1, "features.plist")
- glyphsPath1 = os.path.join(path1, "glyphs")
- glyphsPath1_contents = os.path.join(glyphsPath1, "contents.plist")
- glyphsPath1_A = os.path.join(glyphsPath1, "A_.glif")
- glyphsPath1_B = os.path.join(glyphsPath1, "B_.glif")
- # expected result
- metainfoPath2 = os.path.join(path2, "metainfo.plist")
- fontinfoPath2 = os.path.join(path2, "fontinfo.plist")
- kerningPath2 = os.path.join(path2, "kerning.plist")
- groupsPath2 = os.path.join(path2, "groups.plist")
- libPath2 = os.path.join(path2, "lib.plist")
- featuresPath2 = os.path.join(path2, "features.plist")
- glyphsPath2 = os.path.join(path2, "glyphs")
- glyphsPath2_contents = os.path.join(glyphsPath2, "contents.plist")
- glyphsPath2_A = os.path.join(glyphsPath2, "A_.glif")
- glyphsPath2_B = os.path.join(glyphsPath2, "B_.glif")
- # look for existence
- self.assertEqual(os.path.exists(metainfoPath1), True)
- self.assertEqual(os.path.exists(fontinfoPath1), True)
- self.assertEqual(os.path.exists(kerningPath1), True)
- self.assertEqual(os.path.exists(groupsPath1), True)
- self.assertEqual(os.path.exists(libPath1), True)
- self.assertEqual(os.path.exists(glyphsPath1), True)
- self.assertEqual(os.path.exists(glyphsPath1_contents), True)
- self.assertEqual(os.path.exists(glyphsPath1_A), True)
- self.assertEqual(os.path.exists(glyphsPath1_B), True)
- if testFeatures:
- self.assertEqual(os.path.exists(featuresPath1), True)
- # look for aggrement
- with open(metainfoPath1, "rb") as f:
- data1 = plistlib.load(f)
- with open(metainfoPath2, "rb") as f:
- data2 = plistlib.load(f)
- self.assertEqual(data1, data2)
- with open(fontinfoPath1, "rb") as f:
- data1 = plistlib.load(f)
- self.assertEqual(sorted(data1.items()), sorted(expectedInfoData.items()))
- with open(kerningPath1, "rb") as f:
- data1 = plistlib.load(f)
- with open(kerningPath2, "rb") as f:
- data2 = plistlib.load(f)
- self.assertEqual(data1, data2)
- with open(groupsPath1, "rb") as f:
- data1 = plistlib.load(f)
- with open(groupsPath2, "rb") as f:
- data2 = plistlib.load(f)
- self.assertEqual(data1, data2)
- with open(libPath1, "rb") as f:
- data1 = plistlib.load(f)
- with open(libPath2, "rb") as f:
- data2 = plistlib.load(f)
- if "UFO1" in libPath1:
- for key in removeFromFormatVersion1Lib:
- if key in data1:
- del data1[key]
- if "UFO1" in libPath2:
- for key in removeFromFormatVersion1Lib:
- if key in data2:
- del data2[key]
- self.assertEqual(data1, data2)
- with open(glyphsPath1_contents, "rb") as f:
- data1 = plistlib.load(f)
- with open(glyphsPath2_contents, "rb") as f:
- data2 = plistlib.load(f)
- self.assertEqual(data1, data2)
- with open(glyphsPath1_A, "rb") as f:
- data1 = plistlib.load(f)
- with open(glyphsPath2_A, "rb") as f:
- data2 = plistlib.load(f)
- self.assertEqual(data1, data2)
- with open(glyphsPath1_B, "rb") as f:
- data1 = plistlib.load(f)
- with open(glyphsPath2_B, "rb") as f:
- data2 = plistlib.load(f)
- self.assertEqual(data1, data2)
+ def tearDown(self):
+ path = self.getFontPath("TestFont1 (UFO1) converted.ufo")
+ if os.path.exists(path):
+ shutil.rmtree(path)
+ path = self.getFontPath("TestFont1 (UFO2) converted.ufo")
+ if os.path.exists(path):
+ shutil.rmtree(path)
+
+ def getFontPath(self, fileName):
+ testdata = os.path.join(os.path.dirname(__file__), "testdata")
+ return os.path.join(testdata, fileName)
+
+ def compareFileStructures(self, path1, path2, expectedInfoData, testFeatures):
+ # result
+ metainfoPath1 = os.path.join(path1, "metainfo.plist")
+ fontinfoPath1 = os.path.join(path1, "fontinfo.plist")
+ kerningPath1 = os.path.join(path1, "kerning.plist")
+ groupsPath1 = os.path.join(path1, "groups.plist")
+ libPath1 = os.path.join(path1, "lib.plist")
+ featuresPath1 = os.path.join(path1, "features.plist")
+ glyphsPath1 = os.path.join(path1, "glyphs")
+ glyphsPath1_contents = os.path.join(glyphsPath1, "contents.plist")
+ glyphsPath1_A = os.path.join(glyphsPath1, "A_.glif")
+ glyphsPath1_B = os.path.join(glyphsPath1, "B_.glif")
+ # expected result
+ metainfoPath2 = os.path.join(path2, "metainfo.plist")
+ fontinfoPath2 = os.path.join(path2, "fontinfo.plist")
+ kerningPath2 = os.path.join(path2, "kerning.plist")
+ groupsPath2 = os.path.join(path2, "groups.plist")
+ libPath2 = os.path.join(path2, "lib.plist")
+ featuresPath2 = os.path.join(path2, "features.plist")
+ glyphsPath2 = os.path.join(path2, "glyphs")
+ glyphsPath2_contents = os.path.join(glyphsPath2, "contents.plist")
+ glyphsPath2_A = os.path.join(glyphsPath2, "A_.glif")
+ glyphsPath2_B = os.path.join(glyphsPath2, "B_.glif")
+ # look for existence
+ self.assertEqual(os.path.exists(metainfoPath1), True)
+ self.assertEqual(os.path.exists(fontinfoPath1), True)
+ self.assertEqual(os.path.exists(kerningPath1), True)
+ self.assertEqual(os.path.exists(groupsPath1), True)
+ self.assertEqual(os.path.exists(libPath1), True)
+ self.assertEqual(os.path.exists(glyphsPath1), True)
+ self.assertEqual(os.path.exists(glyphsPath1_contents), True)
+ self.assertEqual(os.path.exists(glyphsPath1_A), True)
+ self.assertEqual(os.path.exists(glyphsPath1_B), True)
+ if testFeatures:
+ self.assertEqual(os.path.exists(featuresPath1), True)
+ # look for aggrement
+ with open(metainfoPath1, "rb") as f:
+ data1 = plistlib.load(f)
+ with open(metainfoPath2, "rb") as f:
+ data2 = plistlib.load(f)
+ self.assertEqual(data1, data2)
+ with open(fontinfoPath1, "rb") as f:
+ data1 = plistlib.load(f)
+ self.assertEqual(sorted(data1.items()), sorted(expectedInfoData.items()))
+ with open(kerningPath1, "rb") as f:
+ data1 = plistlib.load(f)
+ with open(kerningPath2, "rb") as f:
+ data2 = plistlib.load(f)
+ self.assertEqual(data1, data2)
+ with open(groupsPath1, "rb") as f:
+ data1 = plistlib.load(f)
+ with open(groupsPath2, "rb") as f:
+ data2 = plistlib.load(f)
+ self.assertEqual(data1, data2)
+ with open(libPath1, "rb") as f:
+ data1 = plistlib.load(f)
+ with open(libPath2, "rb") as f:
+ data2 = plistlib.load(f)
+ if "UFO1" in libPath1:
+ for key in removeFromFormatVersion1Lib:
+ if key in data1:
+ del data1[key]
+ if "UFO1" in libPath2:
+ for key in removeFromFormatVersion1Lib:
+ if key in data2:
+ del data2[key]
+ self.assertEqual(data1, data2)
+ with open(glyphsPath1_contents, "rb") as f:
+ data1 = plistlib.load(f)
+ with open(glyphsPath2_contents, "rb") as f:
+ data2 = plistlib.load(f)
+ self.assertEqual(data1, data2)
+ with open(glyphsPath1_A, "rb") as f:
+ data1 = plistlib.load(f)
+ with open(glyphsPath2_A, "rb") as f:
+ data2 = plistlib.load(f)
+ self.assertEqual(data1, data2)
+ with open(glyphsPath1_B, "rb") as f:
+ data1 = plistlib.load(f)
+ with open(glyphsPath2_B, "rb") as f:
+ data2 = plistlib.load(f)
+ self.assertEqual(data1, data2)
# ---------------------
# kerning up conversion
# ---------------------
-class TestInfoObject: pass
-
-
-class KerningUpConversionTestCase(unittest.TestCase):
-
- expectedKerning = {
- ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
- ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
- ("public.kern1.BGroup", "A"): 5,
- ("public.kern1.BGroup", "B"): 6,
- ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
- ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
- ("public.kern1.CGroup", "A"): 9,
- ("public.kern1.CGroup", "B"): 10,
- ("A", "public.kern2.CGroup"): 3,
- ("A", "public.kern2.DGroup"): 4,
- ("A", "A"): 1,
- ("A", "B"): 2,
- ("X", "A"): 13,
- ("X", "public.kern2.CGroup"): 14
- }
-
- expectedGroups = {
- "BGroup": ["B"],
- "CGroup": ["C", "Ccedilla"],
- "DGroup": ["D"],
- "public.kern1.BGroup": ["B"],
- "public.kern1.CGroup": ["C", "Ccedilla"],
- "public.kern2.CGroup": ["C", "Ccedilla"],
- "public.kern2.DGroup": ["D"],
- "Not A Kerning Group" : ["A"],
- "X": ["X", "X.sc"]
- }
-
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.ufoPath = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def makeUFO(self, formatVersion):
- 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")
- with open(path, "wb") as f:
- plistlib.dump(metaInfo, f)
- # kerning
- kerning = {
- "A" : {
- "A" : 1,
- "B" : 2,
- "CGroup" : 3,
- "DGroup" : 4
- },
- "BGroup" : {
- "A" : 5,
- "B" : 6,
- "CGroup" : 7,
- "DGroup" : 8
- },
- "CGroup" : {
- "A" : 9,
- "B" : 10,
- "CGroup" : 11,
- "DGroup" : 12
- },
- "X": {
- "A" : 13,
- "CGroup" : 14
- }
- }
- path = os.path.join(self.ufoPath, "kerning.plist")
- with open(path, "wb") as f:
- plistlib.dump(kerning, f)
- # groups
- groups = {
- "BGroup" : ["B"],
- "CGroup" : ["C", "Ccedilla"],
- "DGroup" : ["D"],
- "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:
- plistlib.dump(groups, f)
- # font info
- fontInfo = {
- "familyName" : "Test"
- }
- path = os.path.join(self.ufoPath, "fontinfo.plist")
- with open(path, "wb") as f:
- plistlib.dump(fontInfo, f)
+class TestInfoObject:
+ pass
- def clearUFO(self):
- if os.path.exists(self.ufoPath):
- shutil.rmtree(self.ufoPath)
- def testUFO1(self):
- self.makeUFO(formatVersion=2)
- reader = UFOReader(self.ufoPath, validate=True)
- kerning = reader.readKerning()
- self.assertEqual(self.expectedKerning, kerning)
- groups = reader.readGroups()
- self.assertEqual(self.expectedGroups, groups)
- info = TestInfoObject()
- reader.readInfo(info)
-
- def testUFO2(self):
- self.makeUFO(formatVersion=2)
- reader = UFOReader(self.ufoPath, validate=True)
- kerning = reader.readKerning()
- self.assertEqual(self.expectedKerning, kerning)
- groups = reader.readGroups()
- self.assertEqual(self.expectedGroups, groups)
- info = TestInfoObject()
- reader.readInfo(info)
+class KerningUpConversionTestCase(unittest.TestCase):
+ expectedKerning = {
+ ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
+ ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
+ ("public.kern1.BGroup", "A"): 5,
+ ("public.kern1.BGroup", "B"): 6,
+ ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
+ ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
+ ("public.kern1.CGroup", "A"): 9,
+ ("public.kern1.CGroup", "B"): 10,
+ ("A", "public.kern2.CGroup"): 3,
+ ("A", "public.kern2.DGroup"): 4,
+ ("A", "A"): 1,
+ ("A", "B"): 2,
+ ("X", "A"): 13,
+ ("X", "public.kern2.CGroup"): 14,
+ }
+
+ expectedGroups = {
+ "BGroup": ["B"],
+ "CGroup": ["C", "Ccedilla"],
+ "DGroup": ["D"],
+ "public.kern1.BGroup": ["B"],
+ "public.kern1.CGroup": ["C", "Ccedilla"],
+ "public.kern2.CGroup": ["C", "Ccedilla"],
+ "public.kern2.DGroup": ["D"],
+ "Not A Kerning Group": ["A"],
+ "X": ["X", "X.sc"],
+ }
+
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.ufoPath = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def makeUFO(self, formatVersion):
+ 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")
+ with open(path, "wb") as f:
+ plistlib.dump(metaInfo, f)
+ # kerning
+ kerning = {
+ "A": {"A": 1, "B": 2, "CGroup": 3, "DGroup": 4},
+ "BGroup": {"A": 5, "B": 6, "CGroup": 7, "DGroup": 8},
+ "CGroup": {"A": 9, "B": 10, "CGroup": 11, "DGroup": 12},
+ "X": {"A": 13, "CGroup": 14},
+ }
+ path = os.path.join(self.ufoPath, "kerning.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(kerning, f)
+ # groups
+ groups = {
+ "BGroup": ["B"],
+ "CGroup": ["C", "Ccedilla"],
+ "DGroup": ["D"],
+ "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:
+ plistlib.dump(groups, f)
+ # font info
+ fontInfo = {"familyName": "Test"}
+ path = os.path.join(self.ufoPath, "fontinfo.plist")
+ with open(path, "wb") as f:
+ plistlib.dump(fontInfo, f)
+
+ def clearUFO(self):
+ if os.path.exists(self.ufoPath):
+ shutil.rmtree(self.ufoPath)
+
+ def testUFO1(self):
+ self.makeUFO(formatVersion=2)
+ reader = UFOReader(self.ufoPath, validate=True)
+ kerning = reader.readKerning()
+ self.assertEqual(self.expectedKerning, kerning)
+ groups = reader.readGroups()
+ self.assertEqual(self.expectedGroups, groups)
+ info = TestInfoObject()
+ reader.readInfo(info)
+
+ def testUFO2(self):
+ self.makeUFO(formatVersion=2)
+ reader = UFOReader(self.ufoPath, validate=True)
+ kerning = reader.readKerning()
+ self.assertEqual(self.expectedKerning, kerning)
+ groups = reader.readGroups()
+ self.assertEqual(self.expectedGroups, groups)
+ info = TestInfoObject()
+ reader.readInfo(info)
class KerningDownConversionTestCase(unittest.TestCase):
-
- expectedKerning = {
- ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
- ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
- ("public.kern1.BGroup", "A"): 5,
- ("public.kern1.BGroup", "B"): 6,
- ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
- ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
- ("public.kern1.CGroup", "A"): 9,
- ("public.kern1.CGroup", "B"): 10,
- ("A", "public.kern2.CGroup"): 3,
- ("A", "public.kern2.DGroup"): 4,
- ("A", "A"): 1,
- ("A", "B"): 2
- }
-
- groups = {
- "BGroup": ["B"],
- "CGroup": ["C"],
- "DGroup": ["D"],
- "public.kern1.BGroup": ["B"],
- "public.kern1.CGroup": ["C", "Ccedilla"],
- "public.kern2.CGroup": ["C", "Ccedilla"],
- "public.kern2.DGroup": ["D"],
- "Not A Kerning Group" : ["A"]
- }
- expectedWrittenGroups = {
- "BGroup": ["B"],
- "CGroup": ["C", "Ccedilla"],
- "DGroup": ["D"],
- "Not A Kerning Group" : ["A"]
- }
-
- kerning = {
- ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
- ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
- ("public.kern1.BGroup", "A"): 5,
- ("public.kern1.BGroup", "B"): 6,
- ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
- ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
- ("public.kern1.CGroup", "A"): 9,
- ("public.kern1.CGroup", "B"): 10,
- ("A", "public.kern2.CGroup"): 3,
- ("A", "public.kern2.DGroup"): 4,
- ("A", "A"): 1,
- ("A", "B"): 2
- }
- expectedWrittenKerning = {
- "BGroup" : {
- "CGroup" : 7,
- "DGroup" : 8,
- "A" : 5,
- "B" : 6
- },
- "CGroup" : {
- "CGroup" : 11,
- "DGroup" : 12,
- "A" : 9,
- "B" : 10
- },
- "A" : {
- "CGroup" : 3,
- "DGroup" : 4,
- "A" : 1,
- "B" : 2
- }
- }
-
-
- downConversionMapping = {
- "side1" : {
- "BGroup" : "public.kern1.BGroup",
- "CGroup" : "public.kern1.CGroup"
- },
- "side2" : {
- "CGroup" : "public.kern2.CGroup",
- "DGroup" : "public.kern2.DGroup"
- }
- }
-
- def setUp(self):
- self.tempDir = tempfile.mktemp()
- os.mkdir(self.tempDir)
- self.dstDir = os.path.join(self.tempDir, "test.ufo")
-
- def tearDown(self):
- shutil.rmtree(self.tempDir)
-
- def tearDownUFO(self):
- shutil.rmtree(self.dstDir)
-
- def testWrite(self):
- writer = UFOWriter(self.dstDir, formatVersion=2)
- writer.setKerningGroupConversionRenameMaps(self.downConversionMapping)
- writer.writeKerning(self.kerning)
- writer.writeGroups(self.groups)
- # test groups
- path = os.path.join(self.dstDir, "groups.plist")
- with open(path, "rb") as f:
- writtenGroups = plistlib.load(f)
- self.assertEqual(writtenGroups, self.expectedWrittenGroups)
- # test kerning
- path = os.path.join(self.dstDir, "kerning.plist")
- with open(path, "rb") as f:
- writtenKerning = plistlib.load(f)
- self.assertEqual(writtenKerning, self.expectedWrittenKerning)
- self.tearDownUFO()
+ expectedKerning = {
+ ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
+ ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
+ ("public.kern1.BGroup", "A"): 5,
+ ("public.kern1.BGroup", "B"): 6,
+ ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
+ ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
+ ("public.kern1.CGroup", "A"): 9,
+ ("public.kern1.CGroup", "B"): 10,
+ ("A", "public.kern2.CGroup"): 3,
+ ("A", "public.kern2.DGroup"): 4,
+ ("A", "A"): 1,
+ ("A", "B"): 2,
+ }
+
+ groups = {
+ "BGroup": ["B"],
+ "CGroup": ["C"],
+ "DGroup": ["D"],
+ "public.kern1.BGroup": ["B"],
+ "public.kern1.CGroup": ["C", "Ccedilla"],
+ "public.kern2.CGroup": ["C", "Ccedilla"],
+ "public.kern2.DGroup": ["D"],
+ "Not A Kerning Group": ["A"],
+ }
+ expectedWrittenGroups = {
+ "BGroup": ["B"],
+ "CGroup": ["C", "Ccedilla"],
+ "DGroup": ["D"],
+ "Not A Kerning Group": ["A"],
+ }
+
+ kerning = {
+ ("public.kern1.BGroup", "public.kern2.CGroup"): 7,
+ ("public.kern1.BGroup", "public.kern2.DGroup"): 8,
+ ("public.kern1.BGroup", "A"): 5,
+ ("public.kern1.BGroup", "B"): 6,
+ ("public.kern1.CGroup", "public.kern2.CGroup"): 11,
+ ("public.kern1.CGroup", "public.kern2.DGroup"): 12,
+ ("public.kern1.CGroup", "A"): 9,
+ ("public.kern1.CGroup", "B"): 10,
+ ("A", "public.kern2.CGroup"): 3,
+ ("A", "public.kern2.DGroup"): 4,
+ ("A", "A"): 1,
+ ("A", "B"): 2,
+ }
+ expectedWrittenKerning = {
+ "BGroup": {"CGroup": 7, "DGroup": 8, "A": 5, "B": 6},
+ "CGroup": {"CGroup": 11, "DGroup": 12, "A": 9, "B": 10},
+ "A": {"CGroup": 3, "DGroup": 4, "A": 1, "B": 2},
+ }
+
+ downConversionMapping = {
+ "side1": {"BGroup": "public.kern1.BGroup", "CGroup": "public.kern1.CGroup"},
+ "side2": {"CGroup": "public.kern2.CGroup", "DGroup": "public.kern2.DGroup"},
+ }
+
+ def setUp(self):
+ self.tempDir = tempfile.mktemp()
+ os.mkdir(self.tempDir)
+ self.dstDir = os.path.join(self.tempDir, "test.ufo")
+
+ def tearDown(self):
+ shutil.rmtree(self.tempDir)
+
+ def tearDownUFO(self):
+ shutil.rmtree(self.dstDir)
+
+ def testWrite(self):
+ writer = UFOWriter(self.dstDir, formatVersion=2)
+ writer.setKerningGroupConversionRenameMaps(self.downConversionMapping)
+ writer.writeKerning(self.kerning)
+ writer.writeGroups(self.groups)
+ # test groups
+ path = os.path.join(self.dstDir, "groups.plist")
+ with open(path, "rb") as f:
+ writtenGroups = plistlib.load(f)
+ self.assertEqual(writtenGroups, self.expectedWrittenGroups)
+ # test kerning
+ path = os.path.join(self.dstDir, "kerning.plist")
+ with open(path, "rb") as f:
+ writtenKerning = plistlib.load(f)
+ self.assertEqual(writtenKerning, self.expectedWrittenKerning)
+ self.tearDownUFO()
diff --git a/Tests/ufoLib/UFOZ_test.py b/Tests/ufoLib/UFOZ_test.py
index 6ea39e9a..e2b35034 100644
--- a/Tests/ufoLib/UFOZ_test.py
+++ b/Tests/ufoLib/UFOZ_test.py
@@ -12,9 +12,7 @@ import pytest
import warnings
-TESTDATA = fs.osfs.OSFS(
- os.path.join(os.path.dirname(__file__), "testdata")
-)
+TESTDATA = fs.osfs.OSFS(os.path.join(os.path.dirname(__file__), "testdata"))
TEST_UFO3 = "TestFont1 (UFO3).ufo"
TEST_UFOZ = "TestFont1 (UFO3).ufoz"
@@ -38,7 +36,6 @@ def testufoz():
class TestUFOZ:
-
def test_read(self, testufoz):
with UFOReader(testufoz) as reader:
assert reader.fileStructure == UFOFileStructure.ZIP
@@ -52,9 +49,7 @@ class TestUFOZ:
def test_pathlike(testufo):
-
class PathLike:
-
def __init__(self, s):
self._path = s
@@ -84,7 +79,6 @@ def memufo():
class TestMemoryFS:
-
def test_init_reader(self, memufo):
with UFOReader(memufo) as reader:
assert reader.formatVersion == 3
diff --git a/Tests/ufoLib/__init__.py b/Tests/ufoLib/__init__.py
index e69de29b..e563776a 100644
--- a/Tests/ufoLib/__init__.py
+++ b/Tests/ufoLib/__init__.py
@@ -0,0 +1,3 @@
+import pytest
+
+pytest.importorskip("fontTools.ufoLib")
diff --git a/Tests/ufoLib/filenames_test.py b/Tests/ufoLib/filenames_test.py
index bad41353..22fb10b4 100644
--- a/Tests/ufoLib/filenames_test.py
+++ b/Tests/ufoLib/filenames_test.py
@@ -3,7 +3,6 @@ from fontTools.ufoLib.filenames import userNameToFileName, handleClash1, handleC
class TestFilenames(unittest.TestCase):
-
def test_userNameToFileName(self):
self.assertEqual(userNameToFileName("a"), "a")
self.assertEqual(userNameToFileName("A"), "A_")
@@ -21,8 +20,7 @@ class TestFilenames(unittest.TestCase):
self.assertEqual(userNameToFileName("t_h"), "t_h")
self.assertEqual(userNameToFileName("F_F_I"), "F__F__I_")
self.assertEqual(userNameToFileName("f_f_i"), "f_f_i")
- self.assertEqual(userNameToFileName("Aacute_V.swash"),
- "A_acute_V_.swash")
+ self.assertEqual(userNameToFileName("Aacute_V.swash"), "A_acute_V_.swash")
self.assertEqual(userNameToFileName(".notdef"), "_notdef")
self.assertEqual(userNameToFileName("con"), "_con")
self.assertEqual(userNameToFileName("CON"), "C_O_N_")
@@ -60,25 +58,22 @@ class TestFilenames(unittest.TestCase):
e = list(existing)
self.assertEqual(
- handleClash1(userName="A" * 5, existing=e, prefix=prefix,
- suffix=suffix),
- '00000.AAAAA000000000000001.0000000000'
+ handleClash1(userName="A" * 5, existing=e, prefix=prefix, suffix=suffix),
+ "00000.AAAAA000000000000001.0000000000",
)
e = list(existing)
e.append(prefix + "aaaaa" + "1".zfill(15) + suffix)
self.assertEqual(
- handleClash1(userName="A" * 5, existing=e, prefix=prefix,
- suffix=suffix),
- '00000.AAAAA000000000000002.0000000000'
+ handleClash1(userName="A" * 5, existing=e, prefix=prefix, suffix=suffix),
+ "00000.AAAAA000000000000002.0000000000",
)
e = list(existing)
e.append(prefix + "AAAAA" + "2".zfill(15) + suffix)
self.assertEqual(
- handleClash1(userName="A" * 5, existing=e, prefix=prefix,
- suffix=suffix),
- '00000.AAAAA000000000000001.0000000000'
+ handleClash1(userName="A" * 5, existing=e, prefix=prefix, suffix=suffix),
+ "00000.AAAAA000000000000001.0000000000",
)
def test_handleClash2(self):
@@ -89,19 +84,17 @@ class TestFilenames(unittest.TestCase):
e = list(existing)
self.assertEqual(
handleClash2(existing=e, prefix=prefix, suffix=suffix),
- '00000.100.0000000000'
+ "00000.100.0000000000",
)
e = list(existing)
e.remove(prefix + "1" + suffix)
self.assertEqual(
- handleClash2(existing=e, prefix=prefix, suffix=suffix),
- '00000.1.0000000000'
+ handleClash2(existing=e, prefix=prefix, suffix=suffix), "00000.1.0000000000"
)
e = list(existing)
e.remove(prefix + "2" + suffix)
self.assertEqual(
- handleClash2(existing=e, prefix=prefix, suffix=suffix),
- '00000.2.0000000000'
+ handleClash2(existing=e, prefix=prefix, suffix=suffix), "00000.2.0000000000"
)
diff --git a/Tests/ufoLib/glifLib_test.py b/Tests/ufoLib/glifLib_test.py
index 485c2bd9..8f48168d 100644
--- a/Tests/ufoLib/glifLib_test.py
+++ b/Tests/ufoLib/glifLib_test.py
@@ -3,12 +3,20 @@ import os
import tempfile
import shutil
import unittest
+from pathlib import Path
from io import open
from .testSupport import getDemoFontGlyphSetPath
from fontTools.ufoLib.glifLib import (
- GlyphSet, glyphNameToFileName, readGlyphFromString, writeGlyphToString,
+ GlyphSet,
+ glyphNameToFileName,
+ readGlyphFromString,
+ writeGlyphToString,
+)
+from fontTools.ufoLib.errors import (
+ GlifLibError,
+ UnsupportedGLIFFormat,
+ UnsupportedUFOFormat,
)
-from fontTools.ufoLib.errors import GlifLibError, UnsupportedGLIFFormat, UnsupportedUFOFormat
from fontTools.misc.etree import XML_DECLARATION
from fontTools.pens.recordingPen import RecordingPointPen
import pytest
@@ -17,191 +25,223 @@ GLYPHSETDIR = getDemoFontGlyphSetPath()
class GlyphSetTests(unittest.TestCase):
-
- def setUp(self):
- self.dstDir = tempfile.mktemp()
- os.mkdir(self.dstDir)
-
- def tearDown(self):
- shutil.rmtree(self.dstDir)
-
- def testRoundTrip(self):
- import difflib
- srcDir = GLYPHSETDIR
- dstDir = self.dstDir
- src = GlyphSet(srcDir, ufoFormatVersion=2, validateRead=True, validateWrite=True)
- dst = GlyphSet(dstDir, ufoFormatVersion=2, validateRead=True, validateWrite=True)
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- dst.writeGlyph(glyphName, g, g.drawPoints)
- # compare raw file data:
- for glyphName in sorted(src.keys()):
- fileName = src.contents[glyphName]
- with open(os.path.join(srcDir, fileName), "r") as f:
- org = f.read()
- with open(os.path.join(dstDir, fileName), "r") as f:
- new = f.read()
- added = []
- removed = []
- for line in difflib.unified_diff(
- org.split("\n"), new.split("\n")):
- if line.startswith("+ "):
- added.append(line[1:])
- elif line.startswith("- "):
- removed.append(line[1:])
- self.assertEqual(
- 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
- gset.rebuildContents()
- self.assertEqual(contents, gset.contents)
-
- def testReverseContents(self):
- gset = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
- d = {}
- for k, v in gset.getReverseContents().items():
- d[v] = k
- org = {}
- for k, v in gset.contents.items():
- org[k] = v.lower()
- self.assertEqual(d, org)
-
- def testReverseContents2(self):
- src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
- dst = GlyphSet(self.dstDir, validateRead=True, validateWrite=True)
- dstMap = dst.getReverseContents()
- self.assertEqual(dstMap, {})
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- dst.writeGlyph(glyphName, g, g.drawPoints)
- self.assertNotEqual(dstMap, {})
- srcMap = dict(src.getReverseContents()) # copy
- self.assertEqual(dstMap, srcMap)
- del srcMap["a.glif"]
- dst.deleteGlyph("a")
- self.assertEqual(dstMap, srcMap)
-
- def testCustomFileNamingScheme(self):
- def myGlyphNameToFileName(glyphName, glyphSet):
- return "prefix" + glyphNameToFileName(glyphName, glyphSet)
- src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
- dst = GlyphSet(self.dstDir, myGlyphNameToFileName, validateRead=True, validateWrite=True)
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- dst.writeGlyph(glyphName, g, g.drawPoints)
- d = {}
- for k, v in src.contents.items():
- d[k] = "prefix" + v
- self.assertEqual(d, dst.contents)
-
- def testGetUnicodes(self):
- src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
- unicodes = src.getUnicodes()
- for glyphName in src.keys():
- g = src[glyphName]
- g.drawPoints(None) # load attrs
- if not hasattr(g, "unicodes"):
- self.assertEqual(unicodes[glyphName], [])
- else:
- self.assertEqual(g.unicodes, unicodes[glyphName])
+ def setUp(self):
+ self.dstDir = tempfile.mktemp()
+ os.mkdir(self.dstDir)
+
+ def tearDown(self):
+ shutil.rmtree(self.dstDir)
+
+ def testRoundTrip(self):
+ import difflib
+
+ srcDir = GLYPHSETDIR
+ dstDir = self.dstDir
+ src = GlyphSet(
+ srcDir, ufoFormatVersion=2, validateRead=True, validateWrite=True
+ )
+ dst = GlyphSet(
+ dstDir, ufoFormatVersion=2, validateRead=True, validateWrite=True
+ )
+ for glyphName in src.keys():
+ g = src[glyphName]
+ g.drawPoints(None) # load attrs
+ dst.writeGlyph(glyphName, g, g.drawPoints)
+ # compare raw file data:
+ for glyphName in sorted(src.keys()):
+ fileName = src.contents[glyphName]
+ with open(os.path.join(srcDir, fileName), "r") as f:
+ org = f.read()
+ with open(os.path.join(dstDir, fileName), "r") as f:
+ new = f.read()
+ added = []
+ removed = []
+ for line in difflib.unified_diff(org.split("\n"), new.split("\n")):
+ if line.startswith("+ "):
+ added.append(line[1:])
+ elif line.startswith("- "):
+ removed.append(line[1:])
+ self.assertEqual(
+ 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
+ gset.rebuildContents()
+ self.assertEqual(contents, gset.contents)
+
+ def testReverseContents(self):
+ gset = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
+ d = {}
+ for k, v in gset.getReverseContents().items():
+ d[v] = k
+ org = {}
+ for k, v in gset.contents.items():
+ org[k] = v.lower()
+ self.assertEqual(d, org)
+
+ def testReverseContents2(self):
+ src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
+ dst = GlyphSet(self.dstDir, validateRead=True, validateWrite=True)
+ dstMap = dst.getReverseContents()
+ self.assertEqual(dstMap, {})
+ for glyphName in src.keys():
+ g = src[glyphName]
+ g.drawPoints(None) # load attrs
+ dst.writeGlyph(glyphName, g, g.drawPoints)
+ self.assertNotEqual(dstMap, {})
+ srcMap = dict(src.getReverseContents()) # copy
+ self.assertEqual(dstMap, srcMap)
+ del srcMap["a.glif"]
+ dst.deleteGlyph("a")
+ self.assertEqual(dstMap, srcMap)
+
+ def testCustomFileNamingScheme(self):
+ def myGlyphNameToFileName(glyphName, glyphSet):
+ return "prefix" + glyphNameToFileName(glyphName, glyphSet)
+
+ src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
+ dst = GlyphSet(
+ self.dstDir, myGlyphNameToFileName, validateRead=True, validateWrite=True
+ )
+ for glyphName in src.keys():
+ g = src[glyphName]
+ g.drawPoints(None) # load attrs
+ dst.writeGlyph(glyphName, g, g.drawPoints)
+ d = {}
+ for k, v in src.contents.items():
+ d[k] = "prefix" + v
+ self.assertEqual(d, dst.contents)
+
+ def testGetUnicodes(self):
+ src = GlyphSet(GLYPHSETDIR, validateRead=True, validateWrite=True)
+ unicodes = src.getUnicodes()
+ for glyphName in src.keys():
+ g = src[glyphName]
+ g.drawPoints(None) # load attrs
+ if not hasattr(g, "unicodes"):
+ self.assertEqual(unicodes[glyphName], [])
+ else:
+ self.assertEqual(g.unicodes, unicodes[glyphName])
+
+ def testReadGlyphInvalidXml(self):
+ """Test that calling readGlyph() to read a .glif with invalid XML raises
+ a library error, instead of an exception from the XML dependency that is
+ used internally. In addition, check that the raised exception describes
+ the glyph by name and gives the location of the broken .glif file."""
+
+ # Create a glyph set with three empty glyphs.
+ glyph_set = GlyphSet(self.dstDir)
+ glyph_set.writeGlyph("a", _Glyph())
+ glyph_set.writeGlyph("b", _Glyph())
+ glyph_set.writeGlyph("c", _Glyph())
+
+ # Corrupt the XML of /c.
+ invalid_xml = b"<abc></def>"
+ Path(self.dstDir, glyph_set.contents["c"]).write_bytes(invalid_xml)
+
+ # Confirm that reading /a and /b is fine...
+ glyph_set.readGlyph("a", _Glyph())
+ glyph_set.readGlyph("b", _Glyph())
+
+ # ...but that reading /c raises a descriptive library error.
+ expected_message = (
+ r"GLIF contains invalid XML\.\n"
+ r"The issue is in glyph 'c', located in '.*c\.glif.*\."
+ )
+ with pytest.raises(GlifLibError, match=expected_message):
+ glyph_set.readGlyph("c", _Glyph())
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.deleteGlyph("a_")
- 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
+ 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.deleteGlyph("a_")
+ 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
+ pass
class ReadWriteFuncTest:
+ def test_roundtrip(self):
+ glyph = _Glyph()
+ glyph.name = "a"
+ glyph.unicodes = [0x0061]
- def test_roundtrip(self):
- glyph = _Glyph()
- glyph.name = "a"
- glyph.unicodes = [0x0061]
-
- s1 = writeGlyphToString(glyph.name, glyph)
+ s1 = writeGlyphToString(glyph.name, glyph)
- glyph2 = _Glyph()
- readGlyphFromString(s1, glyph2)
- assert glyph.__dict__ == glyph2.__dict__
+ glyph2 = _Glyph()
+ readGlyphFromString(s1, glyph2)
+ assert glyph.__dict__ == glyph2.__dict__
- s2 = writeGlyphToString(glyph2.name, glyph2)
- assert s1 == s2
+ s2 = writeGlyphToString(glyph2.name, glyph2)
+ assert s1 == s2
- def test_xml_declaration(self):
- s = writeGlyphToString("a", _Glyph())
- assert s.startswith(XML_DECLARATION % "UTF-8")
+ def test_xml_declaration(self):
+ s = writeGlyphToString("a", _Glyph())
+ assert s.startswith(XML_DECLARATION % "UTF-8")
- def test_parse_xml_remove_comments(self):
- s = b"""<?xml version='1.0' encoding='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"/>
@@ -210,64 +250,74 @@ class ReadWriteFuncTest:
</glyph>
"""
- g = _Glyph()
- readGlyphFromString(s, g)
+ g = _Glyph()
+ readGlyphFromString(s, g)
- assert g.name == "A"
- assert g.width == 1290
- assert g.unicodes == [0x0041]
+ 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'?>
+ def test_read_invalid_xml(self):
+ """Test that calling readGlyphFromString() with invalid XML raises a
+ library error, instead of an exception from the XML dependency that is
+ used internally."""
+
+ invalid_xml = b"<abc></def>"
+ empty_glyph = _Glyph()
+
+ with pytest.raises(GlifLibError, match="GLIF contains invalid XML"):
+ readGlyphFromString(invalid_xml, empty_glyph)
+
+ 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 by default
- with pytest.raises(UnsupportedGLIFFormat):
- readGlyphFromString(s, _Glyph(), validate=True)
+ 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)
+ 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
+ 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'?>
+ 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)])
+ # 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)])
+ # 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(
+ 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])
+ 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."""
+ 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'?>
+ s = """<?xml version='1.0' encoding='utf-8'?>
<glyph name="A" format="2">
<outline>
<contour>
@@ -277,40 +327,41 @@ class ReadWriteFuncTest:
</outline>
</glyph>
"""
- pen = RecordingPointPen()
+ 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)
+ with pytest.raises(GlifLibError, match="Required y attribute"):
+ readGlyphFromString(s, _Glyph(), pen, validate=False)
- 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))
+ 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))
+ 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 49f6a539..b29e3a15 100755
--- a/Tests/ufoLib/testSupport.py
+++ b/Tests/ufoLib/testSupport.py
@@ -5,663 +5,651 @@ from fontTools.ufoLib.utils import numberTypes
def getDemoFontPath():
- """Return the path to Data/DemoFont.ufo/."""
- testdata = os.path.join(os.path.dirname(__file__), "testdata")
- return os.path.join(testdata, "DemoFont.ufo")
+ """Return the path to Data/DemoFont.ufo/."""
+ testdata = os.path.join(os.path.dirname(__file__), "testdata")
+ return os.path.join(testdata, "DemoFont.ufo")
def getDemoFontGlyphSetPath():
- """Return the path to Data/DemoFont.ufo/glyphs/."""
- return os.path.join(getDemoFontPath(), "glyphs")
+ """Return the path to Data/DemoFont.ufo/glyphs/."""
+ return os.path.join(getDemoFontPath(), "glyphs")
# GLIF test tools
+
class Glyph:
+ def __init__(self):
+ self.name = None
+ self.width = None
+ self.height = None
+ self.unicodes = None
+ self.note = None
+ self.lib = None
+ self.image = None
+ self.guidelines = None
+ self.anchors = None
+ self.outline = []
- def __init__(self):
- self.name = None
- self.width = None
- self.height = None
- self.unicodes = None
- self.note = None
- self.lib = None
- self.image = None
- self.guidelines = None
- self.anchors = None
- self.outline = []
+ def _writePointPenCommand(self, command, args, kwargs):
+ args = _listToString(args)
+ kwargs = _dictToString(kwargs)
+ if args and kwargs:
+ return f"pointPen.{command}(*{args}, **{kwargs})"
+ elif len(args):
+ return f"pointPen.{command}(*{args})"
+ elif len(kwargs):
+ return f"pointPen.{command}(**{kwargs})"
+ else:
+ return "pointPen.%s()" % command
- def _writePointPenCommand(self, command, args, kwargs):
- args = _listToString(args)
- kwargs = _dictToString(kwargs)
- if args and kwargs:
- return f"pointPen.{command}(*{args}, **{kwargs})"
- elif len(args):
- return f"pointPen.{command}(*{args})"
- elif len(kwargs):
- return f"pointPen.{command}(**{kwargs})"
- else:
- return "pointPen.%s()" % command
+ def beginPath(self, **kwargs):
+ self.outline.append(self._writePointPenCommand("beginPath", [], kwargs))
- def beginPath(self, **kwargs):
- self.outline.append(self._writePointPenCommand("beginPath", [], kwargs))
+ def endPath(self):
+ self.outline.append(self._writePointPenCommand("endPath", [], {}))
- def endPath(self):
- self.outline.append(self._writePointPenCommand("endPath", [], {}))
+ def addPoint(self, *args, **kwargs):
+ self.outline.append(self._writePointPenCommand("addPoint", args, kwargs))
- def addPoint(self, *args, **kwargs):
- self.outline.append(self._writePointPenCommand("addPoint", args, kwargs))
+ def addComponent(self, *args, **kwargs):
+ self.outline.append(self._writePointPenCommand("addComponent", args, kwargs))
- def addComponent(self, *args, **kwargs):
- self.outline.append(self._writePointPenCommand("addComponent", args, kwargs))
+ def drawPoints(self, pointPen):
+ if self.outline:
+ py = "\n".join(self.outline)
+ exec(py, {"pointPen": pointPen})
- def drawPoints(self, pointPen):
- if self.outline:
- py = "\n".join(self.outline)
- exec(py, {"pointPen" : pointPen})
+ def py(self):
+ text = []
+ if self.name is not None:
+ text.append('glyph.name = "%s"' % self.name)
+ if self.width:
+ text.append("glyph.width = %r" % self.width)
+ if self.height:
+ text.append("glyph.height = %r" % self.height)
+ if self.unicodes is not None:
+ text.append(
+ "glyph.unicodes = [%s]" % ", ".join([str(i) for i in self.unicodes])
+ )
+ if self.note is not None:
+ text.append('glyph.note = "%s"' % self.note)
+ if self.lib is not None:
+ text.append("glyph.lib = %s" % _dictToString(self.lib))
+ if self.image is not None:
+ text.append("glyph.image = %s" % _dictToString(self.image))
+ if self.guidelines is not None:
+ text.append("glyph.guidelines = %s" % _listToString(self.guidelines))
+ if self.anchors is not None:
+ text.append("glyph.anchors = %s" % _listToString(self.anchors))
+ if self.outline:
+ text += self.outline
+ return "\n".join(text)
- def py(self):
- text = []
- if self.name is not None:
- text.append("glyph.name = \"%s\"" % self.name)
- if self.width:
- text.append("glyph.width = %r" % self.width)
- if self.height:
- text.append("glyph.height = %r" % self.height)
- if self.unicodes is not None:
- text.append("glyph.unicodes = [%s]" % ", ".join([str(i) for i in self.unicodes]))
- if self.note is not None:
- text.append("glyph.note = \"%s\"" % self.note)
- if self.lib is not None:
- text.append("glyph.lib = %s" % _dictToString(self.lib))
- if self.image is not None:
- text.append("glyph.image = %s" % _dictToString(self.image))
- if self.guidelines is not None:
- text.append("glyph.guidelines = %s" % _listToString(self.guidelines))
- if self.anchors is not None:
- text.append("glyph.anchors = %s" % _listToString(self.anchors))
- if self.outline:
- text += self.outline
- return "\n".join(text)
def _dictToString(d):
- text = []
- for key, value in sorted(d.items()):
- if value is None:
- continue
- key = "\"%s\"" % key
- if isinstance(value, dict):
- value = _dictToString(value)
- elif isinstance(value, list):
- value = _listToString(value)
- elif isinstance(value, tuple):
- value = _tupleToString(value)
- elif isinstance(value, numberTypes):
- value = repr(value)
- elif isinstance(value, str):
- value = "\"%s\"" % value
- text.append(f"{key} : {value}")
- if not text:
- return ""
- return "{%s}" % ", ".join(text)
+ text = []
+ for key, value in sorted(d.items()):
+ if value is None:
+ continue
+ key = '"%s"' % key
+ if isinstance(value, dict):
+ value = _dictToString(value)
+ elif isinstance(value, list):
+ value = _listToString(value)
+ elif isinstance(value, tuple):
+ value = _tupleToString(value)
+ elif isinstance(value, numberTypes):
+ value = repr(value)
+ elif isinstance(value, str):
+ value = '"%s"' % value
+ text.append(f"{key} : {value}")
+ if not text:
+ return ""
+ return "{%s}" % ", ".join(text)
+
def _listToString(l):
- text = []
- for value in l:
- if isinstance(value, dict):
- value = _dictToString(value)
- elif isinstance(value, list):
- value = _listToString(value)
- elif isinstance(value, tuple):
- value = _tupleToString(value)
- elif isinstance(value, numberTypes):
- value = repr(value)
- elif isinstance(value, str):
- value = "\"%s\"" % value
- text.append(value)
- if not text:
- return ""
- return "[%s]" % ", ".join(text)
+ text = []
+ for value in l:
+ if isinstance(value, dict):
+ value = _dictToString(value)
+ elif isinstance(value, list):
+ value = _listToString(value)
+ elif isinstance(value, tuple):
+ value = _tupleToString(value)
+ elif isinstance(value, numberTypes):
+ value = repr(value)
+ elif isinstance(value, str):
+ value = '"%s"' % value
+ text.append(value)
+ if not text:
+ return ""
+ return "[%s]" % ", ".join(text)
+
def _tupleToString(t):
- text = []
- for value in t:
- if isinstance(value, dict):
- value = _dictToString(value)
- elif isinstance(value, list):
- value = _listToString(value)
- elif isinstance(value, tuple):
- value = _tupleToString(value)
- elif isinstance(value, numberTypes):
- value = repr(value)
- elif isinstance(value, str):
- value = "\"%s\"" % value
- text.append(value)
- if not text:
- return ""
- return "(%s)" % ", ".join(text)
+ text = []
+ for value in t:
+ if isinstance(value, dict):
+ value = _dictToString(value)
+ elif isinstance(value, list):
+ value = _listToString(value)
+ elif isinstance(value, tuple):
+ value = _tupleToString(value)
+ elif isinstance(value, numberTypes):
+ value = repr(value)
+ elif isinstance(value, str):
+ value = '"%s"' % value
+ text.append(value)
+ if not text:
+ return ""
+ return "(%s)" % ", ".join(text)
+
def stripText(text):
- new = []
- for line in text.strip().splitlines():
- line = line.strip()
- if not line:
- continue
- new.append(line)
- return "\n".join(new)
+ new = []
+ for line in text.strip().splitlines():
+ line = line.strip()
+ if not line:
+ continue
+ new.append(line)
+ return "\n".join(new)
+
# font info values used by several tests
fontInfoVersion1 = {
- "familyName" : "Some Font (Family Name)",
- "styleName" : "Regular (Style Name)",
- "fullName" : "Some Font-Regular (Postscript Full Name)",
- "fontName" : "SomeFont-Regular (Postscript Font Name)",
- "menuName" : "Some Font Regular (Style Map Family Name)",
- "fontStyle" : 64,
- "note" : "A note.",
- "versionMajor" : 1,
- "versionMinor" : 0,
- "year" : 2008,
- "copyright" : "Copyright Some Foundry.",
- "notice" : "Some Font by Some Designer for Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "license" : "License info for Some Foundry.",
- "licenseURL" : "http://somefoundry.com/license",
- "createdBy" : "Some Foundry",
- "designer" : "Some Designer",
- "designerURL" : "http://somedesigner.com",
- "vendorURL" : "http://somefoundry.com",
- "unitsPerEm" : 1000,
- "ascender" : 750,
- "descender" : -250,
- "capHeight" : 750,
- "xHeight" : 500,
- "defaultWidth" : 400,
- "slantAngle" : -12.5,
- "italicAngle" : -12.5,
- "widthName" : "Medium (normal)",
- "weightName" : "Medium",
- "weightValue" : 500,
- "fondName" : "SomeFont Regular (FOND Name)",
- "otFamilyName" : "Some Font (Preferred Family Name)",
- "otStyleName" : "Regular (Preferred Subfamily Name)",
- "otMacName" : "Some Font Regular (Compatible Full Name)",
- "msCharSet" : 0,
- "fondID" : 15000,
- "uniqueID" : 4000000,
- "ttVendor" : "SOME",
- "ttUniqueID" : "OpenType name Table Unique ID",
- "ttVersion" : "OpenType name Table Version",
+ "familyName": "Some Font (Family Name)",
+ "styleName": "Regular (Style Name)",
+ "fullName": "Some Font-Regular (Postscript Full Name)",
+ "fontName": "SomeFont-Regular (Postscript Font Name)",
+ "menuName": "Some Font Regular (Style Map Family Name)",
+ "fontStyle": 64,
+ "note": "A note.",
+ "versionMajor": 1,
+ "versionMinor": 0,
+ "year": 2008,
+ "copyright": "Copyright Some Foundry.",
+ "notice": "Some Font by Some Designer for Some Foundry.",
+ "trademark": "Trademark Some Foundry",
+ "license": "License info for Some Foundry.",
+ "licenseURL": "http://somefoundry.com/license",
+ "createdBy": "Some Foundry",
+ "designer": "Some Designer",
+ "designerURL": "http://somedesigner.com",
+ "vendorURL": "http://somefoundry.com",
+ "unitsPerEm": 1000,
+ "ascender": 750,
+ "descender": -250,
+ "capHeight": 750,
+ "xHeight": 500,
+ "defaultWidth": 400,
+ "slantAngle": -12.5,
+ "italicAngle": -12.5,
+ "widthName": "Medium (normal)",
+ "weightName": "Medium",
+ "weightValue": 500,
+ "fondName": "SomeFont Regular (FOND Name)",
+ "otFamilyName": "Some Font (Preferred Family Name)",
+ "otStyleName": "Regular (Preferred Subfamily Name)",
+ "otMacName": "Some Font Regular (Compatible Full Name)",
+ "msCharSet": 0,
+ "fondID": 15000,
+ "uniqueID": 4000000,
+ "ttVendor": "SOME",
+ "ttUniqueID": "OpenType name Table Unique ID",
+ "ttVersion": "OpenType name Table Version",
}
fontInfoVersion2 = {
- "familyName" : "Some Font (Family Name)",
- "styleName" : "Regular (Style Name)",
- "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
- "styleMapStyleName" : "regular",
- "versionMajor" : 1,
- "versionMinor" : 0,
- "year" : 2008,
- "copyright" : "Copyright Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "unitsPerEm" : 1000,
- "descender" : -250,
- "xHeight" : 500,
- "capHeight" : 750,
- "ascender" : 750,
- "italicAngle" : -12.5,
- "note" : "A note.",
- "openTypeHeadCreated" : "2000/01/01 00:00:00",
- "openTypeHeadLowestRecPPEM" : 10,
- "openTypeHeadFlags" : [0, 1],
- "openTypeHheaAscender" : 750,
- "openTypeHheaDescender" : -250,
- "openTypeHheaLineGap" : 200,
- "openTypeHheaCaretSlopeRise" : 1,
- "openTypeHheaCaretSlopeRun" : 0,
- "openTypeHheaCaretOffset" : 0,
- "openTypeNameDesigner" : "Some Designer",
- "openTypeNameDesignerURL" : "http://somedesigner.com",
- "openTypeNameManufacturer" : "Some Foundry",
- "openTypeNameManufacturerURL" : "http://somefoundry.com",
- "openTypeNameLicense" : "License info for Some Foundry.",
- "openTypeNameLicenseURL" : "http://somefoundry.com/license",
- "openTypeNameVersion" : "OpenType name Table Version",
- "openTypeNameUniqueID" : "OpenType name Table Unique ID",
- "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
- "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
- "openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)",
- "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
- "openTypeNameSampleText" : "Sample Text for Some Font.",
- "openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)",
- "openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)",
- "openTypeOS2WidthClass" : 5,
- "openTypeOS2WeightClass" : 500,
- "openTypeOS2Selection" : [3],
- "openTypeOS2VendorID" : "SOME",
- "openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
- "openTypeOS2FamilyClass" : [1, 1],
- "openTypeOS2UnicodeRanges" : [0, 1],
- "openTypeOS2CodePageRanges" : [0, 1],
- "openTypeOS2TypoAscender" : 750,
- "openTypeOS2TypoDescender" : -250,
- "openTypeOS2TypoLineGap" : 200,
- "openTypeOS2WinAscent" : 750,
- "openTypeOS2WinDescent" : 250,
- "openTypeOS2Type" : [],
- "openTypeOS2SubscriptXSize" : 200,
- "openTypeOS2SubscriptYSize" : 400,
- "openTypeOS2SubscriptXOffset" : 0,
- "openTypeOS2SubscriptYOffset" : -100,
- "openTypeOS2SuperscriptXSize" : 200,
- "openTypeOS2SuperscriptYSize" : 400,
- "openTypeOS2SuperscriptXOffset" : 0,
- "openTypeOS2SuperscriptYOffset" : 200,
- "openTypeOS2StrikeoutSize" : 20,
- "openTypeOS2StrikeoutPosition" : 300,
- "openTypeVheaVertTypoAscender" : 750,
- "openTypeVheaVertTypoDescender" : -250,
- "openTypeVheaVertTypoLineGap" : 200,
- "openTypeVheaCaretSlopeRise" : 0,
- "openTypeVheaCaretSlopeRun" : 1,
- "openTypeVheaCaretOffset" : 0,
- "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
- "postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
- "postscriptSlantAngle" : -12.5,
- "postscriptUniqueID" : 4000000,
- "postscriptUnderlineThickness" : 20,
- "postscriptUnderlinePosition" : -200,
- "postscriptIsFixedPitch" : False,
- "postscriptBlueValues" : [500, 510],
- "postscriptOtherBlues" : [-250, -260],
- "postscriptFamilyBlues" : [500, 510],
- "postscriptFamilyOtherBlues" : [-250, -260],
- "postscriptStemSnapH" : [100, 120],
- "postscriptStemSnapV" : [80, 90],
- "postscriptBlueFuzz" : 1,
- "postscriptBlueShift" : 7,
- "postscriptBlueScale" : 0.039625,
- "postscriptForceBold" : True,
- "postscriptDefaultWidthX" : 400,
- "postscriptNominalWidthX" : 400,
- "postscriptWeightName" : "Medium",
- "postscriptDefaultCharacter" : ".notdef",
- "postscriptWindowsCharacterSet" : 1,
- "macintoshFONDFamilyID" : 15000,
- "macintoshFONDName" : "SomeFont Regular (FOND Name)",
+ "familyName": "Some Font (Family Name)",
+ "styleName": "Regular (Style Name)",
+ "styleMapFamilyName": "Some Font Regular (Style Map Family Name)",
+ "styleMapStyleName": "regular",
+ "versionMajor": 1,
+ "versionMinor": 0,
+ "year": 2008,
+ "copyright": "Copyright Some Foundry.",
+ "trademark": "Trademark Some Foundry",
+ "unitsPerEm": 1000,
+ "descender": -250,
+ "xHeight": 500,
+ "capHeight": 750,
+ "ascender": 750,
+ "italicAngle": -12.5,
+ "note": "A note.",
+ "openTypeHeadCreated": "2000/01/01 00:00:00",
+ "openTypeHeadLowestRecPPEM": 10,
+ "openTypeHeadFlags": [0, 1],
+ "openTypeHheaAscender": 750,
+ "openTypeHheaDescender": -250,
+ "openTypeHheaLineGap": 200,
+ "openTypeHheaCaretSlopeRise": 1,
+ "openTypeHheaCaretSlopeRun": 0,
+ "openTypeHheaCaretOffset": 0,
+ "openTypeNameDesigner": "Some Designer",
+ "openTypeNameDesignerURL": "http://somedesigner.com",
+ "openTypeNameManufacturer": "Some Foundry",
+ "openTypeNameManufacturerURL": "http://somefoundry.com",
+ "openTypeNameLicense": "License info for Some Foundry.",
+ "openTypeNameLicenseURL": "http://somefoundry.com/license",
+ "openTypeNameVersion": "OpenType name Table Version",
+ "openTypeNameUniqueID": "OpenType name Table Unique ID",
+ "openTypeNameDescription": "Some Font by Some Designer for Some Foundry.",
+ "openTypeNamePreferredFamilyName": "Some Font (Preferred Family Name)",
+ "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)",
+ "openTypeNameCompatibleFullName": "Some Font Regular (Compatible Full Name)",
+ "openTypeNameSampleText": "Sample Text for Some Font.",
+ "openTypeNameWWSFamilyName": "Some Font (WWS Family Name)",
+ "openTypeNameWWSSubfamilyName": "Regular (WWS Subfamily Name)",
+ "openTypeOS2WidthClass": 5,
+ "openTypeOS2WeightClass": 500,
+ "openTypeOS2Selection": [3],
+ "openTypeOS2VendorID": "SOME",
+ "openTypeOS2Panose": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ "openTypeOS2FamilyClass": [1, 1],
+ "openTypeOS2UnicodeRanges": [0, 1],
+ "openTypeOS2CodePageRanges": [0, 1],
+ "openTypeOS2TypoAscender": 750,
+ "openTypeOS2TypoDescender": -250,
+ "openTypeOS2TypoLineGap": 200,
+ "openTypeOS2WinAscent": 750,
+ "openTypeOS2WinDescent": 250,
+ "openTypeOS2Type": [],
+ "openTypeOS2SubscriptXSize": 200,
+ "openTypeOS2SubscriptYSize": 400,
+ "openTypeOS2SubscriptXOffset": 0,
+ "openTypeOS2SubscriptYOffset": -100,
+ "openTypeOS2SuperscriptXSize": 200,
+ "openTypeOS2SuperscriptYSize": 400,
+ "openTypeOS2SuperscriptXOffset": 0,
+ "openTypeOS2SuperscriptYOffset": 200,
+ "openTypeOS2StrikeoutSize": 20,
+ "openTypeOS2StrikeoutPosition": 300,
+ "openTypeVheaVertTypoAscender": 750,
+ "openTypeVheaVertTypoDescender": -250,
+ "openTypeVheaVertTypoLineGap": 200,
+ "openTypeVheaCaretSlopeRise": 0,
+ "openTypeVheaCaretSlopeRun": 1,
+ "openTypeVheaCaretOffset": 0,
+ "postscriptFontName": "SomeFont-Regular (Postscript Font Name)",
+ "postscriptFullName": "Some Font-Regular (Postscript Full Name)",
+ "postscriptSlantAngle": -12.5,
+ "postscriptUniqueID": 4000000,
+ "postscriptUnderlineThickness": 20,
+ "postscriptUnderlinePosition": -200,
+ "postscriptIsFixedPitch": False,
+ "postscriptBlueValues": [500, 510],
+ "postscriptOtherBlues": [-250, -260],
+ "postscriptFamilyBlues": [500, 510],
+ "postscriptFamilyOtherBlues": [-250, -260],
+ "postscriptStemSnapH": [100, 120],
+ "postscriptStemSnapV": [80, 90],
+ "postscriptBlueFuzz": 1,
+ "postscriptBlueShift": 7,
+ "postscriptBlueScale": 0.039625,
+ "postscriptForceBold": True,
+ "postscriptDefaultWidthX": 400,
+ "postscriptNominalWidthX": 400,
+ "postscriptWeightName": "Medium",
+ "postscriptDefaultCharacter": ".notdef",
+ "postscriptWindowsCharacterSet": 1,
+ "macintoshFONDFamilyID": 15000,
+ "macintoshFONDName": "SomeFont Regular (FOND Name)",
}
fontInfoVersion3 = {
- "familyName" : "Some Font (Family Name)",
- "styleName" : "Regular (Style Name)",
- "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
- "styleMapStyleName" : "regular",
- "versionMajor" : 1,
- "versionMinor" : 0,
- "year" : 2008,
- "copyright" : "Copyright Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "unitsPerEm" : 1000,
- "descender" : -250,
- "xHeight" : 500,
- "capHeight" : 750,
- "ascender" : 750,
- "italicAngle" : -12.5,
- "note" : "A note.",
- "openTypeGaspRangeRecords" : [
- dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]),
- dict(rangeMaxPPEM=20, rangeGaspBehavior=[1]),
- dict(rangeMaxPPEM=30, rangeGaspBehavior=[2]),
- dict(rangeMaxPPEM=40, rangeGaspBehavior=[3]),
- dict(rangeMaxPPEM=50, rangeGaspBehavior=[0, 1, 2, 3]),
- dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0])
- ],
- "openTypeHeadCreated" : "2000/01/01 00:00:00",
- "openTypeHeadLowestRecPPEM" : 10,
- "openTypeHeadFlags" : [0, 1],
- "openTypeHheaAscender" : 750,
- "openTypeHheaDescender" : -250,
- "openTypeHheaLineGap" : 200,
- "openTypeHheaCaretSlopeRise" : 1,
- "openTypeHheaCaretSlopeRun" : 0,
- "openTypeHheaCaretOffset" : 0,
- "openTypeNameDesigner" : "Some Designer",
- "openTypeNameDesignerURL" : "http://somedesigner.com",
- "openTypeNameManufacturer" : "Some Foundry",
- "openTypeNameManufacturerURL" : "http://somefoundry.com",
- "openTypeNameLicense" : "License info for Some Foundry.",
- "openTypeNameLicenseURL" : "http://somefoundry.com/license",
- "openTypeNameVersion" : "OpenType name Table Version",
- "openTypeNameUniqueID" : "OpenType name Table Unique ID",
- "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
- "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
- "openTypeNamePreferredSubfamilyName" : "Regular (Preferred Subfamily Name)",
- "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
- "openTypeNameSampleText" : "Sample Text for Some Font.",
- "openTypeNameWWSFamilyName" : "Some Font (WWS Family Name)",
- "openTypeNameWWSSubfamilyName" : "Regular (WWS Subfamily Name)",
- "openTypeNameRecords" : [
- dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record."),
- dict(nameID=2, platformID=1, encodingID=1, languageID=1, string="Name Record.")
- ],
- "openTypeOS2WidthClass" : 5,
- "openTypeOS2WeightClass" : 500,
- "openTypeOS2Selection" : [3],
- "openTypeOS2VendorID" : "SOME",
- "openTypeOS2Panose" : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
- "openTypeOS2FamilyClass" : [1, 1],
- "openTypeOS2UnicodeRanges" : [0, 1],
- "openTypeOS2CodePageRanges" : [0, 1],
- "openTypeOS2TypoAscender" : 750,
- "openTypeOS2TypoDescender" : -250,
- "openTypeOS2TypoLineGap" : 200,
- "openTypeOS2WinAscent" : 750,
- "openTypeOS2WinDescent" : 250,
- "openTypeOS2Type" : [],
- "openTypeOS2SubscriptXSize" : 200,
- "openTypeOS2SubscriptYSize" : 400,
- "openTypeOS2SubscriptXOffset" : 0,
- "openTypeOS2SubscriptYOffset" : -100,
- "openTypeOS2SuperscriptXSize" : 200,
- "openTypeOS2SuperscriptYSize" : 400,
- "openTypeOS2SuperscriptXOffset" : 0,
- "openTypeOS2SuperscriptYOffset" : 200,
- "openTypeOS2StrikeoutSize" : 20,
- "openTypeOS2StrikeoutPosition" : 300,
- "openTypeVheaVertTypoAscender" : 750,
- "openTypeVheaVertTypoDescender" : -250,
- "openTypeVheaVertTypoLineGap" : 200,
- "openTypeVheaCaretSlopeRise" : 0,
- "openTypeVheaCaretSlopeRun" : 1,
- "openTypeVheaCaretOffset" : 0,
- "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
- "postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
- "postscriptSlantAngle" : -12.5,
- "postscriptUniqueID" : 4000000,
- "postscriptUnderlineThickness" : 20,
- "postscriptUnderlinePosition" : -200,
- "postscriptIsFixedPitch" : False,
- "postscriptBlueValues" : [500, 510],
- "postscriptOtherBlues" : [-250, -260],
- "postscriptFamilyBlues" : [500, 510],
- "postscriptFamilyOtherBlues" : [-250, -260],
- "postscriptStemSnapH" : [100, 120],
- "postscriptStemSnapV" : [80, 90],
- "postscriptBlueFuzz" : 1,
- "postscriptBlueShift" : 7,
- "postscriptBlueScale" : 0.039625,
- "postscriptForceBold" : True,
- "postscriptDefaultWidthX" : 400,
- "postscriptNominalWidthX" : 400,
- "postscriptWeightName" : "Medium",
- "postscriptDefaultCharacter" : ".notdef",
- "postscriptWindowsCharacterSet" : 1,
- "macintoshFONDFamilyID" : 15000,
- "macintoshFONDName" : "SomeFont Regular (FOND Name)",
- "woffMajorVersion" : 1,
- "woffMinorVersion" : 0,
- "woffMetadataUniqueID" : dict(id="string"),
- "woffMetadataVendor" : dict(name="Some Foundry", url="http://somefoundry.com"),
- "woffMetadataCredits" : dict(
- credits=[
- dict(name="Some Designer"),
- dict(name=""),
- dict(name="Some Designer", url="http://somedesigner.com"),
- dict(name="Some Designer", url=""),
- dict(name="Some Designer", role="Designer"),
- dict(name="Some Designer", role=""),
- dict(name="Some Designer", dir="ltr"),
- dict(name="rengiseD emoS", dir="rtl"),
- {"name" : "Some Designer", "class" : "hello"},
- {"name" : "Some Designer", "class" : ""},
- ]
- ),
- "woffMetadataDescription" : dict(
- url="http://somefoundry.com/foo/description",
- text=[
- dict(text="foo"),
- dict(text=""),
- dict(text="foo", language="bar"),
- dict(text="foo", language=""),
- dict(text="foo", dir="ltr"),
- dict(text="foo", dir="rtl"),
- {"text" : "foo", "class" : "foo"},
- {"text" : "foo", "class" : ""},
- ]
- ),
- "woffMetadataLicense" : dict(
- url="http://somefoundry.com/foo/license",
- id="foo",
- text=[
- dict(text="foo"),
- dict(text=""),
- dict(text="foo", language="bar"),
- dict(text="foo", language=""),
- dict(text="foo", dir="ltr"),
- dict(text="foo", dir="rtl"),
- {"text" : "foo", "class" : "foo"},
- {"text" : "foo", "class" : ""},
- ]
- ),
- "woffMetadataCopyright" : dict(
- text=[
- dict(text="foo"),
- dict(text=""),
- dict(text="foo", language="bar"),
- dict(text="foo", language=""),
- dict(text="foo", dir="ltr"),
- dict(text="foo", dir="rtl"),
- {"text" : "foo", "class" : "foo"},
- {"text" : "foo", "class" : ""},
- ]
- ),
- "woffMetadataTrademark" : dict(
- text=[
- dict(text="foo"),
- dict(text=""),
- dict(text="foo", language="bar"),
- dict(text="foo", language=""),
- dict(text="foo", dir="ltr"),
- dict(text="foo", dir="rtl"),
- {"text" : "foo", "class" : "foo"},
- {"text" : "foo", "class" : ""},
- ]
- ),
- "woffMetadataLicensee" : dict(
- name="Some Licensee"
- ),
- "woffMetadataExtensions" : [
- dict(
- # everything
- names=[
- dict(text="foo"),
- dict(text=""),
- dict(text="foo", language="bar"),
- dict(text="foo", language=""),
- dict(text="foo", dir="ltr"),
- dict(text="foo", dir="rtl"),
- {"text" : "foo", "class" : "hello"},
- {"text" : "foo", "class" : ""},
- ],
- items=[
- # everything
- dict(
- id="foo",
- names=[
- dict(text="foo"),
- dict(text=""),
- dict(text="foo", language="bar"),
- dict(text="foo", language=""),
- dict(text="foo", dir="ltr"),
- dict(text="foo", dir="rtl"),
- {"text" : "foo", "class" : "hello"},
- {"text" : "foo", "class" : ""},
- ],
- values=[
- dict(text="foo"),
- dict(text=""),
- dict(text="foo", language="bar"),
- dict(text="foo", language=""),
- dict(text="foo", dir="ltr"),
- dict(text="foo", dir="rtl"),
- {"text" : "foo", "class" : "hello"},
- {"text" : "foo", "class" : ""},
- ]
- ),
- # no id
- dict(
- names=[
- dict(text="foo")
- ],
- values=[
- dict(text="foo")
- ]
- )
- ]
- ),
- # no names
- dict(
- items=[
- dict(
- id="foo",
- names=[
- dict(text="foo")
- ],
- values=[
- dict(text="foo")
- ]
- )
- ]
- ),
- ],
- "guidelines" : [
- # ints
- dict(x=100, y=200, angle=45),
- # floats
- dict(x=100.5, y=200.5, angle=45.5),
- # edges
- dict(x=0, y=0, angle=0),
- dict(x=0, y=0, angle=360),
- dict(x=0, y=0, angle=360.0),
- # no y
- dict(x=100),
- # no x
- dict(y=200),
- # name
- dict(x=100, y=200, angle=45, name="foo"),
- dict(x=100, y=200, angle=45, name=""),
- # identifier
- dict(x=100, y=200, angle=45, identifier="guide1"),
- dict(x=100, y=200, angle=45, identifier="guide2"),
- dict(x=100, y=200, angle=45, identifier="\x20"),
- dict(x=100, y=200, angle=45, identifier="\x7E"),
- # colors
- dict(x=100, y=200, angle=45, color="0,0,0,0"),
- dict(x=100, y=200, angle=45, color="1,0,0,0"),
- dict(x=100, y=200, angle=45, color="1,1,1,1"),
- dict(x=100, y=200, angle=45, color="0,1,0,0"),
- dict(x=100, y=200, angle=45, color="0,0,1,0"),
- dict(x=100, y=200, angle=45, color="0,0,0,1"),
- dict(x=100, y=200, angle=45, color="1, 0, 0, 0"),
- dict(x=100, y=200, angle=45, color="0, 1, 0, 0"),
- dict(x=100, y=200, angle=45, color="0, 0, 1, 0"),
- dict(x=100, y=200, angle=45, color="0, 0, 0, 1"),
- dict(x=100, y=200, angle=45, color=".5,0,0,0"),
- dict(x=100, y=200, angle=45, color="0,.5,0,0"),
- dict(x=100, y=200, angle=45, color="0,0,.5,0"),
- dict(x=100, y=200, angle=45, color="0,0,0,.5"),
- dict(x=100, y=200, angle=45, color=".5,1,1,1"),
- dict(x=100, y=200, angle=45, color="1,.5,1,1"),
- dict(x=100, y=200, angle=45, color="1,1,.5,1"),
- dict(x=100, y=200, angle=45, color="1,1,1,.5"),
- ],
+ "familyName": "Some Font (Family Name)",
+ "styleName": "Regular (Style Name)",
+ "styleMapFamilyName": "Some Font Regular (Style Map Family Name)",
+ "styleMapStyleName": "regular",
+ "versionMajor": 1,
+ "versionMinor": 0,
+ "year": 2008,
+ "copyright": "Copyright Some Foundry.",
+ "trademark": "Trademark Some Foundry",
+ "unitsPerEm": 1000,
+ "descender": -250,
+ "xHeight": 500,
+ "capHeight": 750,
+ "ascender": 750,
+ "italicAngle": -12.5,
+ "note": "A note.",
+ "openTypeGaspRangeRecords": [
+ dict(rangeMaxPPEM=10, rangeGaspBehavior=[0]),
+ dict(rangeMaxPPEM=20, rangeGaspBehavior=[1]),
+ dict(rangeMaxPPEM=30, rangeGaspBehavior=[2]),
+ dict(rangeMaxPPEM=40, rangeGaspBehavior=[3]),
+ dict(rangeMaxPPEM=50, rangeGaspBehavior=[0, 1, 2, 3]),
+ dict(rangeMaxPPEM=0xFFFF, rangeGaspBehavior=[0]),
+ ],
+ "openTypeHeadCreated": "2000/01/01 00:00:00",
+ "openTypeHeadLowestRecPPEM": 10,
+ "openTypeHeadFlags": [0, 1],
+ "openTypeHheaAscender": 750,
+ "openTypeHheaDescender": -250,
+ "openTypeHheaLineGap": 200,
+ "openTypeHheaCaretSlopeRise": 1,
+ "openTypeHheaCaretSlopeRun": 0,
+ "openTypeHheaCaretOffset": 0,
+ "openTypeNameDesigner": "Some Designer",
+ "openTypeNameDesignerURL": "http://somedesigner.com",
+ "openTypeNameManufacturer": "Some Foundry",
+ "openTypeNameManufacturerURL": "http://somefoundry.com",
+ "openTypeNameLicense": "License info for Some Foundry.",
+ "openTypeNameLicenseURL": "http://somefoundry.com/license",
+ "openTypeNameVersion": "OpenType name Table Version",
+ "openTypeNameUniqueID": "OpenType name Table Unique ID",
+ "openTypeNameDescription": "Some Font by Some Designer for Some Foundry.",
+ "openTypeNamePreferredFamilyName": "Some Font (Preferred Family Name)",
+ "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)",
+ "openTypeNameCompatibleFullName": "Some Font Regular (Compatible Full Name)",
+ "openTypeNameSampleText": "Sample Text for Some Font.",
+ "openTypeNameWWSFamilyName": "Some Font (WWS Family Name)",
+ "openTypeNameWWSSubfamilyName": "Regular (WWS Subfamily Name)",
+ "openTypeNameRecords": [
+ dict(nameID=1, platformID=1, encodingID=1, languageID=1, string="Name Record."),
+ dict(nameID=2, platformID=1, encodingID=1, languageID=1, string="Name Record."),
+ ],
+ "openTypeOS2WidthClass": 5,
+ "openTypeOS2WeightClass": 500,
+ "openTypeOS2Selection": [3],
+ "openTypeOS2VendorID": "SOME",
+ "openTypeOS2Panose": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ "openTypeOS2FamilyClass": [1, 1],
+ "openTypeOS2UnicodeRanges": [0, 1],
+ "openTypeOS2CodePageRanges": [0, 1],
+ "openTypeOS2TypoAscender": 750,
+ "openTypeOS2TypoDescender": -250,
+ "openTypeOS2TypoLineGap": 200,
+ "openTypeOS2WinAscent": 750,
+ "openTypeOS2WinDescent": 250,
+ "openTypeOS2Type": [],
+ "openTypeOS2SubscriptXSize": 200,
+ "openTypeOS2SubscriptYSize": 400,
+ "openTypeOS2SubscriptXOffset": 0,
+ "openTypeOS2SubscriptYOffset": -100,
+ "openTypeOS2SuperscriptXSize": 200,
+ "openTypeOS2SuperscriptYSize": 400,
+ "openTypeOS2SuperscriptXOffset": 0,
+ "openTypeOS2SuperscriptYOffset": 200,
+ "openTypeOS2StrikeoutSize": 20,
+ "openTypeOS2StrikeoutPosition": 300,
+ "openTypeVheaVertTypoAscender": 750,
+ "openTypeVheaVertTypoDescender": -250,
+ "openTypeVheaVertTypoLineGap": 200,
+ "openTypeVheaCaretSlopeRise": 0,
+ "openTypeVheaCaretSlopeRun": 1,
+ "openTypeVheaCaretOffset": 0,
+ "postscriptFontName": "SomeFont-Regular (Postscript Font Name)",
+ "postscriptFullName": "Some Font-Regular (Postscript Full Name)",
+ "postscriptSlantAngle": -12.5,
+ "postscriptUniqueID": 4000000,
+ "postscriptUnderlineThickness": 20,
+ "postscriptUnderlinePosition": -200,
+ "postscriptIsFixedPitch": False,
+ "postscriptBlueValues": [500, 510],
+ "postscriptOtherBlues": [-250, -260],
+ "postscriptFamilyBlues": [500, 510],
+ "postscriptFamilyOtherBlues": [-250, -260],
+ "postscriptStemSnapH": [100, 120],
+ "postscriptStemSnapV": [80, 90],
+ "postscriptBlueFuzz": 1,
+ "postscriptBlueShift": 7,
+ "postscriptBlueScale": 0.039625,
+ "postscriptForceBold": True,
+ "postscriptDefaultWidthX": 400,
+ "postscriptNominalWidthX": 400,
+ "postscriptWeightName": "Medium",
+ "postscriptDefaultCharacter": ".notdef",
+ "postscriptWindowsCharacterSet": 1,
+ "macintoshFONDFamilyID": 15000,
+ "macintoshFONDName": "SomeFont Regular (FOND Name)",
+ "woffMajorVersion": 1,
+ "woffMinorVersion": 0,
+ "woffMetadataUniqueID": dict(id="string"),
+ "woffMetadataVendor": dict(name="Some Foundry", url="http://somefoundry.com"),
+ "woffMetadataCredits": dict(
+ credits=[
+ dict(name="Some Designer"),
+ dict(name=""),
+ dict(name="Some Designer", url="http://somedesigner.com"),
+ dict(name="Some Designer", url=""),
+ dict(name="Some Designer", role="Designer"),
+ dict(name="Some Designer", role=""),
+ dict(name="Some Designer", dir="ltr"),
+ dict(name="rengiseD emoS", dir="rtl"),
+ {"name": "Some Designer", "class": "hello"},
+ {"name": "Some Designer", "class": ""},
+ ]
+ ),
+ "woffMetadataDescription": dict(
+ url="http://somefoundry.com/foo/description",
+ text=[
+ dict(text="foo"),
+ dict(text=""),
+ dict(text="foo", language="bar"),
+ dict(text="foo", language=""),
+ dict(text="foo", dir="ltr"),
+ dict(text="foo", dir="rtl"),
+ {"text": "foo", "class": "foo"},
+ {"text": "foo", "class": ""},
+ ],
+ ),
+ "woffMetadataLicense": dict(
+ url="http://somefoundry.com/foo/license",
+ id="foo",
+ text=[
+ dict(text="foo"),
+ dict(text=""),
+ dict(text="foo", language="bar"),
+ dict(text="foo", language=""),
+ dict(text="foo", dir="ltr"),
+ dict(text="foo", dir="rtl"),
+ {"text": "foo", "class": "foo"},
+ {"text": "foo", "class": ""},
+ ],
+ ),
+ "woffMetadataCopyright": dict(
+ text=[
+ dict(text="foo"),
+ dict(text=""),
+ dict(text="foo", language="bar"),
+ dict(text="foo", language=""),
+ dict(text="foo", dir="ltr"),
+ dict(text="foo", dir="rtl"),
+ {"text": "foo", "class": "foo"},
+ {"text": "foo", "class": ""},
+ ]
+ ),
+ "woffMetadataTrademark": dict(
+ text=[
+ dict(text="foo"),
+ dict(text=""),
+ dict(text="foo", language="bar"),
+ dict(text="foo", language=""),
+ dict(text="foo", dir="ltr"),
+ dict(text="foo", dir="rtl"),
+ {"text": "foo", "class": "foo"},
+ {"text": "foo", "class": ""},
+ ]
+ ),
+ "woffMetadataLicensee": dict(name="Some Licensee"),
+ "woffMetadataExtensions": [
+ dict(
+ # everything
+ names=[
+ dict(text="foo"),
+ dict(text=""),
+ dict(text="foo", language="bar"),
+ dict(text="foo", language=""),
+ dict(text="foo", dir="ltr"),
+ dict(text="foo", dir="rtl"),
+ {"text": "foo", "class": "hello"},
+ {"text": "foo", "class": ""},
+ ],
+ items=[
+ # everything
+ dict(
+ id="foo",
+ names=[
+ dict(text="foo"),
+ dict(text=""),
+ dict(text="foo", language="bar"),
+ dict(text="foo", language=""),
+ dict(text="foo", dir="ltr"),
+ dict(text="foo", dir="rtl"),
+ {"text": "foo", "class": "hello"},
+ {"text": "foo", "class": ""},
+ ],
+ values=[
+ dict(text="foo"),
+ dict(text=""),
+ dict(text="foo", language="bar"),
+ dict(text="foo", language=""),
+ dict(text="foo", dir="ltr"),
+ dict(text="foo", dir="rtl"),
+ {"text": "foo", "class": "hello"},
+ {"text": "foo", "class": ""},
+ ],
+ ),
+ # no id
+ dict(names=[dict(text="foo")], values=[dict(text="foo")]),
+ ],
+ ),
+ # no names
+ dict(
+ items=[dict(id="foo", names=[dict(text="foo")], values=[dict(text="foo")])]
+ ),
+ ],
+ "guidelines": [
+ # ints
+ dict(x=100, y=200, angle=45),
+ # floats
+ dict(x=100.5, y=200.5, angle=45.5),
+ # edges
+ dict(x=0, y=0, angle=0),
+ dict(x=0, y=0, angle=360),
+ dict(x=0, y=0, angle=360.0),
+ # no y
+ dict(x=100),
+ # no x
+ dict(y=200),
+ # name
+ dict(x=100, y=200, angle=45, name="foo"),
+ dict(x=100, y=200, angle=45, name=""),
+ # identifier
+ dict(x=100, y=200, angle=45, identifier="guide1"),
+ dict(x=100, y=200, angle=45, identifier="guide2"),
+ dict(x=100, y=200, angle=45, identifier="\x20"),
+ dict(x=100, y=200, angle=45, identifier="\x7E"),
+ # colors
+ dict(x=100, y=200, angle=45, color="0,0,0,0"),
+ dict(x=100, y=200, angle=45, color="1,0,0,0"),
+ dict(x=100, y=200, angle=45, color="1,1,1,1"),
+ dict(x=100, y=200, angle=45, color="0,1,0,0"),
+ dict(x=100, y=200, angle=45, color="0,0,1,0"),
+ dict(x=100, y=200, angle=45, color="0,0,0,1"),
+ dict(x=100, y=200, angle=45, color="1, 0, 0, 0"),
+ dict(x=100, y=200, angle=45, color="0, 1, 0, 0"),
+ dict(x=100, y=200, angle=45, color="0, 0, 1, 0"),
+ dict(x=100, y=200, angle=45, color="0, 0, 0, 1"),
+ dict(x=100, y=200, angle=45, color=".5,0,0,0"),
+ dict(x=100, y=200, angle=45, color="0,.5,0,0"),
+ dict(x=100, y=200, angle=45, color="0,0,.5,0"),
+ dict(x=100, y=200, angle=45, color="0,0,0,.5"),
+ dict(x=100, y=200, angle=45, color=".5,1,1,1"),
+ dict(x=100, y=200, angle=45, color="1,.5,1,1"),
+ dict(x=100, y=200, angle=45, color="1,1,.5,1"),
+ dict(x=100, y=200, angle=45, color="1,1,1,.5"),
+ ],
}
expectedFontInfo1To2Conversion = {
- "familyName" : "Some Font (Family Name)",
- "styleMapFamilyName" : "Some Font Regular (Style Map Family Name)",
- "styleMapStyleName" : "regular",
- "styleName" : "Regular (Style Name)",
- "unitsPerEm" : 1000,
- "ascender" : 750,
- "capHeight" : 750,
- "xHeight" : 500,
- "descender" : -250,
- "italicAngle" : -12.5,
- "versionMajor" : 1,
- "versionMinor" : 0,
- "year" : 2008,
- "copyright" : "Copyright Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "note" : "A note.",
- "macintoshFONDFamilyID" : 15000,
- "macintoshFONDName" : "SomeFont Regular (FOND Name)",
- "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
- "openTypeNameDescription" : "Some Font by Some Designer for Some Foundry.",
- "openTypeNameDesigner" : "Some Designer",
- "openTypeNameDesignerURL" : "http://somedesigner.com",
- "openTypeNameLicense" : "License info for Some Foundry.",
- "openTypeNameLicenseURL" : "http://somefoundry.com/license",
- "openTypeNameManufacturer" : "Some Foundry",
- "openTypeNameManufacturerURL" : "http://somefoundry.com",
- "openTypeNamePreferredFamilyName" : "Some Font (Preferred Family Name)",
- "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)",
- "openTypeNameCompatibleFullName" : "Some Font Regular (Compatible Full Name)",
- "openTypeNameUniqueID" : "OpenType name Table Unique ID",
- "openTypeNameVersion" : "OpenType name Table Version",
- "openTypeOS2VendorID" : "SOME",
- "openTypeOS2WeightClass" : 500,
- "openTypeOS2WidthClass" : 5,
- "postscriptDefaultWidthX" : 400,
- "postscriptFontName" : "SomeFont-Regular (Postscript Font Name)",
- "postscriptFullName" : "Some Font-Regular (Postscript Full Name)",
- "postscriptSlantAngle" : -12.5,
- "postscriptUniqueID" : 4000000,
- "postscriptWeightName" : "Medium",
- "postscriptWindowsCharacterSet" : 1
+ "familyName": "Some Font (Family Name)",
+ "styleMapFamilyName": "Some Font Regular (Style Map Family Name)",
+ "styleMapStyleName": "regular",
+ "styleName": "Regular (Style Name)",
+ "unitsPerEm": 1000,
+ "ascender": 750,
+ "capHeight": 750,
+ "xHeight": 500,
+ "descender": -250,
+ "italicAngle": -12.5,
+ "versionMajor": 1,
+ "versionMinor": 0,
+ "year": 2008,
+ "copyright": "Copyright Some Foundry.",
+ "trademark": "Trademark Some Foundry",
+ "note": "A note.",
+ "macintoshFONDFamilyID": 15000,
+ "macintoshFONDName": "SomeFont Regular (FOND Name)",
+ "openTypeNameCompatibleFullName": "Some Font Regular (Compatible Full Name)",
+ "openTypeNameDescription": "Some Font by Some Designer for Some Foundry.",
+ "openTypeNameDesigner": "Some Designer",
+ "openTypeNameDesignerURL": "http://somedesigner.com",
+ "openTypeNameLicense": "License info for Some Foundry.",
+ "openTypeNameLicenseURL": "http://somefoundry.com/license",
+ "openTypeNameManufacturer": "Some Foundry",
+ "openTypeNameManufacturerURL": "http://somefoundry.com",
+ "openTypeNamePreferredFamilyName": "Some Font (Preferred Family Name)",
+ "openTypeNamePreferredSubfamilyName": "Regular (Preferred Subfamily Name)",
+ "openTypeNameCompatibleFullName": "Some Font Regular (Compatible Full Name)",
+ "openTypeNameUniqueID": "OpenType name Table Unique ID",
+ "openTypeNameVersion": "OpenType name Table Version",
+ "openTypeOS2VendorID": "SOME",
+ "openTypeOS2WeightClass": 500,
+ "openTypeOS2WidthClass": 5,
+ "postscriptDefaultWidthX": 400,
+ "postscriptFontName": "SomeFont-Regular (Postscript Font Name)",
+ "postscriptFullName": "Some Font-Regular (Postscript Full Name)",
+ "postscriptSlantAngle": -12.5,
+ "postscriptUniqueID": 4000000,
+ "postscriptWeightName": "Medium",
+ "postscriptWindowsCharacterSet": 1,
}
expectedFontInfo2To1Conversion = {
- "familyName" : "Some Font (Family Name)",
- "menuName" : "Some Font Regular (Style Map Family Name)",
- "fontStyle" : 64,
- "styleName" : "Regular (Style Name)",
- "unitsPerEm" : 1000,
- "ascender" : 750,
- "capHeight" : 750,
- "xHeight" : 500,
- "descender" : -250,
- "italicAngle" : -12.5,
- "versionMajor" : 1,
- "versionMinor" : 0,
- "copyright" : "Copyright Some Foundry.",
- "trademark" : "Trademark Some Foundry",
- "note" : "A note.",
- "fondID" : 15000,
- "fondName" : "SomeFont Regular (FOND Name)",
- "fullName" : "Some Font Regular (Compatible Full Name)",
- "notice" : "Some Font by Some Designer for Some Foundry.",
- "designer" : "Some Designer",
- "designerURL" : "http://somedesigner.com",
- "license" : "License info for Some Foundry.",
- "licenseURL" : "http://somefoundry.com/license",
- "createdBy" : "Some Foundry",
- "vendorURL" : "http://somefoundry.com",
- "otFamilyName" : "Some Font (Preferred Family Name)",
- "otStyleName" : "Regular (Preferred Subfamily Name)",
- "otMacName" : "Some Font Regular (Compatible Full Name)",
- "ttUniqueID" : "OpenType name Table Unique ID",
- "ttVersion" : "OpenType name Table Version",
- "ttVendor" : "SOME",
- "weightValue" : 500,
- "widthName" : "Medium (normal)",
- "defaultWidth" : 400,
- "fontName" : "SomeFont-Regular (Postscript Font Name)",
- "fullName" : "Some Font-Regular (Postscript Full Name)",
- "slantAngle" : -12.5,
- "uniqueID" : 4000000,
- "weightName" : "Medium",
- "msCharSet" : 0,
- "year" : 2008
+ "familyName": "Some Font (Family Name)",
+ "menuName": "Some Font Regular (Style Map Family Name)",
+ "fontStyle": 64,
+ "styleName": "Regular (Style Name)",
+ "unitsPerEm": 1000,
+ "ascender": 750,
+ "capHeight": 750,
+ "xHeight": 500,
+ "descender": -250,
+ "italicAngle": -12.5,
+ "versionMajor": 1,
+ "versionMinor": 0,
+ "copyright": "Copyright Some Foundry.",
+ "trademark": "Trademark Some Foundry",
+ "note": "A note.",
+ "fondID": 15000,
+ "fondName": "SomeFont Regular (FOND Name)",
+ "fullName": "Some Font Regular (Compatible Full Name)",
+ "notice": "Some Font by Some Designer for Some Foundry.",
+ "designer": "Some Designer",
+ "designerURL": "http://somedesigner.com",
+ "license": "License info for Some Foundry.",
+ "licenseURL": "http://somefoundry.com/license",
+ "createdBy": "Some Foundry",
+ "vendorURL": "http://somefoundry.com",
+ "otFamilyName": "Some Font (Preferred Family Name)",
+ "otStyleName": "Regular (Preferred Subfamily Name)",
+ "otMacName": "Some Font Regular (Compatible Full Name)",
+ "ttUniqueID": "OpenType name Table Unique ID",
+ "ttVersion": "OpenType name Table Version",
+ "ttVendor": "SOME",
+ "weightValue": 500,
+ "widthName": "Medium (normal)",
+ "defaultWidth": 400,
+ "fontName": "SomeFont-Regular (Postscript Font Name)",
+ "fullName": "Some Font-Regular (Postscript Full Name)",
+ "slantAngle": -12.5,
+ "uniqueID": 4000000,
+ "weightName": "Medium",
+ "msCharSet": 0,
+ "year": 2008,
}
diff --git a/Tests/unicodedata_test.py b/Tests/unicodedata_test.py
index 5cdb3404..77301f4d 100644
--- a/Tests/unicodedata_test.py
+++ b/Tests/unicodedata_test.py
@@ -10,147 +10,148 @@ def test_script():
assert unicodedata.script(chr(0x10FFFF)) == "Zzzz"
# these were randomly sampled, one character per script
- 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'
+ 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"
+ assert unicodedata.script(chr(0x11F00)) == "Kawi"
def test_script_extension():
@@ -159,11 +160,29 @@ def test_script_extension():
assert unicodedata.script_extension(chr(0x0378)) == {"Zzzz"}
assert unicodedata.script_extension(chr(0x10FFFF)) == {"Zzzz"}
- assert unicodedata.script_extension("\u0660") == {'Arab', 'Thaa', 'Yezi'}
+ 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',
- 'Telu', 'Tirh'}
+ "Beng",
+ "Deva",
+ "Dogr",
+ "Gong",
+ "Gonm",
+ "Gran",
+ "Gujr",
+ "Guru",
+ "Knda",
+ "Mahj",
+ "Mlym",
+ "Nand",
+ "Orya",
+ "Sind",
+ "Sinh",
+ "Sylo",
+ "Takr",
+ "Taml",
+ "Telu",
+ "Tirh",
+ }
def test_script_name():
@@ -199,6 +218,7 @@ def test_block():
assert unicodedata.block("\x80") == "Latin-1 Supplement"
assert unicodedata.block("\u1c90") == "Georgian Extended"
assert unicodedata.block("\u0870") == "Arabic Extended-B"
+ assert unicodedata.block("\U00011B00") == "Devanagari Extended-A"
def test_ot_tags_from_script():
@@ -208,6 +228,7 @@ def test_ot_tags_from_script():
assert unicodedata.ot_tags_from_script("Deva") == ["dev2", "deva"]
# exceptions
assert unicodedata.ot_tags_from_script("Hira") == ["kana"]
+ assert unicodedata.ot_tags_from_script("Zmth") == ["math"]
# special script codes map to DFLT
assert unicodedata.ot_tags_from_script("Zinh") == ["DFLT"]
assert unicodedata.ot_tags_from_script("Zyyy") == ["DFLT"]
@@ -230,6 +251,7 @@ def test_ot_tag_to_script():
assert unicodedata.ot_tag_to_script("vai ") == "Vaii"
assert unicodedata.ot_tag_to_script("lao ") == "Laoo"
assert unicodedata.ot_tag_to_script("yi") == "Yiii"
+ assert unicodedata.ot_tag_to_script("math") == "Zmth"
# both 'hang' and 'jamo' tags map to the Hangul script
assert unicodedata.ot_tag_to_script("hang") == "Hang"
assert unicodedata.ot_tag_to_script("jamo") == "Hang"
@@ -247,10 +269,10 @@ def test_script_horizontal_direction():
with pytest.raises(KeyError):
unicodedata.script_horizontal_direction("Azzz")
- assert unicodedata.script_horizontal_direction("Azzz",
- default="LTR") == "LTR"
+ assert unicodedata.script_horizontal_direction("Azzz", default="LTR") == "LTR"
if __name__ == "__main__":
import sys
+
sys.exit(pytest.main(sys.argv))
diff --git a/Tests/varLib/builder_test.py b/Tests/varLib/builder_test.py
index 6cad103a..33d1dfb0 100644
--- a/Tests/varLib/builder_test.py
+++ b/Tests/varLib/builder_test.py
@@ -2,27 +2,31 @@ from fontTools.varLib.builder import buildVarData
import pytest
-@pytest.mark.parametrize("region_indices, items, expected_num_shorts", [
- ([], [], 0),
- ([0], [[1]], 0),
- ([0], [[128]], 1),
- ([0, 1, 2], [[128, 1, 2], [3, -129, 5], [6, 7, 8]], 2),
- ([0, 1, 2], [[0, 128, 2], [3, 4, 5], [6, 7, -129]], 3),
- ([0], [[32768]], 0x8001),
- ([0, 1, 2], [[32768, 1, 2], [3, -129, 5], [6, 7, 8]], 0x8001),
- ([0, 1, 2], [[32768, 1, 2], [3, -32769, 5], [6, 7, 8]], 0x8002),
- ([0, 1, 2], [[0, 32768, 2], [3, 4, 5], [6, 7, -32769]], 0x8003),
-], ids=[
- "0_regions_0_deltas",
- "1_region_1_uint8",
- "1_region_1_short",
- "3_regions_2_shorts_ordered",
- "3_regions_2_shorts_unordered",
- "1_region_1_long",
- "3_regions_1_long_ordered",
- "3_regions_2_longs_ordered",
- "3_regions_2_longs_unordered",
-])
+@pytest.mark.parametrize(
+ "region_indices, items, expected_num_shorts",
+ [
+ ([], [], 0),
+ ([0], [[1]], 0),
+ ([0], [[128]], 1),
+ ([0, 1, 2], [[128, 1, 2], [3, -129, 5], [6, 7, 8]], 2),
+ ([0, 1, 2], [[0, 128, 2], [3, 4, 5], [6, 7, -129]], 3),
+ ([0], [[32768]], 0x8001),
+ ([0, 1, 2], [[32768, 1, 2], [3, -129, 5], [6, 7, 8]], 0x8001),
+ ([0, 1, 2], [[32768, 1, 2], [3, -32769, 5], [6, 7, 8]], 0x8002),
+ ([0, 1, 2], [[0, 32768, 2], [3, 4, 5], [6, 7, -32769]], 0x8003),
+ ],
+ ids=[
+ "0_regions_0_deltas",
+ "1_region_1_uint8",
+ "1_region_1_short",
+ "3_regions_2_shorts_ordered",
+ "3_regions_2_shorts_unordered",
+ "1_region_1_long",
+ "3_regions_1_long_ordered",
+ "3_regions_2_longs_ordered",
+ "3_regions_2_longs_unordered",
+ ],
+)
def test_buildVarData_no_optimize(region_indices, items, expected_num_shorts):
data = buildVarData(region_indices, items, optimize=False)
@@ -33,48 +37,110 @@ def test_buildVarData_no_optimize(region_indices, items, expected_num_shorts):
assert data.Item == items
-@pytest.mark.parametrize([
- "region_indices", "items", "expected_num_shorts",
- "expected_regions", "expected_items"
-], [
- ([0, 1, 2], [[0, 1, 2], [3, 4, 5], [6, 7, 8]], 0,
- [0, 1, 2], [[0, 1, 2], [3, 4, 5], [6, 7, 8]]),
- ([0, 1, 2], [[0, 128, 2], [3, 4, 5], [6, 7, 8]], 1,
- [1, 0, 2], [[128, 0, 2], [4, 3, 5], [7, 6, 8]]),
- ([0, 1, 2], [[0, 1, 128], [3, 4, 5], [6, -129, 8]], 2,
- [1, 2, 0], [[1, 128, 0], [4, 5, 3], [-129, 8, 6]]),
- ([0, 1, 2], [[128, 1, -129], [3, 4, 5], [6, 7, 8]], 2,
- [0, 2, 1], [[128, -129, 1], [3, 5, 4], [6, 8, 7]]),
- ([0, 1, 2], [[0, 1, 128], [3, -129, 5], [256, 7, 8]], 3,
- [0, 1, 2], [[0, 1, 128], [3, -129, 5], [256, 7, 8]]),
- ([0, 1, 2], [[0, 128, 2], [0, 4, 5], [0, 7, 8]], 1,
- [1, 2], [[128, 2], [4, 5], [7, 8]]),
- ([0, 1, 2], [[0, 32768, 2], [3, 4, 5], [6, 7, 8]], 0x8001,
- [1, 0, 2], [[32768, 0, 2], [4, 3, 5], [7, 6, 8]]),
- ([0, 1, 2], [[0, 1, 32768], [3, 4, 5], [6, -32769, 8]], 0x8002,
- [1, 2, 0], [[1, 32768, 0], [4, 5, 3], [-32769, 8, 6]]),
- ([0, 1, 2], [[32768, 1, -32769], [3, 4, 5], [6, 7, 8]], 0x8002,
- [0, 2, 1], [[32768, -32769, 1], [3, 5, 4], [6, 8, 7]]),
- ([0, 1, 2], [[0, 1, 32768], [3, -32769, 5], [65536, 7, 8]], 0x8003,
- [0, 1, 2], [[0, 1, 32768], [3, -32769, 5], [65536, 7, 8]]),
- ([0, 1, 2], [[0, 32768, 2], [0, 4, 5], [0, 7, 8]], 0x8001,
- [1, 2], [[32768, 2], [4, 5], [7, 8]]),
-], ids=[
- "0/3_shorts_no_reorder",
- "1/3_shorts_reorder",
- "2/3_shorts_reorder",
- "2/3_shorts_same_row_reorder",
- "3/3_shorts_no_reorder",
- "1/3_shorts_1/3_zeroes",
- "1/3_longs_reorder",
- "2/3_longs_reorder",
- "2/3_longs_same_row_reorder",
- "3/3_longs_no_reorder",
- "1/3_longs_1/3_zeroes",
-])
+@pytest.mark.parametrize(
+ [
+ "region_indices",
+ "items",
+ "expected_num_shorts",
+ "expected_regions",
+ "expected_items",
+ ],
+ [
+ (
+ [0, 1, 2],
+ [[0, 1, 2], [3, 4, 5], [6, 7, 8]],
+ 0,
+ [0, 1, 2],
+ [[0, 1, 2], [3, 4, 5], [6, 7, 8]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 128, 2], [3, 4, 5], [6, 7, 8]],
+ 1,
+ [1, 0, 2],
+ [[128, 0, 2], [4, 3, 5], [7, 6, 8]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 1, 128], [3, 4, 5], [6, -129, 8]],
+ 2,
+ [1, 2, 0],
+ [[1, 128, 0], [4, 5, 3], [-129, 8, 6]],
+ ),
+ (
+ [0, 1, 2],
+ [[128, 1, -129], [3, 4, 5], [6, 7, 8]],
+ 2,
+ [0, 2, 1],
+ [[128, -129, 1], [3, 5, 4], [6, 8, 7]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 1, 128], [3, -129, 5], [256, 7, 8]],
+ 3,
+ [0, 1, 2],
+ [[0, 1, 128], [3, -129, 5], [256, 7, 8]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 128, 2], [0, 4, 5], [0, 7, 8]],
+ 1,
+ [1, 2],
+ [[128, 2], [4, 5], [7, 8]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 32768, 2], [3, 4, 5], [6, 7, 8]],
+ 0x8001,
+ [1, 0, 2],
+ [[32768, 0, 2], [4, 3, 5], [7, 6, 8]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 1, 32768], [3, 4, 5], [6, -32769, 8]],
+ 0x8002,
+ [1, 2, 0],
+ [[1, 32768, 0], [4, 5, 3], [-32769, 8, 6]],
+ ),
+ (
+ [0, 1, 2],
+ [[32768, 1, -32769], [3, 4, 5], [6, 7, 8]],
+ 0x8002,
+ [0, 2, 1],
+ [[32768, -32769, 1], [3, 5, 4], [6, 8, 7]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 1, 32768], [3, -32769, 5], [65536, 7, 8]],
+ 0x8003,
+ [0, 1, 2],
+ [[0, 1, 32768], [3, -32769, 5], [65536, 7, 8]],
+ ),
+ (
+ [0, 1, 2],
+ [[0, 32768, 2], [0, 4, 5], [0, 7, 8]],
+ 0x8001,
+ [1, 2],
+ [[32768, 2], [4, 5], [7, 8]],
+ ),
+ ],
+ ids=[
+ "0/3_shorts_no_reorder",
+ "1/3_shorts_reorder",
+ "2/3_shorts_reorder",
+ "2/3_shorts_same_row_reorder",
+ "3/3_shorts_no_reorder",
+ "1/3_shorts_1/3_zeroes",
+ "1/3_longs_reorder",
+ "2/3_longs_reorder",
+ "2/3_longs_same_row_reorder",
+ "3/3_longs_no_reorder",
+ "1/3_longs_1/3_zeroes",
+ ],
+)
def test_buildVarData_optimize(
- region_indices, items, expected_num_shorts, expected_regions,
- expected_items):
+ region_indices, items, expected_num_shorts, expected_regions, expected_items
+):
data = buildVarData(region_indices, items, optimize=True)
assert data.ItemCount == len(items)
@@ -86,4 +152,5 @@ def test_buildVarData_optimize(
if __name__ == "__main__":
import sys
+
sys.exit(pytest.main(sys.argv))
diff --git a/Tests/varLib/data/BuildAvar2.designspace b/Tests/varLib/data/BuildAvar2.designspace
new file mode 100644
index 00000000..1cfa94ab
--- /dev/null
+++ b/Tests/varLib/data/BuildAvar2.designspace
@@ -0,0 +1,55 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="3">
+ <axes>
+ <axis default="400.0" maximum="900.0" minimum="100.0" name="weight" tag="wght">
+ <map input="100.0" output="26" />
+ <map input="200.0" output="39" />
+ <map input="300.0" output="58" />
+ <map input="400.0" output="90" />
+ <map input="500.0" output="108" />
+ <map input="600.0" output="128" />
+ <map input="700.0" output="151" />
+ <map input="800.0" output="169" />
+ <map input="900.0" output="190" />
+ <labelname xml:lang="en">Weight</labelname>
+ </axis>
+ <mappings>
+ <mapping>
+ <input>
+ <dimension name="weight" xvalue="128"/>
+ </input>
+ <output>
+ <dimension name="weight" xvalue="138"/>
+ </output>
+ </mapping>
+ </mappings>
+ </axes>
+ <sources>
+ <source familyname="Test Family 3" filename="master_ufo/TestFamily3-Light.ufo" name="Test Family 3 Light" stylename="Light">
+ <location>
+ <dimension name="weight" xvalue="26.000000" />
+ </location>
+ </source>
+ <source familyname="Test Family 3" filename="master_ufo/TestFamily3-Regular.ufo" name="Test Family 3 Regular" stylename="Regular">
+ <lib copy="1" />
+ <groups copy="1" />
+ <features copy="1" />
+ <info copy="1" />
+ <location>
+ <dimension name="weight" xvalue="90.000000" />
+ </location>
+ </source>
+ <source familyname="Test Family 3" filename="master_ufo/TestFamily3-SemiBold.ufo" name="Test Family 3 SemiBold" stylename="SemiBold">
+ <location>
+ <dimension name="weight" xvalue="151.000000" />
+ </location>
+ </source>
+ <source familyname="Test Family 3" filename="master_ufo/TestFamily3-Bold.ufo" name="Test Family 3 Bold" stylename="Bold">
+ <location>
+ <dimension name="weight" xvalue="190.000000" />
+ </location>
+ </source>
+ </sources>
+ <instances>
+ </instances>
+</designspace>
diff --git a/Tests/varLib/data/DropOnCurves.designspace b/Tests/varLib/data/DropOnCurves.designspace
new file mode 100644
index 00000000..a4769aa2
--- /dev/null
+++ b/Tests/varLib/data/DropOnCurves.designspace
@@ -0,0 +1,20 @@
+<?xml version='1.0' encoding='utf-8'?>
+<designspace format="3">
+ <axes>
+ <axis default="400" maximum="1000" minimum="400" name="weight" tag="wght" />
+ </axes>
+ <sources>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master1.ttx" name="master_1" stylename="Master1">
+ <location>
+ <dimension name="weight" xvalue="400" />
+ </location>
+ </source>
+ <source familyname="Test Family" filename="master_ufo/TestFamily-Master2.ttx" name="master_2" stylename="Master2">
+ <location>
+ <dimension name="weight" xvalue="1000" />
+ </location>
+ </source>
+ </sources>
+ <instances>
+ </instances>
+</designspace>
diff --git a/Tests/varLib/data/InterpolateLayout.glyphs b/Tests/varLib/data/InterpolateLayout.glyphs
new file mode 100644
index 00000000..90493950
--- /dev/null
+++ b/Tests/varLib/data/InterpolateLayout.glyphs
@@ -0,0 +1,2402 @@
+{
+.appVersion = "895";
+customParameters = (
+{
+name = hheaAscender;
+value = 984;
+},
+{
+name = hheaDescender;
+value = -273;
+},
+{
+name = hheaLineGap;
+value = 0;
+},
+{
+name = panose;
+value = (
+2,
+11,
+5,
+3,
+3,
+4,
+3,
+2,
+2,
+4
+);
+},
+{
+name = typoAscender;
+value = 750;
+},
+{
+name = typoDescender;
+value = -250;
+},
+{
+name = typoLineGap;
+value = 0;
+},
+{
+name = unicodeRanges;
+value = (
+0,
+1
+);
+},
+{
+name = blueScale;
+value = 0.0625;
+},
+{
+name = underlinePosition;
+value = -75;
+},
+{
+name = vendorID;
+value = ADBO;
+},
+{
+name = postscriptFontName;
+value = "TestFamily2-Master0";
+},
+{
+name = postscriptBlueFuzz;
+value = 0;
+},
+{
+name = postscriptForceBold;
+value = 0;
+},
+{
+name = styleMapFamilyName;
+value = "Test Family 2";
+},
+{
+name = postscriptFamilyBlues;
+value = (
+-12,
+0,
+486,
+498,
+518,
+530,
+574,
+586,
+638,
+650,
+656,
+668,
+712,
+724
+);
+},
+{
+name = postscriptFamilyOtherBlues;
+value = (
+-217,
+-205
+);
+},
+{
+name = codePageRanges;
+value = (
+1252,
+1250
+);
+},
+{
+name = codePageRangesUnsupportedBits;
+value = (
+29
+);
+},
+{
+name = winAscent;
+value = 984;
+},
+{
+name = winDescent;
+value = 273;
+},
+{
+name = weightClass;
+value = 200;
+},
+{
+name = glyphOrder;
+value = (
+.notdef,
+space,
+A,
+a,
+d,
+f,
+n,
+t,
+f_t,
+a.alt,
+A.sc,
+atilde,
+ampersand,
+circledotted,
+tildecmb,
+dieresiscmb,
+tildebelowcmb,
+dieresisbelowcmb
+);
+},
+{
+name = "Disable Last Change";
+value = 1;
+},
+{
+name = Axes;
+value = (
+{
+Name = weight;
+Tag = wght;
+}
+);
+}
+);
+designer = "Paul D. Hunt";
+disablesAutomaticAlignment = 1;
+familyName = "Test Family 2";
+featurePrefixes = (
+{
+code = "# Do not use Glyphs to edit features.\012#\012# This Glyphs file was made from several UFOs that had different\012# features. As a result, the features are not editable in Glyphs and\012# the original features will be restored when you go back to UFOs.\012";
+name = WARNING;
+}
+);
+fontMaster = (
+{
+alignmentZones = (
+"{722, 12}",
+"{660, 12}",
+"{640, 12}",
+"{570, 12}",
+"{510, 12}",
+"{478, 12}",
+"{0, -12}",
+"{-222, -12}"
+);
+ascender = 722;
+capHeight = 660;
+customParameters = (
+{
+name = "UFO Filename";
+value = "master_ufo/TestFamily2-Master0.ufo";
+},
+{
+name = "Master Name";
+value = "Master 0";
+},
+{
+name = hheaAscender;
+value = 984;
+},
+{
+name = hheaDescender;
+value = -273;
+},
+{
+name = hheaLineGap;
+value = 0;
+},
+{
+name = panose;
+value = (
+2,
+11,
+5,
+3,
+3,
+4,
+3,
+2,
+2,
+4
+);
+},
+{
+name = typoAscender;
+value = 750;
+},
+{
+name = typoDescender;
+value = -250;
+},
+{
+name = typoLineGap;
+value = 0;
+},
+{
+name = unicodeRanges;
+value = (
+0,
+1
+);
+},
+{
+name = blueScale;
+value = 0.0625;
+},
+{
+name = underlinePosition;
+value = -75;
+},
+{
+name = vendorID;
+value = ADBO;
+},
+{
+name = postscriptFontName;
+value = "TestFamily2-Master0";
+},
+{
+name = postscriptBlueFuzz;
+value = 0;
+},
+{
+name = postscriptForceBold;
+value = 0;
+},
+{
+name = styleMapFamilyName;
+value = "Test Family 2";
+},
+{
+name = postscriptFamilyBlues;
+value = (
+-12,
+0,
+486,
+498,
+518,
+530,
+574,
+586,
+638,
+650,
+656,
+668,
+712,
+724
+);
+},
+{
+name = postscriptFamilyOtherBlues;
+value = (
+-217,
+-205
+);
+},
+{
+name = codePageRanges;
+value = (
+1252,
+1250
+);
+},
+{
+name = codePageRangesUnsupportedBits;
+value = (
+29
+);
+},
+{
+name = winAscent;
+value = 984;
+},
+{
+name = winDescent;
+value = 273;
+},
+{
+name = weightClass;
+value = 200;
+}
+);
+descender = -222;
+horizontalStems = (
+28,
+40
+);
+id = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+userData = {
+com.schriftgestaltung.Glyphs.originalFeatureCode = "table head {\012 FontRevision 2.020;\012} head;\012\012\012table name {\012 nameid 9 \"Paul D. Hunt\";\012 nameid 9 1 \"Paul D. Hunt\";\012} name;\012\012\012table hhea {\012 Ascender 984;\012 Descender -273;\012 LineGap 0;\012} hhea;\012\012\012table BASE {\012 HorizAxis.BaseTagList ideo romn;\012 HorizAxis.BaseScriptList\012 latn romn -170 0,\012 grek romn -170 0,\012 cyrl romn -170 0,\012 DFLT romn -170 0;\012} BASE;\012\012\012table OS/2 {\012 Panose 2 11 3 3 3 4 3 2 2 4;\012 XHeight 478;\012 WeightClass 200;\012\012 TypoAscender 750;\012 TypoDescender -250;\012 TypoLineGap 0;\012 winAscent 984;\012 winDescent 273;\012\012 CapHeight 660;\012 WidthClass 5;\012 Vendor \"ADBO\";\012 FSType 0;\012} OS/2;\012\012\012languagesystem DFLT dflt;\012languagesystem latn dflt;\012\012# GSUB =========================================\012# Merging of GSUB is not performed. The variable\012# font will inherit the GSUB table from the\012# base master.\012\012feature c2sc {\012 sub A by A.sc; # GSUB LookupType 1\012} c2sc;\012\012feature ss01 {\012 featureNames {\012 name \"Alternate a\";\012 name 1 0 0 \"Alternate a\";};\012 sub a by a.alt;\012} ss01;\012\012feature ccmp {\012 sub ampersand by a n d; # GSUB LookupType 2\012} ccmp;\012\012feature salt {\012 sub a from [a.alt A.sc]; # GSUB LookupType 3\012} salt;\012\012feature liga {\012 sub f t by f_t; # GSUB LookupType 4\012} liga;\012\012feature calt {\012 sub a' t by a.alt; # GSUB LookupType 6\012} calt;\012\012";
+};
+verticalStems = (
+32,
+48
+);
+weightValue = 0;
+xHeight = 478;
+},
+{
+alignmentZones = (
+"{696, 12}",
+"{650, 12}",
+"{634, 12}",
+"{580, 12}",
+"{532, 12}",
+"{500, 12}",
+"{0, -12}",
+"{-176, -12}"
+);
+ascender = 696;
+capHeight = 650;
+customParameters = (
+{
+name = "UFO Filename";
+value = "master_ufo/TestFamily2-Master1.ufo";
+},
+{
+name = "Master Name";
+value = "Master 1";
+},
+{
+name = hheaAscender;
+value = 984;
+},
+{
+name = hheaDescender;
+value = -273;
+},
+{
+name = hheaLineGap;
+value = 0;
+},
+{
+name = panose;
+value = (
+2,
+11,
+5,
+3,
+3,
+4,
+3,
+2,
+2,
+4
+);
+},
+{
+name = typoAscender;
+value = 750;
+},
+{
+name = typoDescender;
+value = -250;
+},
+{
+name = typoLineGap;
+value = 0;
+},
+{
+name = unicodeRanges;
+value = (
+0,
+1
+);
+},
+{
+name = blueScale;
+value = 0.0625;
+},
+{
+name = underlinePosition;
+value = -75;
+},
+{
+name = vendorID;
+value = ADBO;
+},
+{
+name = postscriptFontName;
+value = "TestFamily2-Master1";
+},
+{
+name = postscriptBlueFuzz;
+value = 0;
+},
+{
+name = postscriptForceBold;
+value = 0;
+},
+{
+name = styleMapFamilyName;
+value = "Test Family 2";
+},
+{
+name = postscriptFamilyBlues;
+value = (
+-12,
+0,
+486,
+498,
+518,
+530,
+574,
+586,
+638,
+650,
+656,
+668,
+712,
+724
+);
+},
+{
+name = postscriptFamilyOtherBlues;
+value = (
+-217,
+-205
+);
+},
+{
+name = codePageRanges;
+value = (
+1252,
+1250
+);
+},
+{
+name = codePageRangesUnsupportedBits;
+value = (
+29
+);
+},
+{
+name = winAscent;
+value = 984;
+},
+{
+name = winDescent;
+value = 273;
+},
+{
+name = weightClass;
+value = 900;
+}
+);
+descender = -176;
+horizontalStems = (
+134,
+144
+);
+id = "A99E50E2-B754-449B-A60B-37BA27802C99";
+userData = {
+com.schriftgestaltung.Glyphs.originalFeatureCode = "table head {\012 FontRevision 2.020;\012} head;\012\012\012table name {\012 nameid 9 \"Paul D. Hunt\";\012 nameid 9 1 \"Paul D. Hunt\";\012} name;\012\012\012table hhea {\012 Ascender 984;\012 Descender -273;\012 LineGap 0;\012} hhea;\012\012\012table BASE {\012 HorizAxis.BaseTagList ideo romn;\012 HorizAxis.BaseScriptList\012 latn romn -170 0,\012 grek romn -170 0,\012 cyrl romn -170 0,\012 DFLT romn -170 0;\012} BASE;\012\012\012table OS/2 {\012 Panose 2 11 8 3 3 4 3 2 2 4;\012 XHeight 500;\012 WeightClass 900;\012\012 TypoAscender 750;\012 TypoDescender -250;\012 TypoLineGap 0;\012 winAscent 984;\012 winDescent 273;\012\012 CapHeight 660;\012 WidthClass 5;\012 Vendor \"ADBO\";\012 FSType 0;\012} OS/2;\012\012\012languagesystem DFLT dflt;\012languagesystem latn dflt;\012\012# GSUB =========================================\012# No merging of GSUB is performed. The variable\012# font will inherit the GSUB table from the\012# base master.\012\012";
+};
+verticalStems = (
+172,
+176
+);
+weightValue = 1000;
+xHeight = 500;
+}
+);
+glyphs = (
+{
+glyphname = .notdef;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"528 0 LINE",
+"528 660 LINE",
+"96 660 LINE",
+"96 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"246 208 LINE",
+"310 314 LINE",
+"314 314 LINE",
+"376 208 LINE",
+"476 32 LINE",
+"144 32 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"254 458 LINE",
+"160 626 LINE",
+"462 626 LINE",
+"368 458 LINE",
+"314 366 LINE",
+"310 366 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"134 610 LINE",
+"288 340 LINE",
+"134 74 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"336 340 LINE",
+"488 610 LINE",
+"488 74 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 624;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"628 0 LINE",
+"628 660 LINE",
+"76 660 LINE",
+"76 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"314 160 LINE",
+"350 256 LINE",
+"354 256 LINE",
+"390 160 LINE",
+"416 104 LINE",
+"288 104 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"310 520 LINE",
+"292 556 LINE",
+"412 556 LINE",
+"394 520 LINE",
+"354 424 LINE",
+"350 424 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"188 508 LINE",
+"270 340 LINE",
+"188 172 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"434 340 LINE",
+"516 508 LINE",
+"516 172 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 704;
+}
+);
+note = "";
+},
+{
+glyphname = space;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+vertWidth = 0;
+width = 200;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+vertWidth = 0;
+width = 200;
+}
+);
+note = "";
+unicode = 0020;
+},
+{
+glyphname = A;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"42 0 LINE",
+"182 396 LINE SMOOTH",
+"210 476 OFFCURVE",
+"234 544 OFFCURVE",
+"258 626 CURVE",
+"262 626 LINE",
+"286 544 OFFCURVE",
+"310 476 OFFCURVE",
+"338 396 CURVE SMOOTH",
+"476 0 LINE",
+"510 0 LINE",
+"274 660 LINE",
+"246 660 LINE",
+"10 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"405 236 LINE",
+"405 264 LINE",
+"112 264 LINE",
+"112 236 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 520;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"166 0 LINE",
+"240 316 LINE SMOOTH",
+"256 378 OFFCURVE",
+"272 456 OFFCURVE",
+"286 522 CURVE",
+"290 522 LINE",
+"306 457 OFFCURVE",
+"322 378 OFFCURVE",
+"338 316 CURVE SMOOTH",
+"412 0 LINE",
+"594 0 LINE",
+"396 650 LINE",
+"188 650 LINE",
+"-10 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"450 138 LINE",
+"450 271 LINE",
+"132 271 LINE",
+"132 138 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 584;
+}
+);
+note = "";
+unicode = 0041;
+},
+{
+glyphname = a;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"262 -12 OFFCURVE",
+"322 24 OFFCURVE",
+"372 64 CURVE",
+"374 64 LINE",
+"378 0 LINE",
+"404 0 LINE",
+"404 310 LINE SMOOTH",
+"404 406 OFFCURVE",
+"370 490 OFFCURVE",
+"258 490 CURVE SMOOTH",
+"180 490 OFFCURVE",
+"114 450 OFFCURVE",
+"84 428 CURVE",
+"100 404 LINE",
+"130 428 OFFCURVE",
+"188 462 OFFCURVE",
+"256 462 CURVE SMOOTH",
+"356 462 OFFCURVE",
+"376 376 OFFCURVE",
+"374 298 CURVE",
+"158 274 OFFCURVE",
+"60 224 OFFCURVE",
+"60 117 CURVE SMOOTH",
+"60 26 OFFCURVE",
+"124 -12 OFFCURVE",
+"198 -12 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"142 16 OFFCURVE",
+"92 44 OFFCURVE",
+"92 118 CURVE SMOOTH",
+"92 200 OFFCURVE",
+"164 248 OFFCURVE",
+"374 272 CURVE",
+"374 98 LINE",
+"310 44 OFFCURVE",
+"258 16 OFFCURVE",
+"200 16 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 486;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"242 -12 OFFCURVE",
+"286 12 OFFCURVE",
+"326 48 CURVE",
+"330 48 LINE",
+"342 0 LINE",
+"482 0 LINE",
+"482 278 LINE SMOOTH",
+"482 442 OFFCURVE",
+"404 512 OFFCURVE",
+"274 512 CURVE SMOOTH",
+"196 512 OFFCURVE",
+"124 488 OFFCURVE",
+"54 446 CURVE",
+"114 334 LINE",
+"166 362 OFFCURVE",
+"204 376 OFFCURVE",
+"240 376 CURVE SMOOTH",
+"284 376 OFFCURVE",
+"306 360 OFFCURVE",
+"310 324 CURVE",
+"118 304 OFFCURVE",
+"38 246 OFFCURVE",
+"38 142 CURVE SMOOTH",
+"38 60 OFFCURVE",
+"94 -12 OFFCURVE",
+"188 -12 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"218 120 OFFCURVE",
+"202 133 OFFCURVE",
+"202 156 CURVE SMOOTH",
+"202 184 OFFCURVE",
+"228 210 OFFCURVE",
+"310 222 CURVE",
+"310 154 LINE",
+"292 134 OFFCURVE",
+"276 120 OFFCURVE",
+"248 120 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 536;
+}
+);
+note = "";
+unicode = 0061;
+},
+{
+glyphname = d;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"318 -12 OFFCURVE",
+"372 24 OFFCURVE",
+"412 64 CURVE",
+"414 64 LINE",
+"418 0 LINE",
+"444 0 LINE",
+"444 722 LINE",
+"414 722 LINE",
+"414 520 LINE",
+"416 430 LINE",
+"366 468 OFFCURVE",
+"326 490 OFFCURVE",
+"268 490 CURVE SMOOTH",
+"152 490 OFFCURVE",
+"54 392 OFFCURVE",
+"54 238 CURVE SMOOTH",
+"54 76 OFFCURVE",
+"132 -12 OFFCURVE",
+"252 -12 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"146 16 OFFCURVE",
+"86 106 OFFCURVE",
+"86 238 CURVE SMOOTH",
+"86 362 OFFCURVE",
+"164 462 OFFCURVE",
+"266 462 CURVE SMOOTH",
+"316 462 OFFCURVE",
+"360 444 OFFCURVE",
+"414 396 CURVE",
+"414 100 LINE",
+"360 46 OFFCURVE",
+"310 16 OFFCURVE",
+"254 16 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 540;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"284 -12 OFFCURVE",
+"332 12 OFFCURVE",
+"366 46 CURVE",
+"370 46 LINE",
+"382 0 LINE",
+"522 0 LINE",
+"522 696 LINE",
+"350 696 LINE",
+"350 534 LINE",
+"356 462 LINE",
+"326 492 OFFCURVE",
+"294 512 OFFCURVE",
+"240 512 CURVE SMOOTH",
+"138 512 OFFCURVE",
+"36 414 OFFCURVE",
+"36 250 CURVE SMOOTH",
+"36 88 OFFCURVE",
+"116 -12 OFFCURVE",
+"240 -12 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"240 128 OFFCURVE",
+"212 162 OFFCURVE",
+"212 252 CURVE SMOOTH",
+"212 340 OFFCURVE",
+"246 372 OFFCURVE",
+"282 372 CURVE SMOOTH",
+"304 372 OFFCURVE",
+"330 366 OFFCURVE",
+"350 348 CURVE",
+"350 164 LINE",
+"332 136 OFFCURVE",
+"312 128 OFFCURVE",
+"286 128 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 580;
+}
+);
+note = "";
+unicode = 0064;
+},
+{
+glyphname = f;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"130 0 LINE",
+"130 592 LINE SMOOTH",
+"130 664 OFFCURVE",
+"154 706 OFFCURVE",
+"208 706 CURVE SMOOTH",
+"226 706 OFFCURVE",
+"246 702 OFFCURVE",
+"266 692 CURVE",
+"276 718 LINE",
+"254 728 OFFCURVE",
+"230 734 OFFCURVE",
+"210 734 CURVE SMOOTH",
+"142 734 OFFCURVE",
+"100 690 OFFCURVE",
+"100 596 CURVE SMOOTH",
+"100 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"244 450 LINE",
+"244 478 LINE",
+"100 478 LINE",
+"34 474 LINE",
+"34 450 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 252;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"260 0 LINE",
+"260 512 LINE SMOOTH",
+"260 559 OFFCURVE",
+"280 574 OFFCURVE",
+"312 574 CURVE SMOOTH",
+"328 574 OFFCURVE",
+"346 570 OFFCURVE",
+"362 564 CURVE",
+"392 690 LINE",
+"370 698 OFFCURVE",
+"332 708 OFFCURVE",
+"286 708 CURVE SMOOTH",
+"138 708 OFFCURVE",
+"88 613 OFFCURVE",
+"88 506 CURVE SMOOTH",
+"88 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"344 366 LINE",
+"344 500 LINE",
+"98 500 LINE",
+"22 494 LINE",
+"22 366 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 360;
+}
+);
+note = "";
+unicode = 0066;
+},
+{
+glyphname = n;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"126 0 LINE",
+"126 366 LINE",
+"188 430 OFFCURVE",
+"232 462 OFFCURVE",
+"292 462 CURVE SMOOTH",
+"374 462 OFFCURVE",
+"408 410 OFFCURVE",
+"408 304 CURVE SMOOTH",
+"408 0 LINE",
+"438 0 LINE",
+"438 308 LINE SMOOTH",
+"438 432 OFFCURVE",
+"392 490 OFFCURVE",
+"294 490 CURVE SMOOTH",
+"228 490 OFFCURVE",
+"178 452 OFFCURVE",
+"128 402 CURVE",
+"126 402 LINE",
+"122 478 LINE",
+"96 478 LINE",
+"96 0 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 526;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"230 0 LINE",
+"230 328 LINE",
+"256 352 OFFCURVE",
+"274 366 OFFCURVE",
+"306 366 CURVE SMOOTH",
+"340 366 OFFCURVE",
+"356 350 OFFCURVE",
+"356 286 CURVE SMOOTH",
+"356 0 LINE",
+"528 0 LINE",
+"528 308 LINE SMOOTH",
+"528 432 OFFCURVE",
+"482 512 OFFCURVE",
+"372 512 CURVE SMOOTH",
+"304 512 OFFCURVE",
+"254 478 OFFCURVE",
+"214 440 CURVE",
+"210 440 LINE",
+"198 500 LINE",
+"58 500 LINE",
+"58 0 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 582;
+}
+);
+note = "";
+unicode = 006E;
+},
+{
+glyphname = t;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"234 -12 OFFCURVE",
+"264 -4 OFFCURVE",
+"292 6 CURVE",
+"282 32 LINE",
+"264 24 OFFCURVE",
+"238 16 OFFCURVE",
+"220 16 CURVE SMOOTH",
+"150 16 OFFCURVE",
+"136 60 OFFCURVE",
+"136 122 CURVE SMOOTH",
+"136 450 LINE",
+"278 450 LINE",
+"278 478 LINE",
+"136 478 LINE",
+"136 618 LINE",
+"110 618 LINE",
+"106 478 LINE",
+"30 474 LINE",
+"30 450 LINE",
+"106 450 LINE",
+"106 126 LINE SMOOTH",
+"106 44 OFFCURVE",
+"130 -12 OFFCURVE",
+"218 -12 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 302;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"319 -12 OFFCURVE",
+"356 -2 OFFCURVE",
+"382 6 CURVE",
+"356 130 LINE",
+"344 126 OFFCURVE",
+"328 122 OFFCURVE",
+"312 122 CURVE SMOOTH",
+"280 122 OFFCURVE",
+"252 140 OFFCURVE",
+"252 195 CURVE SMOOTH",
+"252 366 LINE",
+"366 366 LINE",
+"366 500 LINE",
+"252 500 LINE",
+"252 630 LINE",
+"110 630 LINE",
+"90 500 LINE",
+"14 494 LINE",
+"14 366 LINE",
+"80 366 LINE",
+"80 192 LINE SMOOTH",
+"80 70 OFFCURVE",
+"134 -12 OFFCURVE",
+"264 -12 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 400;
+}
+);
+note = "";
+unicode = 0074;
+},
+{
+glyphname = f_t;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"130 0 LINE",
+"130 592 LINE SMOOTH",
+"130 664 OFFCURVE",
+"154 706 OFFCURVE",
+"208 706 CURVE SMOOTH",
+"226 706 OFFCURVE",
+"246 702 OFFCURVE",
+"266 692 CURVE",
+"276 718 LINE",
+"254 728 OFFCURVE",
+"230 734 OFFCURVE",
+"210 734 CURVE SMOOTH",
+"142 734 OFFCURVE",
+"100 690 OFFCURVE",
+"100 596 CURVE SMOOTH",
+"100 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"450 -12 OFFCURVE",
+"480 -4 OFFCURVE",
+"508 6 CURVE",
+"498 32 LINE",
+"480 24 OFFCURVE",
+"454 16 OFFCURVE",
+"436 16 CURVE SMOOTH",
+"366 16 OFFCURVE",
+"352 60 OFFCURVE",
+"352 122 CURVE SMOOTH",
+"352 450 LINE",
+"494 450 LINE",
+"494 478 LINE",
+"352 478 LINE",
+"352 618 LINE",
+"326 618 LINE",
+"322 478 LINE",
+"100 478 LINE",
+"34 474 LINE",
+"34 450 LINE",
+"322 450 LINE",
+"322 126 LINE SMOOTH",
+"322 44 OFFCURVE",
+"346 -12 OFFCURVE",
+"434 -12 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 518;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"260 0 LINE",
+"260 512 LINE SMOOTH",
+"260 559 OFFCURVE",
+"280 574 OFFCURVE",
+"312 574 CURVE SMOOTH",
+"328 574 OFFCURVE",
+"346 570 OFFCURVE",
+"362 564 CURVE",
+"392 690 LINE",
+"370 698 OFFCURVE",
+"332 708 OFFCURVE",
+"286 708 CURVE SMOOTH",
+"138 708 OFFCURVE",
+"88 613 OFFCURVE",
+"88 506 CURVE SMOOTH",
+"88 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"643 -12 OFFCURVE",
+"680 -2 OFFCURVE",
+"706 6 CURVE",
+"680 130 LINE",
+"668 126 OFFCURVE",
+"652 122 OFFCURVE",
+"636 122 CURVE SMOOTH",
+"604 122 OFFCURVE",
+"576 140 OFFCURVE",
+"576 195 CURVE",
+"576 366 LINE",
+"690 366 LINE",
+"690 500 LINE",
+"576 500 LINE",
+"576 630 LINE",
+"434 630 LINE",
+"414 500 LINE",
+"98 500 LINE",
+"22 494 LINE",
+"22 366 LINE",
+"404 366 LINE",
+"404 192 LINE SMOOTH",
+"404 70 OFFCURVE",
+"458 -12 OFFCURVE",
+"588 -12 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 724;
+}
+);
+note = "";
+},
+{
+glyphname = a.alt;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"318 -12 OFFCURVE",
+"372 24 OFFCURVE",
+"412 64 CURVE",
+"414 64 LINE",
+"418 0 LINE",
+"444 0 LINE",
+"444 478 LINE",
+"416 478 LINE",
+"414 432 LINE",
+"412 432 LINE",
+"366 468 OFFCURVE",
+"326 490 OFFCURVE",
+"268 490 CURVE SMOOTH",
+"152 490 OFFCURVE",
+"54 392 OFFCURVE",
+"54 238 CURVE SMOOTH",
+"54 76 OFFCURVE",
+"132 -12 OFFCURVE",
+"252 -12 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"146 16 OFFCURVE",
+"86 106 OFFCURVE",
+"86 238 CURVE SMOOTH",
+"86 362 OFFCURVE",
+"164 462 OFFCURVE",
+"266 462 CURVE SMOOTH",
+"316 462 OFFCURVE",
+"360 444 OFFCURVE",
+"414 396 CURVE",
+"414 100 LINE",
+"360 46 OFFCURVE",
+"310 16 OFFCURVE",
+"254 16 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 540;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"284 -12 OFFCURVE",
+"332 12 OFFCURVE",
+"366 46 CURVE",
+"370 46 LINE",
+"382 0 LINE",
+"522 0 LINE",
+"522 500 LINE",
+"388 500 LINE",
+"374 450 LINE",
+"370 450 LINE",
+"332 494 OFFCURVE",
+"292 512 OFFCURVE",
+"244 512 CURVE SMOOTH",
+"142 512 OFFCURVE",
+"36 414 OFFCURVE",
+"36 250 CURVE SMOOTH",
+"36 88 OFFCURVE",
+"116 -12 OFFCURVE",
+"240 -12 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"240 128 OFFCURVE",
+"212 162 OFFCURVE",
+"212 252 CURVE SMOOTH",
+"212 340 OFFCURVE",
+"246 372 OFFCURVE",
+"282 372 CURVE SMOOTH",
+"304 372 OFFCURVE",
+"330 366 OFFCURVE",
+"350 348 CURVE",
+"350 164 LINE",
+"332 136 OFFCURVE",
+"312 128 OFFCURVE",
+"286 128 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 580;
+}
+);
+note = "";
+},
+{
+glyphname = A.sc;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"42 0 LINE",
+"158 304 LINE SMOOTH",
+"181 366 OFFCURVE",
+"199 414 OFFCURVE",
+"220 475 CURVE",
+"224 475 LINE",
+"245 415 OFFCURVE",
+"263 367 OFFCURVE",
+"286 304 CURVE SMOOTH",
+"400 0 LINE",
+"434 0 LINE",
+"236 510 LINE",
+"207 510 LINE",
+"10 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"345 176 LINE",
+"345 204 LINE",
+"97 204 LINE",
+"97 176 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 444;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"164 0 LINE",
+"219 244 LINE SMOOTH",
+"230 292 OFFCURVE",
+"241 358 OFFCURVE",
+"252 409 CURVE",
+"256 409 LINE",
+"269 359 OFFCURVE",
+"280 292 OFFCURVE",
+"291 244 CURVE SMOOTH",
+"346 0 LINE",
+"526 0 LINE",
+"361 532 LINE",
+"155 532 LINE",
+"-10 0 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"397 94 LINE",
+"397 216 LINE",
+"118 216 LINE",
+"118 94 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 516;
+}
+);
+note = "";
+},
+{
+glyphname = atilde;
+layers = (
+{
+components = (
+{
+name = a;
+},
+{
+name = tildecmb;
+transform = "{1, 0, 0, 1, 242, 0}";
+}
+);
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+vertWidth = 0;
+width = 486;
+},
+{
+components = (
+{
+name = a;
+},
+{
+name = tildecmb;
+transform = "{1, 0, 0, 1, 266, 0}";
+}
+);
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+vertWidth = 0;
+width = 536;
+}
+);
+note = "";
+unicode = 00E3;
+},
+{
+glyphname = ampersand;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"302 -12 OFFCURVE",
+"360 28 OFFCURVE",
+"410 84 CURVE SMOOTH",
+"468 153 OFFCURVE",
+"510 244 OFFCURVE",
+"538 342 CURVE",
+"508 342 LINE",
+"482 248 OFFCURVE",
+"444 166 OFFCURVE",
+"388 102 CURVE SMOOTH",
+"344 52 OFFCURVE",
+"288 16 OFFCURVE",
+"226 16 CURVE SMOOTH",
+"142 16 OFFCURVE",
+"70 76 OFFCURVE",
+"70 168 CURVE SMOOTH",
+"70 332 OFFCURVE",
+"364 392 OFFCURVE",
+"364 556 CURVE SMOOTH",
+"364 622 OFFCURVE",
+"328 672 OFFCURVE",
+"260 672 CURVE SMOOTH",
+"184 672 OFFCURVE",
+"130 612 OFFCURVE",
+"130 528 CURVE SMOOTH",
+"130 382 OFFCURVE",
+"264 196 OFFCURVE",
+"392 82 CURVE SMOOTH",
+"446 34 OFFCURVE",
+"496 4 OFFCURVE",
+"538 -12 CURVE",
+"550 16 LINE",
+"508 32 OFFCURVE",
+"460 62 OFFCURVE",
+"410 106 CURVE SMOOTH",
+"290 210 OFFCURVE",
+"160 392 OFFCURVE",
+"160 530 CURVE SMOOTH",
+"160 592 OFFCURVE",
+"196 644 OFFCURVE",
+"258 644 CURVE SMOOTH",
+"314 644 OFFCURVE",
+"334 598 OFFCURVE",
+"334 554 CURVE SMOOTH",
+"334 402 OFFCURVE",
+"38 346 OFFCURVE",
+"38 166 CURVE SMOOTH",
+"38 56 OFFCURVE",
+"124 -12 OFFCURVE",
+"224 -12 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 562;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"362 -12 OFFCURVE",
+"452 34 OFFCURVE",
+"516 104 CURVE SMOOTH",
+"590 187 OFFCURVE",
+"638 276 OFFCURVE",
+"668 374 CURVE",
+"512 374 LINE",
+"490 292 OFFCURVE",
+"448 228 OFFCURVE",
+"398 180 CURVE SMOOTH",
+"356 142 OFFCURVE",
+"310 118 OFFCURVE",
+"268 118 CURVE SMOOTH",
+"216 118 OFFCURVE",
+"184 146 OFFCURVE",
+"184 186 CURVE SMOOTH",
+"184 296 OFFCURVE",
+"458 332 OFFCURVE",
+"458 508 CURVE SMOOTH",
+"458 602 OFFCURVE",
+"390 662 OFFCURVE",
+"286 662 CURVE SMOOTH",
+"170 662 OFFCURVE",
+"98 580 OFFCURVE",
+"98 486 CURVE SMOOTH",
+"98 359 OFFCURVE",
+"244 182 OFFCURVE",
+"415 75 CURVE SMOOTH",
+"485 31 OFFCURVE",
+"560 0 OFFCURVE",
+"630 -12 CURVE",
+"670 126 LINE",
+"627 131 OFFCURVE",
+"573 153 OFFCURVE",
+"518 183 CURVE SMOOTH",
+"382 258 OFFCURVE",
+"239 390 OFFCURVE",
+"239 486 CURVE SMOOTH",
+"239 528 OFFCURVE",
+"263 550 OFFCURVE",
+"290 550 CURVE SMOOTH",
+"315 550 OFFCURVE",
+"328 536 OFFCURVE",
+"328 508 CURVE SMOOTH",
+"328 386 OFFCURVE",
+"22 396 OFFCURVE",
+"22 176 CURVE SMOOTH",
+"22 78 OFFCURVE",
+"95 -12 OFFCURVE",
+"246 -12 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 690;
+}
+);
+note = "";
+unicode = 0026;
+},
+{
+glyphname = circledotted;
+production = uni25CC;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"129 97 OFFCURVE",
+"141 110 OFFCURVE",
+"141 129 CURVE SMOOTH",
+"141 150 OFFCURVE",
+"128 161 OFFCURVE",
+"110 161 CURVE SMOOTH",
+"94 161 OFFCURVE",
+"81 150 OFFCURVE",
+"81 129 CURVE SMOOTH",
+"81 110 OFFCURVE",
+"94 97 OFFCURVE",
+"110 97 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"101 207 OFFCURVE",
+"114 219 OFFCURVE",
+"114 239 CURVE SMOOTH",
+"114 260 OFFCURVE",
+"101 270 OFFCURVE",
+"82 270 CURVE SMOOTH",
+"67 270 OFFCURVE",
+"54 260 OFFCURVE",
+"54 239 CURVE SMOOTH",
+"54 219 OFFCURVE",
+"67 207 OFFCURVE",
+"82 207 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"129 318 OFFCURVE",
+"141 330 OFFCURVE",
+"141 351 CURVE SMOOTH",
+"141 371 OFFCURVE",
+"128 382 OFFCURVE",
+"110 382 CURVE SMOOTH",
+"94 382 OFFCURVE",
+"81 371 OFFCURVE",
+"81 351 CURVE SMOOTH",
+"81 330 OFFCURVE",
+"94 318 OFFCURVE",
+"110 318 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"207 15 OFFCURVE",
+"219 27 OFFCURVE",
+"219 49 CURVE SMOOTH",
+"219 68 OFFCURVE",
+"206 78 OFFCURVE",
+"189 78 CURVE SMOOTH",
+"173 78 OFFCURVE",
+"160 68 OFFCURVE",
+"160 49 CURVE SMOOTH",
+"160 27 OFFCURVE",
+"173 15 OFFCURVE",
+"189 15 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"207 400 OFFCURVE",
+"219 412 OFFCURVE",
+"219 431 CURVE SMOOTH",
+"219 453 OFFCURVE",
+"206 463 OFFCURVE",
+"189 463 CURVE SMOOTH",
+"173 463 OFFCURVE",
+"160 453 OFFCURVE",
+"160 431 CURVE SMOOTH",
+"160 412 OFFCURVE",
+"173 400 OFFCURVE",
+"189 400 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"313 -12 OFFCURVE",
+"326 -1 OFFCURVE",
+"326 20 CURVE SMOOTH",
+"326 40 OFFCURVE",
+"313 51 OFFCURVE",
+"295 51 CURVE SMOOTH",
+"279 51 OFFCURVE",
+"266 40 OFFCURVE",
+"266 20 CURVE SMOOTH",
+"266 -1 OFFCURVE",
+"279 -12 OFFCURVE",
+"295 -12 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"313 426 OFFCURVE",
+"326 438 OFFCURVE",
+"326 458 CURVE SMOOTH",
+"326 478 OFFCURVE",
+"313 490 OFFCURVE",
+"295 490 CURVE SMOOTH",
+"279 490 OFFCURVE",
+"266 478 OFFCURVE",
+"266 458 CURVE SMOOTH",
+"266 438 OFFCURVE",
+"279 426 OFFCURVE",
+"295 426 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"420 15 OFFCURVE",
+"431 27 OFFCURVE",
+"431 49 CURVE SMOOTH",
+"431 68 OFFCURVE",
+"418 78 OFFCURVE",
+"401 78 CURVE SMOOTH",
+"386 78 OFFCURVE",
+"373 68 OFFCURVE",
+"373 49 CURVE SMOOTH",
+"373 27 OFFCURVE",
+"386 15 OFFCURVE",
+"401 15 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"420 399 OFFCURVE",
+"431 412 OFFCURVE",
+"431 431 CURVE SMOOTH",
+"431 452 OFFCURVE",
+"418 462 OFFCURVE",
+"401 462 CURVE SMOOTH",
+"386 462 OFFCURVE",
+"373 452 OFFCURVE",
+"373 431 CURVE SMOOTH",
+"373 412 OFFCURVE",
+"386 399 OFFCURVE",
+"401 399 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"499 97 OFFCURVE",
+"510 110 OFFCURVE",
+"510 129 CURVE SMOOTH",
+"510 150 OFFCURVE",
+"497 161 OFFCURVE",
+"480 161 CURVE SMOOTH",
+"465 161 OFFCURVE",
+"451 150 OFFCURVE",
+"451 129 CURVE SMOOTH",
+"451 110 OFFCURVE",
+"465 97 OFFCURVE",
+"480 97 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"526 207 OFFCURVE",
+"538 219 OFFCURVE",
+"538 239 CURVE SMOOTH",
+"538 260 OFFCURVE",
+"523 270 OFFCURVE",
+"508 270 CURVE SMOOTH",
+"491 270 OFFCURVE",
+"478 260 OFFCURVE",
+"478 239 CURVE SMOOTH",
+"478 219 OFFCURVE",
+"491 207 OFFCURVE",
+"508 207 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"499 317 OFFCURVE",
+"510 329 OFFCURVE",
+"510 349 CURVE SMOOTH",
+"510 369 OFFCURVE",
+"497 380 OFFCURVE",
+"480 380 CURVE SMOOTH",
+"465 380 OFFCURVE",
+"451 369 OFFCURVE",
+"451 349 CURVE SMOOTH",
+"451 329 OFFCURVE",
+"465 317 OFFCURVE",
+"480 317 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 592;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"131 96 OFFCURVE",
+"149 112 OFFCURVE",
+"149 141 CURVE SMOOTH",
+"149 170 OFFCURVE",
+"130 187 OFFCURVE",
+"104 187 CURVE SMOOTH",
+"82 187 OFFCURVE",
+"61 170 OFFCURVE",
+"61 141 CURVE SMOOTH",
+"61 112 OFFCURVE",
+"82 96 OFFCURVE",
+"104 96 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"104 204 OFFCURVE",
+"122 221 OFFCURVE",
+"122 251 CURVE SMOOTH",
+"122 279 OFFCURVE",
+"102 295 OFFCURVE",
+"76 295 CURVE SMOOTH",
+"53 295 OFFCURVE",
+"32 279 OFFCURVE",
+"32 251 CURVE SMOOTH",
+"32 221 OFFCURVE",
+"53 204 OFFCURVE",
+"76 204 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"131 313 OFFCURVE",
+"149 331 OFFCURVE",
+"149 360 CURVE SMOOTH",
+"149 390 OFFCURVE",
+"130 405 OFFCURVE",
+"104 405 CURVE SMOOTH",
+"82 405 OFFCURVE",
+"61 390 OFFCURVE",
+"61 360 CURVE SMOOTH",
+"61 331 OFFCURVE",
+"82 313 OFFCURVE",
+"104 313 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"208 14 OFFCURVE",
+"227 31 OFFCURVE",
+"227 61 CURVE SMOOTH",
+"227 89 OFFCURVE",
+"206 105 OFFCURVE",
+"182 105 CURVE SMOOTH",
+"158 105 OFFCURVE",
+"137 89 OFFCURVE",
+"137 61 CURVE SMOOTH",
+"137 31 OFFCURVE",
+"158 14 OFFCURVE",
+"182 14 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"208 395 OFFCURVE",
+"227 412 OFFCURVE",
+"227 440 CURVE SMOOTH",
+"227 470 OFFCURVE",
+"206 486 OFFCURVE",
+"182 486 CURVE SMOOTH",
+"158 486 OFFCURVE",
+"137 470 OFFCURVE",
+"137 440 CURVE SMOOTH",
+"137 412 OFFCURVE",
+"158 395 OFFCURVE",
+"182 395 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"314 -13 OFFCURVE",
+"332 4 OFFCURVE",
+"332 34 CURVE SMOOTH",
+"332 62 OFFCURVE",
+"313 78 OFFCURVE",
+"287 78 CURVE SMOOTH",
+"264 78 OFFCURVE",
+"244 62 OFFCURVE",
+"244 34 CURVE SMOOTH",
+"244 4 OFFCURVE",
+"264 -13 OFFCURVE",
+"287 -13 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"314 421 OFFCURVE",
+"332 439 OFFCURVE",
+"332 468 CURVE SMOOTH",
+"332 496 OFFCURVE",
+"313 512 OFFCURVE",
+"287 512 CURVE SMOOTH",
+"264 512 OFFCURVE",
+"244 496 OFFCURVE",
+"244 468 CURVE SMOOTH",
+"244 439 OFFCURVE",
+"264 421 OFFCURVE",
+"287 421 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"420 14 OFFCURVE",
+"438 31 OFFCURVE",
+"438 61 CURVE SMOOTH",
+"438 89 OFFCURVE",
+"417 105 OFFCURVE",
+"392 105 CURVE SMOOTH",
+"369 105 OFFCURVE",
+"348 89 OFFCURVE",
+"348 61 CURVE SMOOTH",
+"348 31 OFFCURVE",
+"369 14 OFFCURVE",
+"392 14 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"420 394 OFFCURVE",
+"438 411 OFFCURVE",
+"438 440 CURVE SMOOTH",
+"438 469 OFFCURVE",
+"417 486 OFFCURVE",
+"392 486 CURVE SMOOTH",
+"369 486 OFFCURVE",
+"348 469 OFFCURVE",
+"348 440 CURVE SMOOTH",
+"348 411 OFFCURVE",
+"369 394 OFFCURVE",
+"392 394 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"498 96 OFFCURVE",
+"516 112 OFFCURVE",
+"516 141 CURVE SMOOTH",
+"516 170 OFFCURVE",
+"496 187 OFFCURVE",
+"472 187 CURVE SMOOTH",
+"447 187 OFFCURVE",
+"426 170 OFFCURVE",
+"426 141 CURVE SMOOTH",
+"426 112 OFFCURVE",
+"447 96 OFFCURVE",
+"472 96 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"524 204 OFFCURVE",
+"543 221 OFFCURVE",
+"543 251 CURVE SMOOTH",
+"543 279 OFFCURVE",
+"522 295 OFFCURVE",
+"498 295 CURVE SMOOTH",
+"473 295 OFFCURVE",
+"453 279 OFFCURVE",
+"453 251 CURVE SMOOTH",
+"453 221 OFFCURVE",
+"473 204 OFFCURVE",
+"498 204 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"498 313 OFFCURVE",
+"516 330 OFFCURVE",
+"516 359 CURVE SMOOTH",
+"516 388 OFFCURVE",
+"496 404 OFFCURVE",
+"472 404 CURVE SMOOTH",
+"447 404 OFFCURVE",
+"426 388 OFFCURVE",
+"426 359 CURVE SMOOTH",
+"426 330 OFFCURVE",
+"447 313 OFFCURVE",
+"472 313 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 574;
+}
+);
+note = "";
+unicode = 25CC;
+},
+{
+glyphname = tildecmb;
+production = uni0303;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"140 580 OFFCURVE",
+"156 646 OFFCURVE",
+"160 702 CURVE",
+"134 704 LINE",
+"132 652 OFFCURVE",
+"116 606 OFFCURVE",
+"79 606 CURVE SMOOTH",
+"20 606 OFFCURVE",
+"0 706 OFFCURVE",
+"-76 706 CURVE SMOOTH",
+"-140 706 OFFCURVE",
+"-156 641 OFFCURVE",
+"-160 584 CURVE",
+"-134 582 LINE",
+"-132 636 OFFCURVE",
+"-116 680 OFFCURVE",
+"-78 680 CURVE SMOOTH",
+"-20 680 OFFCURVE",
+"0 580 OFFCURVE",
+"77 580 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 0;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"144 572 OFFCURVE",
+"194 617 OFFCURVE",
+"196 730 CURVE",
+"90 736 LINE",
+"86 700 OFFCURVE",
+"76 690 OFFCURVE",
+"60 690 CURVE SMOOTH",
+"34 690 OFFCURVE",
+"-4 746 OFFCURVE",
+"-64 746 CURVE SMOOTH",
+"-144 746 OFFCURVE",
+"-194 701 OFFCURVE",
+"-196 588 CURVE",
+"-90 582 LINE",
+"-86 618 OFFCURVE",
+"-76 628 OFFCURVE",
+"-60 628 CURVE SMOOTH",
+"-34 628 OFFCURVE",
+"4 572 OFFCURVE",
+"64 572 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 0;
+}
+);
+note = "";
+unicode = 0303;
+},
+{
+glyphname = dieresiscmb;
+production = uni0308;
+layers = (
+{
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+paths = (
+{
+closed = 1;
+nodes = (
+"-68 602 OFFCURVE",
+"-54 616 OFFCURVE",
+"-54 634 CURVE SMOOTH",
+"-54 652 OFFCURVE",
+"-68 666 OFFCURVE",
+"-86 666 CURVE SMOOTH",
+"-104 666 OFFCURVE",
+"-118 652 OFFCURVE",
+"-118 634 CURVE SMOOTH",
+"-118 616 OFFCURVE",
+"-104 602 OFFCURVE",
+"-86 602 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"104 602 OFFCURVE",
+"118 616 OFFCURVE",
+"118 634 CURVE SMOOTH",
+"118 652 OFFCURVE",
+"104 666 OFFCURVE",
+"86 666 CURVE SMOOTH",
+"68 666 OFFCURVE",
+"54 652 OFFCURVE",
+"54 634 CURVE SMOOTH",
+"54 616 OFFCURVE",
+"68 602 OFFCURVE",
+"86 602 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 0;
+},
+{
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+paths = (
+{
+closed = 1;
+nodes = (
+"-67 562 OFFCURVE",
+"-34 597 OFFCURVE",
+"-34 642 CURVE SMOOTH",
+"-34 687 OFFCURVE",
+"-67 722 OFFCURVE",
+"-114 722 CURVE SMOOTH",
+"-161 722 OFFCURVE",
+"-194 687 OFFCURVE",
+"-194 642 CURVE SMOOTH",
+"-194 597 OFFCURVE",
+"-161 562 OFFCURVE",
+"-114 562 CURVE SMOOTH"
+);
+},
+{
+closed = 1;
+nodes = (
+"161 562 OFFCURVE",
+"194 597 OFFCURVE",
+"194 642 CURVE SMOOTH",
+"194 687 OFFCURVE",
+"161 722 OFFCURVE",
+"114 722 CURVE SMOOTH",
+"67 722 OFFCURVE",
+"34 687 OFFCURVE",
+"34 642 CURVE SMOOTH",
+"34 597 OFFCURVE",
+"67 562 OFFCURVE",
+"114 562 CURVE SMOOTH"
+);
+}
+);
+vertWidth = 0;
+width = 0;
+}
+);
+note = "";
+unicode = 0308;
+},
+{
+glyphname = tildebelowcmb;
+production = uni0330;
+layers = (
+{
+components = (
+{
+name = tildecmb;
+transform = "{1, 0, 0, 1, 0, -800}";
+}
+);
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+vertWidth = 0;
+width = 0;
+},
+{
+components = (
+{
+name = tildecmb;
+transform = "{1, 0, 0, 1, 0, -800}";
+}
+);
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+vertWidth = 0;
+width = 0;
+}
+);
+note = "";
+unicode = 0330;
+},
+{
+glyphname = dieresisbelowcmb;
+production = uni0324;
+layers = (
+{
+components = (
+{
+name = dieresiscmb;
+transform = "{1, 0, 0, 1, 0, -790}";
+}
+);
+layerId = "8DB0CCF0-BD6F-426B-90E2-48FD021BE868";
+vertWidth = 0;
+width = 0;
+},
+{
+components = (
+{
+name = dieresiscmb;
+transform = "{1, 0, 0, 1, 0, -786}";
+}
+);
+layerId = "A99E50E2-B754-449B-A60B-37BA27802C99";
+vertWidth = 0;
+width = 0;
+}
+);
+note = "";
+unicode = 0324;
+}
+);
+instances = (
+{
+customParameters = (
+{
+name = weightClass;
+value = 0;
+},
+{
+name = postscriptFontName;
+value = "TestFamily2-ExtraLight";
+},
+{
+name = "UFO Filename";
+value = "instances/TestFamily2-ExtraLight.ufo";
+}
+);
+interpolationWeight = 0;
+name = ExtraLight;
+weightClass = Thin;
+},
+{
+customParameters = (
+{
+name = postscriptFontName;
+value = "TestFamily2-Light";
+},
+{
+name = "UFO Filename";
+value = "instances/TestFamily2-Light.ufo";
+}
+);
+name = Light;
+weightClass = Thin;
+},
+{
+customParameters = (
+{
+name = weightClass;
+value = 368;
+},
+{
+name = postscriptFontName;
+value = "TestFamily2-Regular";
+},
+{
+name = "UFO Filename";
+value = "instances/TestFamily2-Regular.ufo";
+}
+);
+interpolationWeight = 368;
+name = Regular;
+weightClass = Normal;
+},
+{
+customParameters = (
+{
+name = postscriptFontName;
+value = "TestFamily2-Semibold";
+},
+{
+name = "UFO Filename";
+value = "instances/TestFamily2-Semibold.ufo";
+}
+);
+interpolationWeight = 600;
+name = Semibold;
+weightClass = DemiBold;
+},
+{
+customParameters = (
+{
+name = weightClass;
+value = 824;
+},
+{
+name = postscriptFontName;
+value = "TestFamily2-Bold";
+},
+{
+name = "UFO Filename";
+value = "instances/TestFamily2-Bold.ufo";
+}
+);
+interpolationWeight = 824;
+name = Bold;
+weightClass = ExtraBold;
+},
+{
+customParameters = (
+{
+name = weightClass;
+value = 1000;
+},
+{
+name = postscriptFontName;
+value = "TestFamily2-Black";
+},
+{
+name = "UFO Filename";
+value = "instances/TestFamily2-Black.ufo";
+}
+);
+interpolationWeight = 1000;
+name = Black;
+weightClass = Black;
+}
+);
+unitsPerEm = 1000;
+userData = {
+com.schriftgestaltung.Glyphs.groupsNotInFeature = (
+);
+};
+versionMajor = 2;
+versionMinor = 20;
+}
diff --git a/Tests/varLib/data/SparseCFF2.designspace b/Tests/varLib/data/SparseCFF2.designspace
new file mode 100644
index 00000000..cd8823a7
--- /dev/null
+++ b/Tests/varLib/data/SparseCFF2.designspace
@@ -0,0 +1,23 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="350" maximum="625" default="350"/>
+ </axes>
+ <sources>
+ <source filename="master_sparse_cff2_empty/SparseCFF-Regular.ttx" name="Sparse Font Regular" familyname="Sparse Font" stylename="Regular">
+ <location>
+ <dimension name="Weight" xvalue="350"/>
+ </location>
+ </source>
+ <source filename="master_sparse_cff2_empty/SparseCFF-Medium.ttx" name="Sparse Font Medium" familyname="Sparse Font" stylename="Medium">
+ <location>
+ <dimension name="Weight" xvalue="450"/>
+ </location>
+ </source>
+ <source filename="master_sparse_cff2_empty/SparseCFF-Bold.ttx" name="Sparse Font Bold" familyname="Sparse Font" stylename="Bold">
+ <location>
+ <dimension name="Weight" xvalue="625"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/SparseMasters.glyphs b/Tests/varLib/data/SparseMasters.glyphs
new file mode 100644
index 00000000..a9843a46
--- /dev/null
+++ b/Tests/varLib/data/SparseMasters.glyphs
@@ -0,0 +1,486 @@
+{
+.appVersion = "895";
+customParameters = (
+{
+name = glyphOrder;
+value = (
+.notdef,
+a,
+e,
+edotabove,
+s,
+dotabovecomb
+);
+},
+{
+name = "Disable Last Change";
+value = 1;
+}
+);
+disablesAutomaticAlignment = 1;
+familyName = "Sparse Masters";
+fontMaster = (
+{
+ascender = 750;
+capHeight = 700;
+customParameters = (
+{
+name = "UFO Filename";
+value = "master_ufo/SparseMasters-Regular.ufo";
+}
+);
+descender = -250;
+id = "CCC32AD0-E3D7-4595-BA12-BA39A95902C9";
+userData = {
+com.defcon.sortDescriptor = (
+{
+ascending = (
+.notdef,
+a,
+e,
+edotabove,
+s,
+dotabovecomb
+);
+type = glyphList;
+}
+);
+};
+weightValue = 350;
+xHeight = 500;
+},
+{
+ascender = 750;
+capHeight = 700;
+customParameters = (
+{
+name = "UFO Filename";
+value = "master_ufo/SparseMasters-Medium.ufo";
+},
+{
+name = "Master Name";
+value = Medium;
+}
+);
+descender = -250;
+id = "2B2F6A55-E8C4-4456-AFD7-7A9468BB18B9";
+userData = {
+};
+weightValue = 450;
+xHeight = 500;
+},
+{
+ascender = 750;
+capHeight = 700;
+customParameters = (
+{
+name = "UFO Filename";
+value = "master_ufo/SparseMasters-Bold.ufo";
+},
+{
+name = "Master Name";
+value = Bold;
+}
+);
+descender = -250;
+id = "36D5BF76-782C-4F60-A6DB-0A9BC5828108";
+userData = {
+com.defcon.sortDescriptor = (
+{
+ascending = (
+.notdef,
+a,
+e,
+edotabove,
+s,
+dotabovecomb
+);
+type = glyphList;
+}
+);
+};
+weightValue = 625;
+xHeight = 500;
+}
+);
+glyphs = (
+{
+glyphname = .notdef;
+layers = (
+{
+layerId = "CCC32AD0-E3D7-4595-BA12-BA39A95902C9";
+paths = (
+{
+closed = 1;
+nodes = (
+"450 750 LINE",
+"450 -250 LINE",
+"50 -250 LINE",
+"50 750 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"400 700 LINE",
+"100 700 LINE",
+"100 -200 LINE",
+"400 -200 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 500;
+},
+{
+layerId = "2B2F6A55-E8C4-4456-AFD7-7A9468BB18B9";
+paths = (
+{
+closed = 1;
+nodes = (
+"450 750 LINE",
+"450 -250 LINE",
+"50 -250 LINE",
+"50 750 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"400 700 LINE",
+"100 700 LINE",
+"100 -200 LINE",
+"400 -200 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 500;
+},
+{
+layerId = "36D5BF76-782C-4F60-A6DB-0A9BC5828108";
+paths = (
+{
+closed = 1;
+nodes = (
+"450 750 LINE",
+"450 -250 LINE",
+"50 -250 LINE",
+"50 750 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"400 700 LINE",
+"100 700 LINE",
+"100 -200 LINE",
+"400 -200 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 500;
+}
+);
+note = .notdef;
+},
+{
+glyphname = a;
+layers = (
+{
+layerId = "CCC32AD0-E3D7-4595-BA12-BA39A95902C9";
+paths = (
+{
+closed = 1;
+nodes = (
+"214 504 LINE",
+"9 428 LINE",
+"36 337 LINE",
+"208 397 LINE",
+"363 357 LINE",
+"366 -3 LINE",
+"468 -1 LINE",
+"447 434 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"29 22 LINE",
+"168 -12 LINE",
+"389 71 LINE",
+"383 134 LINE",
+"161 74 LINE",
+"86 126 LINE",
+"88 172 LINE",
+"382 207 LINE",
+"378 263 LINE",
+"26 240 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 600;
+},
+{
+layerId = "36D5BF76-782C-4F60-A6DB-0A9BC5828108";
+paths = (
+{
+closed = 1;
+nodes = (
+"214 504 LINE",
+"9 428 LINE",
+"36 281 LINE",
+"208 341 LINE",
+"304 303 LINE",
+"307 -1 LINE",
+"468 -1 LINE",
+"447 434 LINE"
+);
+},
+{
+closed = 1;
+nodes = (
+"29 22 LINE",
+"168 -12 LINE",
+"389 71 LINE",
+"383 149 LINE",
+"201 102 LINE",
+"163 133 LINE",
+"165 179 LINE",
+"381 184 LINE",
+"378 263 LINE",
+"26 240 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 600;
+}
+);
+note = a;
+unicode = 0061;
+},
+{
+glyphname = e;
+layers = (
+{
+layerId = "CCC32AD0-E3D7-4595-BA12-BA39A95902C9";
+paths = (
+{
+closed = 1;
+nodes = (
+"571 305 LINE",
+"316 513 LINE",
+"40 261 LINE",
+"188 -18 LINE",
+"526 45 LINE",
+"509 129 LINE",
+"229 75 LINE",
+"147 263 LINE",
+"317 416 LINE",
+"480 292 LINE",
+"125 298 LINE",
+"127 228 LINE",
+"576 226 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 600;
+},
+{
+layerId = "2B2F6A55-E8C4-4456-AFD7-7A9468BB18B9";
+paths = (
+{
+closed = 1;
+nodes = (
+"571 305 LINE",
+"316 513 LINE",
+"40 261 LINE",
+"188 -18 LINE",
+"526 45 LINE",
+"507 157 LINE",
+"264 116 LINE",
+"180 264 LINE",
+"318 387 LINE",
+"396 297 LINE",
+"125 298 LINE",
+"126 203 LINE",
+"576 199 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 600;
+},
+{
+layerId = "36D5BF76-782C-4F60-A6DB-0A9BC5828108";
+paths = (
+{
+closed = 1;
+nodes = (
+"596 304 LINE",
+"314 548 LINE",
+"9 262 LINE",
+"188 -18 LINE",
+"528 0 LINE",
+"524 184 LINE",
+"244 130 LINE",
+"217 264 LINE",
+"301 360 LINE",
+"404 293 LINE",
+"195 299 LINE",
+"197 229 LINE",
+"601 225 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 600;
+}
+);
+note = e;
+unicode = 0065;
+},
+{
+glyphname = edotabove;
+layers = (
+{
+components = (
+{
+name = e;
+},
+{
+name = dotabovecomb;
+transform = "{1, 0, 0, 1, 313, 96}";
+}
+);
+layerId = "CCC32AD0-E3D7-4595-BA12-BA39A95902C9";
+vertWidth = 0;
+width = 600;
+},
+{
+components = (
+{
+name = e;
+},
+{
+name = dotabovecomb;
+transform = "{1, 0, 0, 1, 307, 187}";
+}
+);
+layerId = "36D5BF76-782C-4F60-A6DB-0A9BC5828108";
+vertWidth = 0;
+width = 600;
+}
+);
+note = edotabove;
+unicode = 0117;
+},
+{
+glyphname = s;
+layers = (
+{
+layerId = "CCC32AD0-E3D7-4595-BA12-BA39A95902C9";
+paths = (
+{
+closed = 1;
+nodes = (
+"38 343 LINE",
+"427 155 LINE",
+"282 76 LINE",
+"53 174 LINE",
+"25 83 LINE",
+"304 -13 LINE",
+"582 174 LINE",
+"213 366 LINE",
+"326 442 LINE",
+"539 376 LINE",
+"559 459 LINE",
+"324 530 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 600;
+},
+{
+layerId = "36D5BF76-782C-4F60-A6DB-0A9BC5828108";
+paths = (
+{
+closed = 1;
+nodes = (
+"16 398 LINE",
+"347 149 LINE",
+"221 119 LINE",
+"26 226 LINE",
+"7 79 LINE",
+"284 -58 LINE",
+"608 141 LINE",
+"268 357 LINE",
+"324 402 LINE",
+"537 336 LINE",
+"559 459 LINE",
+"324 530 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 600;
+}
+);
+note = s;
+unicode = 0073;
+},
+{
+glyphname = dotabovecomb;
+layers = (
+{
+layerId = "CCC32AD0-E3D7-4595-BA12-BA39A95902C9";
+paths = (
+{
+closed = 1;
+nodes = (
+"41 501 LINE",
+"50 589 LINE",
+"-21 597 LINE",
+"-37 503 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 0;
+},
+{
+layerId = "36D5BF76-782C-4F60-A6DB-0A9BC5828108";
+paths = (
+{
+closed = 1;
+nodes = (
+"58 488 LINE",
+"63 605 LINE",
+"-29 625 LINE",
+"-64 483 LINE"
+);
+}
+);
+vertWidth = 0;
+width = 0;
+}
+);
+note = dotabovecomb;
+unicode = 0307;
+}
+);
+instances = (
+);
+unitsPerEm = 1000;
+userData = {
+com.schriftgestaltung.Glyphs.groupsNotInFeature = (
+);
+};
+versionMajor = 1;
+versionMinor = 0;
+}
diff --git a/Tests/varLib/data/SparseMasters_ufo.designspace b/Tests/varLib/data/SparseMasters_ufo.designspace
new file mode 100644
index 00000000..1fd57bca
--- /dev/null
+++ b/Tests/varLib/data/SparseMasters_ufo.designspace
@@ -0,0 +1,23 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="4.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="350" maximum="625" default="350"/>
+ </axes>
+ <sources>
+ <source filename="master_ufo/SparseMasters-Regular.ufo" name="Sparse Masters Regular">
+ <location>
+ <dimension name="Weight" xvalue="350"/>
+ </location>
+ </source>
+ <source filename="master_ufo/SparseMasters-Medium.ufo" name="Sparse Masters Medium">
+ <location>
+ <dimension name="Weight" xvalue="450"/>
+ </location>
+ </source>
+ <source filename="master_ufo/SparseMasters-Bold.ufo" name="Sparse Masters Bold">
+ <location>
+ <dimension name="Weight" xvalue="625"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/TestNoOverwriteSTAT.designspace b/Tests/varLib/data/TestNoOverwriteSTAT.designspace
new file mode 100644
index 00000000..e06a1be5
--- /dev/null
+++ b/Tests/varLib/data/TestNoOverwriteSTAT.designspace
@@ -0,0 +1,36 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<designspace format="5.0">
+ <axes>
+ <axis tag="wght" name="Weight" minimum="100" maximum="900" default="100">
+ <map input="100" output="30"/>
+ <map input="900" output="240"/>
+ </axis>
+ <axis tag="wdth" name="Width" minimum="80" maximum="115" default="80"/>
+ </axes>
+ <sources>
+ <source filename="master_no_overwrite_stat/Test-CondensedThin.ttx" name="Test Condensed Thin">
+ <location>
+ <dimension name="Weight" xvalue="30"/>
+ <dimension name="Width" xvalue="80"/>
+ </location>
+ </source>
+ <source filename="master_no_overwrite_stat/Test-CondensedBlack.ttx" name="Test Condensed Black">
+ <location>
+ <dimension name="Weight" xvalue="240"/>
+ <dimension name="Width" xvalue="80"/>
+ </location>
+ </source>
+ <source filename="master_no_overwrite_stat/Test-ExtendedThin.ttx" name="Test Extended Thin">
+ <location>
+ <dimension name="Weight" xvalue="30"/>
+ <dimension name="Width" xvalue="115"/>
+ </location>
+ </source>
+ <source filename="master_no_overwrite_stat/Test-ExtendedBlack.ttx" name="Test Extended Black">
+ <location>
+ <dimension name="Weight" xvalue="240"/>
+ <dimension name="Width" xvalue="115"/>
+ </location>
+ </source>
+ </sources>
+</designspace>
diff --git a/Tests/varLib/data/master_no_overwrite_stat/Test-CondensedBlack.ttx b/Tests/varLib/data/master_no_overwrite_stat/Test-CondensedBlack.ttx
new file mode 100644
index 00000000..db687a9f
--- /dev/null
+++ b/Tests/varLib/data/master_no_overwrite_stat/Test-CondensedBlack.ttx
@@ -0,0 +1,243 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <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="3.013"/>
+ <checkSumAdjustment value="0x37268237"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Feb 17 14:29:44 2023"/>
+ <modified value="Tue Mar 7 12:56:58 2023"/>
+ <xMin value="-2"/>
+ <yMin value="-250"/>
+ <xMax value="583"/>
+ <yMax value="750"/>
+ <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="-300"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="582"/>
+ <minLeftSideBearing value="-2"/>
+ <minRightSideBearing value="-1"/>
+ <xMaxExtent value="583"/>
+ <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="19"/>
+ <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="541"/>
+ <usWeightClass value="900"/>
+ <usWidthClass value="3"/>
+ <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="80"/>
+ <yStrikeoutPosition value="303"/>
+ <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="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="250"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="505"/>
+ <sCapHeight value="670"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="582" lsb="-2"/>
+ </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" xMin="50" yMin="-250" xMax="450" yMax="750">
+ <contour>
+ <pt x="50" y="-250" on="1"/>
+ <pt x="50" y="750" on="1"/>
+ <pt x="450" y="750" on="1"/>
+ <pt x="450" y="-250" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-200" on="1"/>
+ <pt x="400" y="-200" on="1"/>
+ <pt x="400" y="700" on="1"/>
+ <pt x="100" y="700" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="-2" yMin="0" xMax="583" yMax="672">
+ <contour>
+ <pt x="410" y="672" on="1"/>
+ <pt x="570" y="81" on="1"/>
+ <pt x="576" y="58" on="0"/>
+ <pt x="583" y="19" on="0"/>
+ <pt x="583" y="0" on="1"/>
+ <pt x="384" y="0" on="1"/>
+ <pt x="315" y="355" on="1"/>
+ <pt x="297" y="480" on="1"/>
+ <pt x="292" y="480" on="1"/>
+ <pt x="195" y="0" on="1"/>
+ <pt x="-2" y="0" on="1"/>
+ <pt x="-2" y="15" on="0"/>
+ <pt x="2" y="55" on="0"/>
+ <pt x="9" y="79" on="1"/>
+ <pt x="176" y="668" on="1"/>
+ </contour>
+ <contour>
+ <pt x="422" y="257" on="1"/>
+ <pt x="422" y="107" on="1"/>
+ <pt x="137" y="107" on="1"/>
+ <pt x="137" y="257" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test Condensed Black
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 3.013;NONE;Test-CondensedBlack
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Condensed Black
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 3.013
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Test-CondensedBlack
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ Test
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Condensed Black
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-130"/>
+ <underlineThickness value="80"/>
+ <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>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_no_overwrite_stat/Test-CondensedThin.ttx b/Tests/varLib/data/master_no_overwrite_stat/Test-CondensedThin.ttx
new file mode 100644
index 00000000..e3d16464
--- /dev/null
+++ b/Tests/varLib/data/master_no_overwrite_stat/Test-CondensedThin.ttx
@@ -0,0 +1,373 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <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="3.013"/>
+ <checkSumAdjustment value="0x176a479f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Feb 17 14:29:44 2023"/>
+ <modified value="Tue Mar 7 12:56:58 2023"/>
+ <xMin value="28"/>
+ <yMin value="-250"/>
+ <xMax value="450"/>
+ <yMax value="750"/>
+ <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="-300"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="500"/>
+ <minLeftSideBearing value="28"/>
+ <minRightSideBearing value="31"/>
+ <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="2"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="2"/>
+ <maxPoints value="19"/>
+ <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="482"/>
+ <usWeightClass value="100"/>
+ <usWidthClass value="3"/>
+ <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="25"/>
+ <yStrikeoutPosition value="274"/>
+ <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="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="250"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="456"/>
+ <sCapHeight value="670"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="464" lsb="28"/>
+ </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" xMin="50" yMin="-250" xMax="450" yMax="750">
+ <contour>
+ <pt x="50" y="-250" on="1"/>
+ <pt x="50" y="750" on="1"/>
+ <pt x="450" y="750" on="1"/>
+ <pt x="450" y="-250" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-200" on="1"/>
+ <pt x="400" y="-200" on="1"/>
+ <pt x="400" y="700" on="1"/>
+ <pt x="100" y="700" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="28" yMin="0" xMax="433" yMax="672">
+ <contour>
+ <pt x="247" y="672" on="1"/>
+ <pt x="408" y="90" on="1"/>
+ <pt x="414" y="68" on="0"/>
+ <pt x="427" y="23" on="0"/>
+ <pt x="433" y="0" on="1"/>
+ <pt x="399" y="0" on="1"/>
+ <pt x="266" y="482" on="1"/>
+ <pt x="235" y="613" on="1"/>
+ <pt x="233" y="613" on="1"/>
+ <pt x="62" y="0" on="1"/>
+ <pt x="28" y="0" on="1"/>
+ <pt x="35" y="23" on="0"/>
+ <pt x="47" y="68" on="0"/>
+ <pt x="54" y="90" on="1"/>
+ <pt x="218" y="668" on="1"/>
+ </contour>
+ <contour>
+ <pt x="354" y="244" on="1"/>
+ <pt x="354" y="217" on="1"/>
+ <pt x="109" y="217" on="1"/>
+ <pt x="109" y="244" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test Condensed Thin
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 3.013;NONE;Test-CondensedThin
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Condensed Thin
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 3.013
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Test-CondensedThin
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ Test
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Condensed Thin
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Standard
+ </namerecord>
+ <namerecord nameID="257" platformID="3" platEncID="1" langID="0x409">
+ Heaviness
+ </namerecord>
+ <namerecord nameID="258" platformID="3" platEncID="1" langID="0x409">
+ Extralight
+ </namerecord>
+ <namerecord nameID="259" platformID="3" platEncID="1" langID="0x409">
+ Thin
+ </namerecord>
+ <namerecord nameID="260" platformID="3" platEncID="1" langID="0x409">
+ Light
+ </namerecord>
+ <namerecord nameID="261" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="262" platformID="3" platEncID="1" langID="0x409">
+ Medium
+ </namerecord>
+ <namerecord nameID="263" platformID="3" platEncID="1" langID="0x409">
+ Semibold
+ </namerecord>
+ <namerecord nameID="264" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="265" platformID="3" platEncID="1" langID="0x409">
+ Wideness
+ </namerecord>
+ <namerecord nameID="266" platformID="3" platEncID="1" langID="0x409">
+ Compressed
+ </namerecord>
+ <namerecord nameID="267" platformID="3" platEncID="1" langID="0x409">
+ Condensed
+ </namerecord>
+ <namerecord nameID="268" platformID="3" platEncID="1" langID="0x409">
+ Normal
+ </namerecord>
+ <namerecord nameID="269" platformID="3" platEncID="1" langID="0x409">
+ Extended
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-130"/>
+ <underlineThickness value="25"/>
+ <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=2 -->
+ <DesignAxisRecord>
+ <Axis index="0">
+ <AxisTag value="wght"/>
+ <AxisNameID value="257"/> <!-- Heaviness -->
+ <AxisOrdering value="0"/>
+ </Axis>
+ <Axis index="1">
+ <AxisTag value="wdth"/>
+ <AxisNameID value="265"/> <!-- Wideness -->
+ <AxisOrdering value="1"/>
+ </Axis>
+ </DesignAxisRecord>
+ <!-- AxisValueCount=11 -->
+ <AxisValueArray>
+ <AxisValue index="0" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="258"/> <!-- Extralight -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="1" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="259"/> <!-- Thin -->
+ <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="1">
+ <AxisIndex value="0"/>
+ <Flags value="2"/> <!-- ElidableAxisValueName -->
+ <ValueNameID value="261"/> <!-- Regular -->
+ <Value value="400.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="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="266"/> <!-- Compressed -->
+ <Value value="80.0"/>
+ </AxisValue>
+ <AxisValue index="8" Format="1">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="267"/> <!-- Condensed -->
+ <Value value="90.0"/>
+ </AxisValue>
+ <AxisValue index="9" Format="1">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="268"/> <!-- Normal -->
+ <Value value="100.0"/>
+ </AxisValue>
+ <AxisValue index="10" Format="1">
+ <AxisIndex value="1"/>
+ <Flags value="0"/>
+ <ValueNameID value="269"/> <!-- Extended -->
+ <Value value="115.0"/>
+ </AxisValue>
+ </AxisValueArray>
+ <ElidedFallbackNameID value="256"/> <!-- Standard -->
+ </STAT>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedBlack.ttx b/Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedBlack.ttx
new file mode 100644
index 00000000..8dc69399
--- /dev/null
+++ b/Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedBlack.ttx
@@ -0,0 +1,243 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <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="3.013"/>
+ <checkSumAdjustment value="0xc2b89a77"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Feb 17 14:29:44 2023"/>
+ <modified value="Tue Mar 7 12:56:58 2023"/>
+ <xMin value="-6"/>
+ <yMin value="-250"/>
+ <xMax value="759"/>
+ <yMax value="750"/>
+ <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="-300"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="762"/>
+ <minLeftSideBearing value="-6"/>
+ <minRightSideBearing value="3"/>
+ <xMaxExtent value="759"/>
+ <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="19"/>
+ <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="631"/>
+ <usWeightClass value="900"/>
+ <usWidthClass value="6"/>
+ <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="100"/>
+ <yStrikeoutPosition value="303"/>
+ <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="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="250"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="505"/>
+ <sCapHeight value="670"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="762" lsb="-6"/>
+ </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" xMin="50" yMin="-250" xMax="450" yMax="750">
+ <contour>
+ <pt x="50" y="-250" on="1"/>
+ <pt x="50" y="750" on="1"/>
+ <pt x="450" y="750" on="1"/>
+ <pt x="450" y="-250" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-200" on="1"/>
+ <pt x="400" y="-200" on="1"/>
+ <pt x="400" y="700" on="1"/>
+ <pt x="100" y="700" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="-6" yMin="0" xMax="759" yMax="672">
+ <contour>
+ <pt x="542" y="672" on="1"/>
+ <pt x="735" y="121" on="1"/>
+ <pt x="747" y="86" on="0"/>
+ <pt x="759" y="24" on="0"/>
+ <pt x="759" y="0" on="1"/>
+ <pt x="501" y="0" on="1"/>
+ <pt x="421" y="258" on="1"/>
+ <pt x="384" y="458" on="1"/>
+ <pt x="377" y="458" on="1"/>
+ <pt x="244" y="0" on="1"/>
+ <pt x="-6" y="0" on="1"/>
+ <pt x="-6" y="21" on="0"/>
+ <pt x="4" y="87" on="0"/>
+ <pt x="17" y="121" on="1"/>
+ <pt x="233" y="668" on="1"/>
+ </contour>
+ <contour>
+ <pt x="545" y="259" on="1"/>
+ <pt x="545" y="92" on="1"/>
+ <pt x="176" y="92" on="1"/>
+ <pt x="176" y="259" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test Extended Black
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 3.013;NONE;Test-ExtendedBlack
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Extended Black
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 3.013
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Test-ExtendedBlack
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ Test
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Extended Black
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-130"/>
+ <underlineThickness value="100"/>
+ <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>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedThin.ttx b/Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedThin.ttx
new file mode 100644
index 00000000..d9209edd
--- /dev/null
+++ b/Tests/varLib/data/master_no_overwrite_stat/Test-ExtendedThin.ttx
@@ -0,0 +1,243 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <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="3.013"/>
+ <checkSumAdjustment value="0x4af8381f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Fri Feb 17 14:29:44 2023"/>
+ <modified value="Tue Mar 7 12:56:58 2023"/>
+ <xMin value="23"/>
+ <yMin value="-250"/>
+ <xMax value="605"/>
+ <yMax value="750"/>
+ <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="-300"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="633"/>
+ <minLeftSideBearing value="23"/>
+ <minRightSideBearing value="28"/>
+ <xMaxExtent value="605"/>
+ <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="19"/>
+ <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="567"/>
+ <usWeightClass value="100"/>
+ <usWidthClass value="6"/>
+ <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="28"/>
+ <yStrikeoutPosition value="274"/>
+ <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="65"/>
+ <usLastCharIndex value="65"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="250"/>
+ <usWinAscent value="1000"/>
+ <usWinDescent value="300"/>
+ <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="456"/>
+ <sCapHeight value="670"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="A" width="633" lsb="23"/>
+ </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" xMin="50" yMin="-250" xMax="450" yMax="750">
+ <contour>
+ <pt x="50" y="-250" on="1"/>
+ <pt x="50" y="750" on="1"/>
+ <pt x="450" y="750" on="1"/>
+ <pt x="450" y="-250" on="1"/>
+ </contour>
+ <contour>
+ <pt x="100" y="-200" on="1"/>
+ <pt x="400" y="-200" on="1"/>
+ <pt x="400" y="700" on="1"/>
+ <pt x="100" y="700" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="A" xMin="23" yMin="0" xMax="605" yMax="672">
+ <contour>
+ <pt x="339" y="672" on="1"/>
+ <pt x="566" y="100" on="1"/>
+ <pt x="576" y="75" on="0"/>
+ <pt x="595" y="25" on="0"/>
+ <pt x="605" y="0" on="1"/>
+ <pt x="563" y="0" on="1"/>
+ <pt x="384" y="453" on="1"/>
+ <pt x="320" y="626" on="1"/>
+ <pt x="317" y="626" on="1"/>
+ <pt x="62" y="0" on="1"/>
+ <pt x="23" y="0" on="1"/>
+ <pt x="34" y="25" on="0"/>
+ <pt x="54" y="75" on="0"/>
+ <pt x="64" y="100" on="1"/>
+ <pt x="297" y="668" on="1"/>
+ </contour>
+ <contour>
+ <pt x="491" y="250" on="1"/>
+ <pt x="491" y="217" on="1"/>
+ <pt x="139" y="217" on="1"/>
+ <pt x="139" y="250" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Test Extended Thin
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 3.013;NONE;Test-ExtendedThin
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Test Extended Thin
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 3.013
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ Test-ExtendedThin
+ </namerecord>
+ <namerecord nameID="16" platformID="3" platEncID="1" langID="0x409">
+ Test
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Extended Thin
+ </namerecord>
+ </name>
+
+ <post>
+ <formatType value="2.0"/>
+ <italicAngle value="0.0"/>
+ <underlinePosition value="-130"/>
+ <underlineThickness value="28"/>
+ <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>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Bold.ttx b/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Bold.ttx
new file mode 100644
index 00000000..410489dc
--- /dev/null
+++ b/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Bold.ttx
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.41">
+
+ <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="e"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0xaa7fc0dd"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Nov 21 11:49:03 2018"/>
+ <modified value="Wed Aug 2 11:47:17 2023"/>
+ <xMin value="-64"/>
+ <yMin value="-350"/>
+ <xMax value="608"/>
+ <yMax value="812"/>
+ <macStyle value="00000000 00000001"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="950"/>
+ <descent value="-250"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="-64"/>
+ <minRightSideBearing value="-63"/>
+ <xMaxExtent value="608"/>
+ <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>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="3"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="580"/>
+ <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 00100000"/>
+ <usFirstCharIndex value="97"/>
+ <usLastCharIndex value="101"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="950"/>
+ <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="4"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sparse CFF
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Bold
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.000;NONE;SparseCFF-Bold
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sparse CFF Bold
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SparseCFF-Bold
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x65" name="e"/><!-- LATIN SMALL LETTER E -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x65" name="e"/><!-- LATIN SMALL LETTER E -->
+ </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="SparseCFF-Bold">
+ <version value="0.0"/>
+ <Notice value=""/>
+ <Copyright value=""/>
+ <FullName value="Sparse CFF Bold"/>
+ <FamilyName value="Sparse CFF"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-100"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="-64 -350 608 812"/>
+ <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="600"/>
+ <nominalWidthX value="0"/>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ 500 50 -350 rmoveto
+ 500 0 rlineto
+ 0 1100 rlineto
+ -500 0 rlineto
+ 50 -950 rmoveto
+ 0 900 rlineto
+ 300 0 rlineto
+ 0 -900 rlineto
+ endchar
+ </CharString>
+ <CharString name="a">
+ 468 -1 rmoveto
+ -21 435 rlineto
+ -233 70 rlineto
+ -205 -76 rlineto
+ 27 -147 rlineto
+ 172 60 rlineto
+ 96 -38 rlineto
+ 3 -304 rlineto
+ 71 264 rmoveto
+ -352 -23 rlineto
+ 3 -218 rlineto
+ 139 -34 rlineto
+ 221 83 rlineto
+ -6 78 rlineto
+ -182 -47 rlineto
+ -38 31 rlineto
+ 2 46 rlineto
+ 216 5 rlineto
+ endchar
+ </CharString>
+ <CharString name="e">
+ 197 229 rmoveto
+ 404 -4 rlineto
+ -5 79 rlineto
+ -282 244 rlineto
+ -305 -286 rlineto
+ 179 -280 rlineto
+ 340 18 rlineto
+ -4 184 rlineto
+ -280 -54 rlineto
+ -27 134 rlineto
+ 84 96 rlineto
+ 103 -67 rlineto
+ -209 6 rlineto
+ endchar
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="e" 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=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>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="800" lsb="200"/>
+ <mtx name="a" width="600" lsb="9"/>
+ <mtx name="e" width="600" lsb="9"/>
+ </hmtx>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Medium.ttx b/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Medium.ttx
new file mode 100644
index 00000000..1b583bd3
--- /dev/null
+++ b/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Medium.ttx
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.41">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="e"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0x263f439"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Nov 21 11:49:03 2018"/>
+ <modified value="Wed Aug 2 11:47:17 2023"/>
+ <xMin value="40"/>
+ <yMin value="-18"/>
+ <xMax value="576"/>
+ <yMax value="513"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <maxp>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="2"/>
+ </maxp>
+
+ <CFF>
+ <major value="1"/>
+ <minor value="0"/>
+ <CFFFont name="LayerFont-Regular">
+ <version value="0.0"/>
+ <Notice value=""/>
+ <Copyright value=""/>
+ <FullName value="Layer Font Regular"/>
+ <FamilyName value="Layer Font"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-75"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="40 -18 576 513"/>
+ <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="500"/>
+ <nominalWidthX value="500"/>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ endchar
+ </CharString>
+ <CharString name="e">
+ 100 126 203 rmoveto
+ 450 -4 rlineto
+ -5 106 rlineto
+ -255 208 rlineto
+ -276 -252 rlineto
+ 148 -279 rlineto
+ 338 63 rlineto
+ -19 112 rlineto
+ -243 -41 rlineto
+ -84 148 rlineto
+ 138 123 rlineto
+ 78 -90 rlineto
+ -271 1 rlineto
+ endchar
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF>
+
+ <hmtx>
+ <mtx name=".notdef" width="65535" lsb="0"/>
+ <mtx name="e" width="600" lsb="40"/>
+ </hmtx>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Regular.ttx b/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Regular.ttx
new file mode 100644
index 00000000..a4dda26f
--- /dev/null
+++ b/Tests/varLib/data/master_sparse_cff2_empty/SparseCFF-Regular.ttx
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.41">
+
+ <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="e"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0xbfef2bdd"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Wed Nov 21 11:49:03 2018"/>
+ <modified value="Wed Aug 2 11:47:17 2023"/>
+ <xMin value="-37"/>
+ <yMin value="-250"/>
+ <xMax value="582"/>
+ <yMax value="750"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="950"/>
+ <descent value="-250"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="-37"/>
+ <minRightSideBearing value="-50"/>
+ <xMaxExtent value="582"/>
+ <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>
+ <tableVersion value="0x5000"/>
+ <numGlyphs value="3"/>
+ </maxp>
+
+ <OS_2>
+ <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+ will be recalculated by the compiler -->
+ <version value="4"/>
+ <xAvgCharWidth value="580"/>
+ <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 01000000"/>
+ <usFirstCharIndex value="97"/>
+ <usLastCharIndex value="101"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="950"/>
+ <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="4"/>
+ </OS_2>
+
+ <name>
+ <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+ Sparse CFF
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.000;NONE;SparseCFF-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sparse CFF Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SparseCFF-Regular
+ </namerecord>
+ </name>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x65" name="e"/><!-- LATIN SMALL LETTER E -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x65" name="e"/><!-- LATIN SMALL LETTER E -->
+ </cmap_format_4>
+ </cmap>
+
+ <post>
+ <formatType value="3.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"/>
+ </post>
+
+ <CFF>
+ <major value="1"/>
+ <minor value="0"/>
+ <CFFFont name="SparseCFF-Regular">
+ <version value="0.0"/>
+ <Notice value=""/>
+ <Copyright value=""/>
+ <FullName value="Sparse CFF Regular"/>
+ <FamilyName value="Sparse CFF"/>
+ <isFixedPitch value="0"/>
+ <ItalicAngle value="0"/>
+ <UnderlinePosition value="-75"/>
+ <UnderlineThickness value="50"/>
+ <PaintType value="0"/>
+ <CharstringType value="2"/>
+ <FontMatrix value="0.001 0 0 0.001 0 0"/>
+ <FontBBox value="-37 -250 582 750"/>
+ <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="600"/>
+ <nominalWidthX value="0"/>
+ </Private>
+ <CharStrings>
+ <CharString name=".notdef">
+ 500 50 -250 rmoveto
+ 400 0 rlineto
+ 0 1000 rlineto
+ -400 0 rlineto
+ 50 -950 rmoveto
+ 0 900 rlineto
+ 300 0 rlineto
+ 0 -900 rlineto
+ endchar
+ </CharString>
+ <CharString name="a">
+ 468 -1 rmoveto
+ -21 435 rlineto
+ -233 70 rlineto
+ -205 -76 rlineto
+ 27 -91 rlineto
+ 172 60 rlineto
+ 155 -40 rlineto
+ 3 -360 rlineto
+ 12 266 rmoveto
+ -352 -23 rlineto
+ 3 -218 rlineto
+ 139 -34 rlineto
+ 221 83 rlineto
+ -6 63 rlineto
+ -222 -60 rlineto
+ -75 52 rlineto
+ 2 46 rlineto
+ 294 35 rlineto
+ endchar
+ </CharString>
+ <CharString name="e">
+ 127 228 rmoveto
+ 449 -2 rlineto
+ -5 79 rlineto
+ -255 208 rlineto
+ -276 -252 rlineto
+ 148 -279 rlineto
+ 338 63 rlineto
+ -17 84 rlineto
+ -280 -54 rlineto
+ -82 188 rlineto
+ 170 153 rlineto
+ 163 -124 rlineto
+ -355 6 rlineto
+ endchar
+ </CharString>
+ </CharStrings>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF>
+
+ <GDEF>
+ <Version value="0x00010000"/>
+ <GlyphClassDef>
+ <ClassDef glyph="e" 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=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>
+ </GSUB>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="a" width="600" lsb="9"/>
+ <mtx name="e" width="600" lsb="40"/>
+ </hmtx>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx
new file mode 100644
index 00000000..14e64a75
--- /dev/null
+++ b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master1.ttx
@@ -0,0 +1,312 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.7">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="uni0020"/>
+ <GlyphID id="2" name="uni0061"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.001"/>
+ <checkSumAdjustment value="0xd723fbc6"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Tue Feb 28 16:48:24 2017"/>
+ <modified value="Tue Feb 28 16:48:24 2017"/>
+ <xMin value="5"/>
+ <yMin value="-115"/>
+ <xMax value="653"/>
+ <yMax value="750"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="918"/>
+ <descent value="-335"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="663"/>
+ <minLeftSideBearing value="5"/>
+ <minRightSideBearing value="7"/>
+ <xMaxExtent value="653"/>
+ <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>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="3"/>
+ <maxPoints value="60"/>
+ <maxContours value="4"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="1"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="1"/>
+ <maxSizeOfInstructions value="5"/>
+ <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="506"/>
+ <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="284"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="4"/>
+ <bWeight value="6"/>
+ <bProportion value="3"/>
+ <bContrast value="5"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="5"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000011"/>
+ <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="32"/>
+ <usLastCharIndex value="97"/>
+ <sTypoAscender value="730"/>
+ <sTypoDescender value="-270"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="918"/>
+ <usWinDescent value="335"/>
+ <ulCodePageRange1 value="00100000 00000000 00000000 00000011"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="474"/>
+ <sCapHeight value="677"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="640" lsb="80"/>
+ <mtx name="uni0020" width="234" lsb="0"/>
+ <mtx name="uni0061" width="508" lsb="46"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="uni0020"/><!-- SPACE -->
+ <map code="0x61" name="uni0061"/><!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="uni0020"/><!-- SPACE -->
+ <map code="0x61" name="uni0061"/><!-- LATIN SMALL 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" xMin="80" yMin="0" xMax="560" yMax="670">
+ <contour>
+ <pt x="80" y="0" on="1"/>
+ <pt x="500" y="670" on="1"/>
+ <pt x="560" y="670" on="1"/>
+ <pt x="140" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="560" y="0" on="1"/>
+ <pt x="500" y="0" on="1"/>
+ <pt x="80" y="670" on="1"/>
+ <pt x="140" y="670" on="1"/>
+ </contour>
+ <contour>
+ <pt x="140" y="50" on="1"/>
+ <pt x="500" y="50" on="1"/>
+ <pt x="500" y="620" on="1"/>
+ <pt x="140" y="620" on="1"/>
+ </contour>
+ <contour>
+ <pt x="80" y="0" on="1"/>
+ <pt x="80" y="670" on="1"/>
+ <pt x="560" y="670" on="1"/>
+ <pt x="560" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="uni0020"/><!-- contains no outline data -->
+
+ <TTGlyph name="uni0061" xMin="46" yMin="-13" xMax="501" yMax="487">
+ <contour>
+ <pt x="46" y="102" on="1"/>
+ <pt x="46" y="154" on="0"/>
+ <pt x="110" y="225" on="0"/>
+ <pt x="210" y="262" on="1"/>
+ <pt x="242" y="273" on="0"/>
+ <pt x="328" y="297" on="0"/>
+ <pt x="365" y="304" on="1"/>
+ <pt x="365" y="268" on="1"/>
+ <pt x="331" y="261" on="0"/>
+ <pt x="254" y="237" on="0"/>
+ <pt x="231" y="228" on="1"/>
+ <pt x="164" y="202" on="0"/>
+ <pt x="131" y="148" on="0"/>
+ <pt x="131" y="126" on="1"/>
+ <pt x="131" y="86" on="0"/>
+ <pt x="178" y="52" on="0"/>
+ <pt x="212" y="52" on="1"/>
+ <pt x="238" y="52" on="0"/>
+ <pt x="283" y="76" on="0"/>
+ <pt x="330" y="110" on="1"/>
+ <pt x="350" y="125" on="1"/>
+ <pt x="364" y="104" on="1"/>
+ <pt x="335" y="75" on="1"/>
+ <pt x="290" y="30" on="0"/>
+ <pt x="226" y="-13" on="0"/>
+ <pt x="180" y="-13" on="1"/>
+ <pt x="125" y="-13" on="0"/>
+ <pt x="46" y="50" on="0"/>
+ </contour>
+ <contour>
+ <pt x="325" y="92" on="1"/>
+ <pt x="325" y="320" on="1"/>
+ <pt x="325" y="394" on="0"/>
+ <pt x="280" y="442" on="0"/>
+ <pt x="231" y="442" on="1"/>
+ <pt x="214" y="442" on="0"/>
+ <pt x="169" y="435" on="0"/>
+ <pt x="141" y="424" on="1"/>
+ <pt x="181" y="455" on="1"/>
+ <pt x="155" y="369" on="1"/>
+ <pt x="148" y="347" on="0"/>
+ <pt x="124" y="324" on="0"/>
+ <pt x="104" y="324" on="1"/>
+ <pt x="62" y="324" on="0"/>
+ <pt x="59" y="364" on="1"/>
+ <pt x="73" y="421" on="0"/>
+ <pt x="177" y="487" on="0"/>
+ <pt x="252" y="487" on="1"/>
+ <pt x="329" y="487" on="0"/>
+ <pt x="405" y="408" on="0"/>
+ <pt x="405" y="314" on="1"/>
+ <pt x="405" y="102" on="1"/>
+ <pt x="405" y="68" on="0"/>
+ <pt x="425" y="41" on="0"/>
+ <pt x="442" y="41" on="1"/>
+ <pt x="455" y="41" on="0"/>
+ <pt x="473" y="53" on="0"/>
+ <pt x="481" y="63" on="1"/>
+ <pt x="501" y="41" on="1"/>
+ <pt x="469" y="-10" on="0"/>
+ <pt x="416" y="-10" on="1"/>
+ <pt x="375" y="-10" on="0"/>
+ <pt x="325" y="46" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <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-Master1
+ </namerecord>
+ <namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
+ Frank Grießhammer
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Master 1
+ </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="uni0020"/>
+ <psName name="uni0061"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010003"/>
+ <GlyphClassDef>
+ <ClassDef glyph="uni0061" class="1"/>
+ </GlyphClassDef>
+ </GDEF>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx
new file mode 100644
index 00000000..1559071a
--- /dev/null
+++ b/Tests/varLib/data/master_ttx_drop_oncurves/TestFamily-Master2.ttx
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.7">
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="uni0020"/>
+ <GlyphID id="2" name="uni0061"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="1.001"/>
+ <checkSumAdjustment value="0x4b3253f0"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Tue Feb 28 16:48:24 2017"/>
+ <modified value="Tue Feb 28 16:48:24 2017"/>
+ <xMin value="10"/>
+ <yMin value="-115"/>
+ <xMax value="665"/>
+ <yMax value="731"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="918"/>
+ <descent value="-335"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="680"/>
+ <minLeftSideBearing value="10"/>
+ <minRightSideBearing value="-8"/>
+ <xMaxExtent value="665"/>
+ <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>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="3"/>
+ <maxPoints value="60"/>
+ <maxContours value="4"/>
+ <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="531"/>
+ <usWeightClass value="900"/>
+ <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="292"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="4"/>
+ <bWeight value="9"/>
+ <bProportion value="3"/>
+ <bContrast value="5"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="5"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000011"/>
+ <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="32"/>
+ <usLastCharIndex value="97"/>
+ <sTypoAscender value="730"/>
+ <sTypoDescender value="-270"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="918"/>
+ <usWinDescent value="335"/>
+ <ulCodePageRange1 value="00100000 00000000 00000000 00000011"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="487"/>
+ <sCapHeight value="677"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="640" lsb="80"/>
+ <mtx name="uni0020" width="206" lsb="0"/>
+ <mtx name="uni0061" width="540" lsb="25"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="uni0020"/><!-- SPACE -->
+ <map code="0x61" name="uni0061"/><!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="uni0020"/><!-- SPACE -->
+ <map code="0x61" name="uni0061"/><!-- LATIN SMALL 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" xMin="80" yMin="0" xMax="560" yMax="652">
+ <contour>
+ <pt x="80" y="0" on="1"/>
+ <pt x="480" y="652" on="1"/>
+ <pt x="560" y="652" on="1"/>
+ <pt x="160" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="560" y="0" on="1"/>
+ <pt x="480" y="0" on="1"/>
+ <pt x="80" y="652" on="1"/>
+ <pt x="160" y="652" on="1"/>
+ </contour>
+ <contour>
+ <pt x="150" y="60" on="1"/>
+ <pt x="490" y="60" on="1"/>
+ <pt x="490" y="592" on="1"/>
+ <pt x="150" y="592" on="1"/>
+ </contour>
+ <contour>
+ <pt x="80" y="0" on="1"/>
+ <pt x="80" y="652" on="1"/>
+ <pt x="560" y="652" on="1"/>
+ <pt x="560" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="uni0020"/><!-- contains no outline data -->
+
+ <TTGlyph name="uni0061" xMin="25" yMin="-16" xMax="548" yMax="503">
+ <contour>
+ <pt x="25" y="111" on="1"/>
+ <pt x="25" y="170" on="0"/>
+ <pt x="108" y="253" on="0"/>
+ <pt x="230" y="285" on="1"/>
+ <pt x="261" y="293" on="0"/>
+ <pt x="356" y="318" on="0"/>
+ <pt x="391" y="327" on="1"/>
+ <pt x="391" y="283" on="1"/>
+ <pt x="355" y="273" on="0"/>
+ <pt x="284" y="254" on="0"/>
+ <pt x="262" y="243" on="1"/>
+ <pt x="241" y="233" on="0"/>
+ <pt x="197" y="184" on="0"/>
+ <pt x="197" y="144" on="1"/>
+ <pt x="197" y="107" on="0"/>
+ <pt x="227" y="71" on="0"/>
+ <pt x="249" y="71" on="1"/>
+ <pt x="259" y="71" on="0"/>
+ <pt x="281" y="81" on="0"/>
+ <pt x="296" y="92" on="1"/>
+ <pt x="344" y="128" on="1"/>
+ <pt x="353" y="116" on="1"/>
+ <pt x="306" y="64" on="1"/>
+ <pt x="273" y="28" on="0"/>
+ <pt x="213" y="-16" on="0"/>
+ <pt x="155" y="-16" on="1"/>
+ <pt x="96" y="-16" on="0"/>
+ <pt x="25" y="52" on="0"/>
+ </contour>
+ <contour>
+ <pt x="291" y="78" on="1"/>
+ <pt x="291" y="337" on="1"/>
+ <pt x="291" y="401" on="0"/>
+ <pt x="262" y="449" on="0"/>
+ <pt x="215" y="449" on="1"/>
+ <pt x="196" y="449" on="0"/>
+ <pt x="154" y="444" on="0"/>
+ <pt x="120" y="436" on="1"/>
+ <pt x="200" y="478" on="1"/>
+ <pt x="200" y="415" on="1"/>
+ <pt x="200" y="354" on="0"/>
+ <pt x="150" y="303" on="0"/>
+ <pt x="118" y="303" on="1"/>
+ <pt x="57" y="303" on="0"/>
+ <pt x="42" y="357" on="1"/>
+ <pt x="42" y="422" on="0"/>
+ <pt x="165" y="503" on="0"/>
+ <pt x="286" y="503" on="1"/>
+ <pt x="390" y="503" on="0"/>
+ <pt x="475" y="412" on="0"/>
+ <pt x="475" y="309" on="1"/>
+ <pt x="475" y="80" on="1"/>
+ <pt x="475" y="72" on="0"/>
+ <pt x="484" y="63" on="0"/>
+ <pt x="492" y="63" on="1"/>
+ <pt x="498" y="63" on="0"/>
+ <pt x="510" y="72" on="0"/>
+ <pt x="519" y="85" on="1"/>
+ <pt x="548" y="69" on="1"/>
+ <pt x="515" y="-16" on="0"/>
+ <pt x="414" y="-16" on="1"/>
+ <pt x="359" y="-16" on="0"/>
+ <pt x="300" y="33" on="0"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ </glyf>
+
+ <name>
+ <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-Master2
+ </namerecord>
+ <namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
+ Frank Grießhammer
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Master 2
+ </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="dollar.nostroke"/>
+ <psName name="uni0020"/>
+ <psName name="uni0061"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010003"/>
+ <GlyphClassDef>
+ <ClassDef glyph="uni0061" class="1"/>
+ </GlyphClassDef>
+ </GDEF>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx b/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx
index 29c5bb31..5de73115 100644
--- a/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx
+++ b/Tests/varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx
@@ -761,6 +761,7 @@
</STAT>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.5" to="-0.7283"/>
diff --git a/Tests/varLib/data/master_ttx_varfont_ttf/SparseMasters-VF.ttx b/Tests/varLib/data/master_ttx_varfont_ttf/SparseMasters-VF.ttx
new file mode 100644
index 00000000..819b3441
--- /dev/null
+++ b/Tests/varLib/data/master_ttx_varfont_ttf/SparseMasters-VF.ttx
@@ -0,0 +1,501 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
+
+ <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="e"/>
+ <GlyphID id="3" name="edotabove"/>
+ <GlyphID id="4" name="s"/>
+ <GlyphID id="5" name="dotabovecomb"/>
+ </GlyphOrder>
+
+ <head>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="1.0"/>
+ <fontRevision value="0.0"/>
+ <checkSumAdjustment value="0x193b520f"/>
+ <magicNumber value="0x5f0f3cf5"/>
+ <flags value="00000000 00000011"/>
+ <unitsPerEm value="1000"/>
+ <created value="Thu Apr 6 00:27:50 2023"/>
+ <modified value="Thu Apr 6 00:27:50 2023"/>
+ <xMin value="-37"/>
+ <yMin value="-250"/>
+ <xMax value="582"/>
+ <yMax value="750"/>
+ <macStyle value="00000000 00000000"/>
+ <lowestRecPPEM value="6"/>
+ <fontDirectionHint value="2"/>
+ <indexToLocFormat value="0"/>
+ <glyphDataFormat value="0"/>
+ </head>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="950"/>
+ <descent value="-250"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="600"/>
+ <minLeftSideBearing value="-37"/>
+ <minRightSideBearing value="-50"/>
+ <xMaxExtent value="582"/>
+ <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="6"/>
+ </hhea>
+
+ <maxp>
+ <!-- Most of this table will be recalculated by the compiler -->
+ <tableVersion value="0x10000"/>
+ <numGlyphs value="6"/>
+ <maxPoints value="18"/>
+ <maxContours value="2"/>
+ <maxCompositePoints value="17"/>
+ <maxCompositeContours value="2"/>
+ <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="580"/>
+ <usWeightClass value="350"/>
+ <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 01000101"/>
+ <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="97"/>
+ <usLastCharIndex value="775"/>
+ <sTypoAscender value="750"/>
+ <sTypoDescender value="-250"/>
+ <sTypoLineGap value="200"/>
+ <usWinAscent value="950"/>
+ <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="500" lsb="50"/>
+ <mtx name="a" width="600" lsb="9"/>
+ <mtx name="dotabovecomb" width="0" lsb="-37"/>
+ <mtx name="e" width="600" lsb="40"/>
+ <mtx name="edotabove" width="600" lsb="40"/>
+ <mtx name="s" width="600" lsb="25"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x65" name="e"/><!-- LATIN SMALL LETTER E -->
+ <map code="0x73" name="s"/><!-- LATIN SMALL LETTER S -->
+ <map code="0x117" name="edotabove"/><!-- LATIN SMALL LETTER E WITH DOT ABOVE -->
+ <map code="0x307" name="dotabovecomb"/><!-- COMBINING DOT ABOVE -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x61" name="a"/><!-- LATIN SMALL LETTER A -->
+ <map code="0x65" name="e"/><!-- LATIN SMALL LETTER E -->
+ <map code="0x73" name="s"/><!-- LATIN SMALL LETTER S -->
+ <map code="0x117" name="edotabove"/><!-- LATIN SMALL LETTER E WITH DOT ABOVE -->
+ <map code="0x307" name="dotabovecomb"/><!-- COMBINING DOT ABOVE -->
+ </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="-250" xMax="450" yMax="750">
+ <contour>
+ <pt x="50" y="750" on="1"/>
+ <pt x="50" y="-250" on="1"/>
+ <pt x="450" y="-250" on="1"/>
+ <pt x="450" y="750" on="1"/>
+ </contour>
+ <contour>
+ <pt x="400" y="-200" on="1"/>
+ <pt x="100" y="-200" on="1"/>
+ <pt x="100" y="700" on="1"/>
+ <pt x="400" y="700" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="a" xMin="9" yMin="-12" xMax="468" yMax="504">
+ <contour>
+ <pt x="447" y="434" on="1"/>
+ <pt x="468" y="-1" on="1"/>
+ <pt x="366" y="-3" on="1"/>
+ <pt x="363" y="357" on="1"/>
+ <pt x="208" y="397" on="1"/>
+ <pt x="36" y="337" on="1"/>
+ <pt x="9" y="428" on="1"/>
+ <pt x="214" y="504" on="1"/>
+ </contour>
+ <contour>
+ <pt x="26" y="240" on="1"/>
+ <pt x="378" y="263" on="1"/>
+ <pt x="382" y="207" on="1"/>
+ <pt x="88" y="172" on="1"/>
+ <pt x="86" y="126" on="1"/>
+ <pt x="161" y="74" on="1"/>
+ <pt x="383" y="134" on="1"/>
+ <pt x="389" y="71" on="1"/>
+ <pt x="168" y="-12" on="1"/>
+ <pt x="29" y="22" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="dotabovecomb" xMin="-37" yMin="501" xMax="50" yMax="597">
+ <contour>
+ <pt x="-37" y="503" on="1"/>
+ <pt x="-21" y="597" on="1"/>
+ <pt x="50" y="589" on="1"/>
+ <pt x="41" y="501" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="e" xMin="40" yMin="-18" xMax="576" yMax="513">
+ <contour>
+ <pt x="576" y="226" on="1"/>
+ <pt x="127" y="228" on="1"/>
+ <pt x="125" y="298" on="1"/>
+ <pt x="480" y="292" on="1"/>
+ <pt x="317" y="416" on="1"/>
+ <pt x="147" y="263" on="1"/>
+ <pt x="229" y="75" on="1"/>
+ <pt x="509" y="129" on="1"/>
+ <pt x="526" y="45" on="1"/>
+ <pt x="188" y="-18" on="1"/>
+ <pt x="40" y="261" on="1"/>
+ <pt x="316" y="513" on="1"/>
+ <pt x="571" y="305" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="edotabove" xMin="40" yMin="-18" xMax="576" yMax="693">
+ <component glyphName="e" x="0" y="0" flags="0x204"/>
+ <component glyphName="dotabovecomb" x="313" y="96" flags="0x4"/>
+ </TTGlyph>
+
+ <TTGlyph name="s" xMin="25" yMin="-13" xMax="582" yMax="530">
+ <contour>
+ <pt x="324" y="530" on="1"/>
+ <pt x="559" y="459" on="1"/>
+ <pt x="539" y="376" on="1"/>
+ <pt x="326" y="442" on="1"/>
+ <pt x="213" y="366" on="1"/>
+ <pt x="582" y="174" on="1"/>
+ <pt x="304" y="-13" on="1"/>
+ <pt x="25" y="83" on="1"/>
+ <pt x="53" y="174" on="1"/>
+ <pt x="282" y="76" on="1"/>
+ <pt x="427" y="155" on="1"/>
+ <pt x="38" y="343" on="1"/>
+ </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">
+ Sparse Masters
+ </namerecord>
+ <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+ Regular
+ </namerecord>
+ <namerecord nameID="3" platformID="3" platEncID="1" langID="0x409">
+ 0.000;NONE;SparseMasters-Regular
+ </namerecord>
+ <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+ Sparse Masters Regular
+ </namerecord>
+ <namerecord nameID="5" platformID="3" platEncID="1" langID="0x409">
+ Version 0.000
+ </namerecord>
+ <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+ SparseMasters-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="-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="edotabove"/>
+ <psName name="dotabovecomb"/>
+ </extraNames>
+ </post>
+
+ <HVAR>
+ <Version value="0x00010000"/>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=1 -->
+ <!-- RegionCount=3 -->
+ <Region index="0">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="0.36365"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ <Region index="1">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.36365"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ <Region index="2">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ </VarRegionList>
+ <!-- VarDataCount=1 -->
+ <VarData index="0">
+ <!-- ItemCount=1 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=0 -->
+ <Item index="0" value="[]"/>
+ </VarData>
+ </VarStore>
+ <AdvWidthMap>
+ <Map glyph=".notdef" outer="0" inner="0"/>
+ <Map glyph="a" outer="0" inner="0"/>
+ <Map glyph="dotabovecomb" outer="0" inner="0"/>
+ <Map glyph="e" outer="0" inner="0"/>
+ <Map glyph="edotabove" outer="0" inner="0"/>
+ <Map glyph="s" outer="0" inner="0"/>
+ </AdvWidthMap>
+ </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>350.0</MinValue>
+ <DefaultValue>350.0</DefaultValue>
+ <MaxValue>625.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="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="-59" y="2"/>
+ <delta pt="3" x="-59" y="-54"/>
+ <delta pt="4" x="0" y="-56"/>
+ <delta pt="5" x="0" y="-56"/>
+ <delta pt="6" x="0" y="0"/>
+ <delta pt="7" x="0" y="0"/>
+ <delta pt="8" x="0" y="0"/>
+ <delta pt="9" x="0" y="0"/>
+ <delta pt="10" x="-1" y="-23"/>
+ <delta pt="11" x="77" y="7"/>
+ <delta pt="12" x="77" y="7"/>
+ <delta pt="13" x="40" y="28"/>
+ <delta pt="14" x="0" y="15"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="0" y="0"/>
+ <delta pt="17" x="0" y="0"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="0"/>
+ <delta pt="20" x="0" y="0"/>
+ <delta pt="21" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="dotabovecomb">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-27" y="-20"/>
+ <delta pt="1" x="-8" y="28"/>
+ <delta pt="2" x="13" y="16"/>
+ <delta pt="3" x="17" y="-13"/>
+ <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"/>
+ <delta pt="0" x="0" y="-27"/>
+ <delta pt="1" x="-1" y="-25"/>
+ <delta pt="2" x="0" y="0"/>
+ <delta pt="3" x="-84" y="5"/>
+ <delta pt="4" x="1" y="-29"/>
+ <delta pt="5" x="33" y="1"/>
+ <delta pt="6" x="35" y="41"/>
+ <delta pt="7" x="-2" y="28"/>
+ <delta pt="8" x="0" y="0"/>
+ <delta pt="9" x="0" y="0"/>
+ <delta pt="10" x="0" y="0"/>
+ <delta pt="11" x="0" y="0"/>
+ <delta pt="12" x="0" y="0"/>
+ <delta pt="13" x="0" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="0" y="0"/>
+ </tuple>
+ <tuple>
+ <coord axis="wght" min="0.36365" value="1.0" max="1.0"/>
+ <delta pt="0" x="25" y="-1"/>
+ <delta pt="1" x="70" y="1"/>
+ <delta pt="2" x="70" y="1"/>
+ <delta pt="3" x="-76" y="1"/>
+ <delta pt="4" x="-16" y="-56"/>
+ <delta pt="5" x="70" y="1"/>
+ <delta pt="6" x="15" y="55"/>
+ <delta pt="7" x="15" y="55"/>
+ <delta pt="8" x="2" y="-45"/>
+ <delta pt="9" x="0" y="0"/>
+ <delta pt="10" x="-31" y="1"/>
+ <delta pt="11" x="-2" y="35"/>
+ <delta pt="12" x="25" y="-1"/>
+ <delta pt="13" x="0" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ <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"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="0" y="0"/>
+ <delta pt="2" x="-2" y="-40"/>
+ <delta pt="3" x="-2" y="-40"/>
+ <delta pt="4" x="55" y="-9"/>
+ <delta pt="5" x="26" y="-33"/>
+ <delta pt="6" x="-20" y="-45"/>
+ <delta pt="7" x="-18" y="-4"/>
+ <delta pt="8" x="-27" y="52"/>
+ <delta pt="9" x="-61" y="43"/>
+ <delta pt="10" x="-80" y="-6"/>
+ <delta pt="11" x="-22" y="55"/>
+ <delta pt="12" x="0" y="0"/>
+ <delta pt="13" x="0" y="0"/>
+ <delta pt="14" x="0" y="0"/>
+ <delta pt="15" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ </gvar>
+
+</ttFont>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/fontinfo.plist b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/fontinfo.plist
new file mode 100644
index 00000000..3898ecc8
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/fontinfo.plist
@@ -0,0 +1,20 @@
+<?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>750</integer>
+ <key>capHeight</key>
+ <integer>700</integer>
+ <key>descender</key>
+ <integer>-250</integer>
+ <key>familyName</key>
+ <string>Sparse Masters</string>
+ <key>styleName</key>
+ <string>Bold</string>
+ <key>unitsPerEm</key>
+ <integer>1000</integer>
+ <key>xHeight</key>
+ <integer>500</integer>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/_notdef.glif b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/_notdef.glif
new file mode 100644
index 00000000..5d3ca4d6
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/_notdef.glif
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name=".notdef" format="2">
+ <advance width="500"/>
+ <outline>
+ <contour>
+ <point x="50" y="750" type="line"/>
+ <point x="450" y="750" type="line"/>
+ <point x="450" y="-250" type="line"/>
+ <point x="50" y="-250" type="line"/>
+ </contour>
+ <contour>
+ <point x="400" y="-200" type="line"/>
+ <point x="400" y="700" type="line"/>
+ <point x="100" y="700" type="line"/>
+ <point x="100" y="-200" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/a.glif b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/a.glif
new file mode 100644
index 00000000..0e038d68
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/a.glif
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="a" format="2">
+ <unicode hex="0061"/>
+ <advance width="600"/>
+ <outline>
+ <contour>
+ <point x="447" y="434" type="line"/>
+ <point x="214" y="504" type="line"/>
+ <point x="9" y="428" type="line"/>
+ <point x="36" y="281" type="line"/>
+ <point x="208" y="341" type="line"/>
+ <point x="304" y="303" type="line"/>
+ <point x="307" y="-1" type="line"/>
+ <point x="468" y="-1" type="line"/>
+ </contour>
+ <contour>
+ <point x="26" y="240" type="line"/>
+ <point x="29" y="22" type="line"/>
+ <point x="168" y="-12" type="line"/>
+ <point x="389" y="71" type="line"/>
+ <point x="383" y="149" type="line"/>
+ <point x="201" y="102" type="line"/>
+ <point x="163" y="133" type="line"/>
+ <point x="165" y="179" type="line"/>
+ <point x="381" y="184" type="line"/>
+ <point x="378" y="263" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/contents.plist b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/contents.plist
new file mode 100644
index 00000000..da7e7a78
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/contents.plist
@@ -0,0 +1,18 @@
+<?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>.notdef</key>
+ <string>_notdef.glif</string>
+ <key>a</key>
+ <string>a.glif</string>
+ <key>dotabovecomb</key>
+ <string>dotabovecomb.glif</string>
+ <key>e</key>
+ <string>e.glif</string>
+ <key>edotabove</key>
+ <string>edotabove.glif</string>
+ <key>s</key>
+ <string>s.glif</string>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/dotabovecomb.glif b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/dotabovecomb.glif
new file mode 100644
index 00000000..1c11088c
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/dotabovecomb.glif
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="dotabovecomb" format="2">
+ <unicode hex="0307"/>
+ <outline>
+ <contour>
+ <point x="-64" y="483" type="line"/>
+ <point x="58" y="488" type="line"/>
+ <point x="63" y="605" type="line"/>
+ <point x="-29" y="625" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/e.glif b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/e.glif
new file mode 100644
index 00000000..c78c38f4
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/e.glif
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="e" format="2">
+ <unicode hex="0065"/>
+ <advance width="600"/>
+ <outline>
+ <contour>
+ <point x="601" y="225" type="line"/>
+ <point x="596" y="304" type="line"/>
+ <point x="314" y="548" type="line"/>
+ <point x="9" y="262" type="line"/>
+ <point x="188" y="-18" type="line"/>
+ <point x="528" y="0" type="line"/>
+ <point x="524" y="184" type="line"/>
+ <point x="244" y="130" type="line"/>
+ <point x="217" y="264" type="line"/>
+ <point x="301" y="360" type="line"/>
+ <point x="404" y="293" type="line"/>
+ <point x="195" y="299" type="line"/>
+ <point x="197" y="229" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/edotabove.glif b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/edotabove.glif
new file mode 100644
index 00000000..bf481928
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/edotabove.glif
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="edotabove" format="2">
+ <unicode hex="0117"/>
+ <advance width="600"/>
+ <outline>
+ <component base="e"/>
+ <component base="dotabovecomb" xOffset="307" yOffset="187"/>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/s.glif b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/s.glif
new file mode 100644
index 00000000..ae47e9a9
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/glyphs/s.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="s" format="2">
+ <unicode hex="0073"/>
+ <advance width="600"/>
+ <outline>
+ <contour>
+ <point x="324" y="530" type="line"/>
+ <point x="16" y="398" type="line"/>
+ <point x="347" y="149" type="line"/>
+ <point x="221" y="119" type="line"/>
+ <point x="26" y="226" type="line"/>
+ <point x="7" y="79" type="line"/>
+ <point x="284" y="-58" type="line"/>
+ <point x="608" y="141" type="line"/>
+ <point x="268" y="357" type="line"/>
+ <point x="324" y="402" type="line"/>
+ <point x="537" y="336" type="line"/>
+ <point x="559" y="459" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/layercontents.plist b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/layercontents.plist
new file mode 100644
index 00000000..03e5dde5
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-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/varLib/data/master_ufo/SparseMasters-Bold.ufo/lib.plist b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/lib.plist
new file mode 100644
index 00000000..b0fd5eb2
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/lib.plist
@@ -0,0 +1,15 @@
+<?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>.notdef</string>
+ <string>a</string>
+ <string>e</string>
+ <string>edotabove</string>
+ <string>s</string>
+ <string>dotabovecomb</string>
+ </array>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/metainfo.plist b/Tests/varLib/data/master_ufo/SparseMasters-Bold.ufo/metainfo.plist
new file mode 100644
index 00000000..555d9ce4
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-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>com.github.fonttools.ufoLib</string>
+ <key>formatVersion</key>
+ <integer>3</integer>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/fontinfo.plist b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/fontinfo.plist
new file mode 100644
index 00000000..a8f59388
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/fontinfo.plist
@@ -0,0 +1,20 @@
+<?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>750</integer>
+ <key>capHeight</key>
+ <integer>700</integer>
+ <key>descender</key>
+ <integer>-250</integer>
+ <key>familyName</key>
+ <string>Sparse Masters</string>
+ <key>styleName</key>
+ <string>Medium</string>
+ <key>unitsPerEm</key>
+ <integer>1000</integer>
+ <key>xHeight</key>
+ <integer>500</integer>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/_notdef.glif b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/_notdef.glif
new file mode 100644
index 00000000..5d3ca4d6
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/_notdef.glif
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name=".notdef" format="2">
+ <advance width="500"/>
+ <outline>
+ <contour>
+ <point x="50" y="750" type="line"/>
+ <point x="450" y="750" type="line"/>
+ <point x="450" y="-250" type="line"/>
+ <point x="50" y="-250" type="line"/>
+ </contour>
+ <contour>
+ <point x="400" y="-200" type="line"/>
+ <point x="400" y="700" type="line"/>
+ <point x="100" y="700" type="line"/>
+ <point x="100" y="-200" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/contents.plist b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/contents.plist
new file mode 100644
index 00000000..456fd5de
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/contents.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>.notdef</key>
+ <string>_notdef.glif</string>
+ <key>e</key>
+ <string>e.glif</string>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/e.glif b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/e.glif
new file mode 100644
index 00000000..bf15c1ab
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/glyphs/e.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="e" format="2">
+ <advance width="600"/>
+ <outline>
+ <contour>
+ <point x="576" y="199" type="line"/>
+ <point x="571" y="305" type="line"/>
+ <point x="316" y="513" type="line"/>
+ <point x="40" y="261" type="line"/>
+ <point x="188" y="-18" type="line"/>
+ <point x="526" y="45" type="line"/>
+ <point x="507" y="157" type="line"/>
+ <point x="264" y="116" type="line"/>
+ <point x="180" y="264" type="line"/>
+ <point x="318" y="387" type="line"/>
+ <point x="396" y="297" type="line"/>
+ <point x="125" y="298" type="line"/>
+ <point x="126" y="203" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/layercontents.plist b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/layercontents.plist
new file mode 100644
index 00000000..03e5dde5
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Medium.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/varLib/data/master_ufo/SparseMasters-Medium.ufo/lib.plist b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/lib.plist
new file mode 100644
index 00000000..3326cd65
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/lib.plist
@@ -0,0 +1,11 @@
+<?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>.notdef</string>
+ <string>e</string>
+ </array>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/metainfo.plist b/Tests/varLib/data/master_ufo/SparseMasters-Medium.ufo/metainfo.plist
new file mode 100644
index 00000000..555d9ce4
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Medium.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>com.github.fonttools.ufoLib</string>
+ <key>formatVersion</key>
+ <integer>3</integer>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/fontinfo.plist b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/fontinfo.plist
new file mode 100644
index 00000000..a36990b7
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/fontinfo.plist
@@ -0,0 +1,20 @@
+<?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>750</integer>
+ <key>capHeight</key>
+ <integer>700</integer>
+ <key>descender</key>
+ <integer>-250</integer>
+ <key>familyName</key>
+ <string>Sparse Masters</string>
+ <key>styleName</key>
+ <string>Regular</string>
+ <key>unitsPerEm</key>
+ <integer>1000</integer>
+ <key>xHeight</key>
+ <integer>500</integer>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/_notdef.glif b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/_notdef.glif
new file mode 100644
index 00000000..5d3ca4d6
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/_notdef.glif
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name=".notdef" format="2">
+ <advance width="500"/>
+ <outline>
+ <contour>
+ <point x="50" y="750" type="line"/>
+ <point x="450" y="750" type="line"/>
+ <point x="450" y="-250" type="line"/>
+ <point x="50" y="-250" type="line"/>
+ </contour>
+ <contour>
+ <point x="400" y="-200" type="line"/>
+ <point x="400" y="700" type="line"/>
+ <point x="100" y="700" type="line"/>
+ <point x="100" y="-200" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/a.glif b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/a.glif
new file mode 100644
index 00000000..5dcc9322
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/a.glif
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="a" format="2">
+ <unicode hex="0061"/>
+ <advance width="600"/>
+ <outline>
+ <contour>
+ <point x="447" y="434" type="line"/>
+ <point x="214" y="504" type="line"/>
+ <point x="9" y="428" type="line"/>
+ <point x="36" y="337" type="line"/>
+ <point x="208" y="397" type="line"/>
+ <point x="363" y="357" type="line"/>
+ <point x="366" y="-3" type="line"/>
+ <point x="468" y="-1" type="line"/>
+ </contour>
+ <contour>
+ <point x="26" y="240" type="line"/>
+ <point x="29" y="22" type="line"/>
+ <point x="168" y="-12" type="line"/>
+ <point x="389" y="71" type="line"/>
+ <point x="383" y="134" type="line"/>
+ <point x="161" y="74" type="line"/>
+ <point x="86" y="126" type="line"/>
+ <point x="88" y="172" type="line"/>
+ <point x="382" y="207" type="line"/>
+ <point x="378" y="263" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/contents.plist b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/contents.plist
new file mode 100644
index 00000000..da7e7a78
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/contents.plist
@@ -0,0 +1,18 @@
+<?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>.notdef</key>
+ <string>_notdef.glif</string>
+ <key>a</key>
+ <string>a.glif</string>
+ <key>dotabovecomb</key>
+ <string>dotabovecomb.glif</string>
+ <key>e</key>
+ <string>e.glif</string>
+ <key>edotabove</key>
+ <string>edotabove.glif</string>
+ <key>s</key>
+ <string>s.glif</string>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/dotabovecomb.glif b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/dotabovecomb.glif
new file mode 100644
index 00000000..3abb24fd
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/dotabovecomb.glif
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="dotabovecomb" format="2">
+ <unicode hex="0307"/>
+ <outline>
+ <contour>
+ <point x="-37" y="503" type="line"/>
+ <point x="41" y="501" type="line"/>
+ <point x="50" y="589" type="line"/>
+ <point x="-21" y="597" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/e.glif b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/e.glif
new file mode 100644
index 00000000..52fc2b3c
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/e.glif
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="e" format="2">
+ <unicode hex="0065"/>
+ <advance width="600"/>
+ <outline>
+ <contour>
+ <point x="576" y="226" type="line"/>
+ <point x="571" y="305" type="line"/>
+ <point x="316" y="513" type="line"/>
+ <point x="40" y="261" type="line"/>
+ <point x="188" y="-18" type="line"/>
+ <point x="526" y="45" type="line"/>
+ <point x="509" y="129" type="line"/>
+ <point x="229" y="75" type="line"/>
+ <point x="147" y="263" type="line"/>
+ <point x="317" y="416" type="line"/>
+ <point x="480" y="292" type="line"/>
+ <point x="125" y="298" type="line"/>
+ <point x="127" y="228" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/edotabove.glif b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/edotabove.glif
new file mode 100644
index 00000000..9a6dbc56
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/edotabove.glif
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="edotabove" format="2">
+ <unicode hex="0117"/>
+ <advance width="600"/>
+ <outline>
+ <component base="e"/>
+ <component base="dotabovecomb" xOffset="313" yOffset="96"/>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/s.glif b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/s.glif
new file mode 100644
index 00000000..205b0e3d
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/glyphs/s.glif
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glyph name="s" format="2">
+ <unicode hex="0073"/>
+ <advance width="600"/>
+ <outline>
+ <contour>
+ <point x="324" y="530" type="line"/>
+ <point x="38" y="343" type="line"/>
+ <point x="427" y="155" type="line"/>
+ <point x="282" y="76" type="line"/>
+ <point x="53" y="174" type="line"/>
+ <point x="25" y="83" type="line"/>
+ <point x="304" y="-13" type="line"/>
+ <point x="582" y="174" type="line"/>
+ <point x="213" y="366" type="line"/>
+ <point x="326" y="442" type="line"/>
+ <point x="539" y="376" type="line"/>
+ <point x="559" y="459" type="line"/>
+ </contour>
+ </outline>
+</glyph>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/layercontents.plist b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/layercontents.plist
new file mode 100644
index 00000000..03e5dde5
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-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/varLib/data/master_ufo/SparseMasters-Regular.ufo/lib.plist b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/lib.plist
new file mode 100644
index 00000000..b0fd5eb2
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/lib.plist
@@ -0,0 +1,15 @@
+<?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>.notdef</string>
+ <string>a</string>
+ <string>e</string>
+ <string>edotabove</string>
+ <string>s</string>
+ <string>dotabovecomb</string>
+ </array>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/metainfo.plist b/Tests/varLib/data/master_ufo/SparseMasters-Regular.ufo/metainfo.plist
new file mode 100644
index 00000000..555d9ce4
--- /dev/null
+++ b/Tests/varLib/data/master_ufo/SparseMasters-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>com.github.fonttools.ufoLib</string>
+ <key>formatVersion</key>
+ <integer>3</integer>
+ </dict>
+</plist>
diff --git a/Tests/varLib/data/test_results/Build.ttx b/Tests/varLib/data/test_results/Build.ttx
index c802bf32..144cca5e 100644
--- a/Tests/varLib/data/test_results/Build.ttx
+++ b/Tests/varLib/data/test_results/Build.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.17">
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.42">
<GDEF>
<Version value="0x00010003"/>
@@ -17,7 +17,7 @@
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
- <!-- RegionCount=5 -->
+ <!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
@@ -42,58 +42,29 @@
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
- <Region index="2">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="0.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="3">
- <VarRegionAxis index="0">
- <StartCoord value="-1.0"/>
- <PeakCoord value="-1.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="4">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
- <!-- ItemCount=6 -->
+ <!-- ItemCount=5 -->
<NumShorts value="0"/>
<!-- VarRegionCount=2 -->
<VarRegionIndex index="0" value="0"/>
<VarRegionIndex index="1" value="1"/>
- <Item index="0" value="[0, 0]"/>
- <Item index="1" value="[14, -28]"/>
- <Item index="2" value="[-10, 17]"/>
- <Item index="3" value="[-3, 32]"/>
- <Item index="4" value="[-7, 63]"/>
- <Item index="5" value="[-7, 63]"/>
+ <Item index="0" value="[-10, 17]"/>
+ <Item index="1" value="[-7, 63]"/>
+ <Item index="2" value="[-3, 32]"/>
+ <Item index="3" value="[0, 0]"/>
+ <Item index="4" value="[14, -28]"/>
</VarData>
</VarStore>
+ <AdvWidthMap>
+ <Map glyph=".notdef" outer="0" inner="3"/>
+ <Map glyph="uni0020" outer="0" inner="4"/>
+ <Map glyph="uni0024" outer="0" inner="1"/>
+ <Map glyph="uni0024.nostroke" outer="0" inner="1"/>
+ <Map glyph="uni0041" outer="0" inner="0"/>
+ <Map glyph="uni0061" outer="0" inner="2"/>
+ </AdvWidthMap>
</HVAR>
<MVAR>
@@ -105,7 +76,7 @@
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
- <!-- RegionCount=5 -->
+ <!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
@@ -130,42 +101,6 @@
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
- <Region index="2">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="0.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="3">
- <VarRegionAxis index="0">
- <StartCoord value="-1.0"/>
- <PeakCoord value="-1.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="4">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
diff --git a/Tests/varLib/data/test_results/BuildAvar2.ttx b/Tests/varLib/data/test_results/BuildAvar2.ttx
new file mode 100644
index 00000000..27a41bfb
--- /dev/null
+++ b/Tests/varLib/data/test_results/BuildAvar2.ttx
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.14">
+
+ <avar>
+ <version major="2" minor="0"/>
+ <segment axis="wght">
+ <mapping from="-1.0" to="-1.0"/>
+ <mapping from="-0.6667" to="-0.7969"/>
+ <mapping from="-0.3333" to="-0.5"/>
+ <mapping from="0.0" to="0.0"/>
+ <mapping from="0.2" to="0.18"/>
+ <mapping from="0.4" to="0.38"/>
+ <mapping from="0.6" to="0.61"/>
+ <mapping from="0.8" to="0.79"/>
+ <mapping from="1.0" to="1.0"/>
+ </segment>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=1 -->
+ <!-- RegionCount=1 -->
+ <Region index="0">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="0.38"/>
+ <EndCoord value="0.38"/>
+ </VarRegionAxis>
+ </Region>
+ </VarRegionList>
+ <!-- VarDataCount=1 -->
+ <VarData index="0">
+ <!-- ItemCount=1 -->
+ <NumShorts value="1"/>
+ <!-- VarRegionCount=1 -->
+ <VarRegionIndex index="0" value="0"/>
+ <Item index="0" value="[1638]"/>
+ </VarData>
+ </VarStore>
+ </avar>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx b/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx
index aee6f5ae..bff0993c 100644
--- a/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx
+++ b/Tests/varLib/data/test_results/BuildAvarEmptyAxis.ttx
@@ -2,6 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.14">
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="0.0" to="0.0"/>
diff --git a/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx b/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx
index 799d68f1..f348a5b7 100644
--- a/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx
+++ b/Tests/varLib/data/test_results/BuildAvarIdentityMaps.ttx
@@ -2,6 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.14">
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>
diff --git a/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx b/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx
index 9daa330f..aacd2888 100644
--- a/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx
+++ b/Tests/varLib/data/test_results/BuildAvarSingleAxis.ttx
@@ -2,6 +2,7 @@
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.14">
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>
diff --git a/Tests/varLib/data/test_results/BuildMain.ttx b/Tests/varLib/data/test_results/BuildMain.ttx
index 27d02d1d..3a1bcfd3 100644
--- a/Tests/varLib/data/test_results/BuildMain.ttx
+++ b/Tests/varLib/data/test_results/BuildMain.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.19">
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.42">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
@@ -629,7 +629,7 @@
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
- <!-- RegionCount=5 -->
+ <!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
@@ -654,58 +654,29 @@
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
- <Region index="2">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="0.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="3">
- <VarRegionAxis index="0">
- <StartCoord value="-1.0"/>
- <PeakCoord value="-1.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="4">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
- <!-- ItemCount=6 -->
+ <!-- ItemCount=5 -->
<NumShorts value="0"/>
<!-- VarRegionCount=2 -->
<VarRegionIndex index="0" value="0"/>
<VarRegionIndex index="1" value="1"/>
- <Item index="0" value="[0, 0]"/>
- <Item index="1" value="[14, -28]"/>
- <Item index="2" value="[-10, 17]"/>
- <Item index="3" value="[-3, 32]"/>
- <Item index="4" value="[-7, 63]"/>
- <Item index="5" value="[-7, 63]"/>
+ <Item index="0" value="[-10, 17]"/>
+ <Item index="1" value="[-7, 63]"/>
+ <Item index="2" value="[-3, 32]"/>
+ <Item index="3" value="[0, 0]"/>
+ <Item index="4" value="[14, -28]"/>
</VarData>
</VarStore>
+ <AdvWidthMap>
+ <Map glyph=".notdef" outer="0" inner="3"/>
+ <Map glyph="uni0020" outer="0" inner="4"/>
+ <Map glyph="uni0024" outer="0" inner="1"/>
+ <Map glyph="uni0024.nostroke" outer="0" inner="1"/>
+ <Map glyph="uni0041" outer="0" inner="0"/>
+ <Map glyph="uni0061" outer="0" inner="2"/>
+ </AdvWidthMap>
</HVAR>
<MVAR>
@@ -717,7 +688,7 @@
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=2 -->
- <!-- RegionCount=5 -->
+ <!-- RegionCount=2 -->
<Region index="0">
<VarRegionAxis index="0">
<StartCoord value="-1.0"/>
@@ -742,42 +713,6 @@
<EndCoord value="0.0"/>
</VarRegionAxis>
</Region>
- <Region index="2">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="0.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="3">
- <VarRegionAxis index="0">
- <StartCoord value="-1.0"/>
- <PeakCoord value="-1.0"/>
- <EndCoord value="0.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="4">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- <VarRegionAxis index="1">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
diff --git a/Tests/varLib/data/test_results/DropOnCurves.ttx b/Tests/varLib/data/test_results/DropOnCurves.ttx
new file mode 100644
index 00000000..4bfd36ad
--- /dev/null
+++ b/Tests/varLib/data/test_results/DropOnCurves.ttx
@@ -0,0 +1,498 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont>
+
+ <GlyphOrder>
+ <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+ <GlyphID id="0" name=".notdef"/>
+ <GlyphID id="1" name="uni0020"/>
+ <GlyphID id="2" name="uni0061"/>
+ </GlyphOrder>
+
+ <hhea>
+ <tableVersion value="0x00010000"/>
+ <ascent value="918"/>
+ <descent value="-335"/>
+ <lineGap value="0"/>
+ <advanceWidthMax value="640"/>
+ <minLeftSideBearing value="46"/>
+ <minRightSideBearing value="7"/>
+ <xMaxExtent value="560"/>
+ <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="60"/>
+ <maxContours value="4"/>
+ <maxCompositePoints value="0"/>
+ <maxCompositeContours value="0"/>
+ <maxZones value="1"/>
+ <maxTwilightPoints value="0"/>
+ <maxStorage value="0"/>
+ <maxFunctionDefs value="1"/>
+ <maxInstructionDefs value="0"/>
+ <maxStackElements value="1"/>
+ <maxSizeOfInstructions value="5"/>
+ <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="506"/>
+ <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="284"/>
+ <sFamilyClass value="0"/>
+ <panose>
+ <bFamilyType value="2"/>
+ <bSerifStyle value="4"/>
+ <bWeight value="6"/>
+ <bProportion value="3"/>
+ <bContrast value="5"/>
+ <bStrokeVariation value="4"/>
+ <bArmStyle value="5"/>
+ <bLetterForm value="2"/>
+ <bMidline value="2"/>
+ <bXHeight value="4"/>
+ </panose>
+ <ulUnicodeRange1 value="00000000 00000000 00000000 00000011"/>
+ <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="32"/>
+ <usLastCharIndex value="97"/>
+ <sTypoAscender value="730"/>
+ <sTypoDescender value="-270"/>
+ <sTypoLineGap value="0"/>
+ <usWinAscent value="918"/>
+ <usWinDescent value="335"/>
+ <ulCodePageRange1 value="00100000 00000000 00000000 00000011"/>
+ <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+ <sxHeight value="474"/>
+ <sCapHeight value="677"/>
+ <usDefaultChar value="0"/>
+ <usBreakChar value="32"/>
+ <usMaxContext value="0"/>
+ </OS_2>
+
+ <hmtx>
+ <mtx name=".notdef" width="640" lsb="80"/>
+ <mtx name="uni0020" width="234" lsb="0"/>
+ <mtx name="uni0061" width="508" lsb="46"/>
+ </hmtx>
+
+ <cmap>
+ <tableVersion version="0"/>
+ <cmap_format_4 platformID="0" platEncID="3" language="0">
+ <map code="0x20" name="uni0020"/><!-- SPACE -->
+ <map code="0x61" name="uni0061"/><!-- LATIN SMALL LETTER A -->
+ </cmap_format_4>
+ <cmap_format_4 platformID="3" platEncID="1" language="0">
+ <map code="0x20" name="uni0020"/><!-- SPACE -->
+ <map code="0x61" name="uni0061"/><!-- LATIN SMALL 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" xMin="80" yMin="0" xMax="560" yMax="670">
+ <contour>
+ <pt x="80" y="0" on="1"/>
+ <pt x="500" y="670" on="1"/>
+ <pt x="560" y="670" on="1"/>
+ <pt x="140" y="0" on="1"/>
+ </contour>
+ <contour>
+ <pt x="560" y="0" on="1"/>
+ <pt x="500" y="0" on="1"/>
+ <pt x="80" y="670" on="1"/>
+ <pt x="140" y="670" on="1"/>
+ </contour>
+ <contour>
+ <pt x="140" y="50" on="1"/>
+ <pt x="500" y="50" on="1"/>
+ <pt x="500" y="620" on="1"/>
+ <pt x="140" y="620" on="1"/>
+ </contour>
+ <contour>
+ <pt x="80" y="0" on="1"/>
+ <pt x="80" y="670" on="1"/>
+ <pt x="560" y="670" on="1"/>
+ <pt x="560" y="0" on="1"/>
+ </contour>
+ <instructions/>
+ </TTGlyph>
+
+ <TTGlyph name="uni0020"/><!-- contains no outline data -->
+
+ <TTGlyph name="uni0061" xMin="46" yMin="-13" xMax="501" yMax="487">
+ <contour>
+ <pt x="46" y="154" on="0"/>
+ <pt x="110" y="225" on="0"/>
+ <pt x="210" y="262" on="1"/>
+ <pt x="242" y="273" on="0"/>
+ <pt x="328" y="297" on="0"/>
+ <pt x="365" y="304" on="1"/>
+ <pt x="365" y="268" on="1"/>
+ <pt x="331" y="261" on="0"/>
+ <pt x="254" y="237" on="0"/>
+ <pt x="231" y="228" on="1"/>
+ <pt x="164" y="202" on="0"/>
+ <pt x="131" y="148" on="0"/>
+ <pt x="131" y="126" on="1"/>
+ <pt x="131" y="86" on="0"/>
+ <pt x="178" y="52" on="0"/>
+ <pt x="212" y="52" on="1"/>
+ <pt x="238" y="52" on="0"/>
+ <pt x="283" y="76" on="0"/>
+ <pt x="330" y="110" on="1"/>
+ <pt x="350" y="125" on="1"/>
+ <pt x="364" y="104" on="1"/>
+ <pt x="335" y="75" on="1"/>
+ <pt x="290" y="30" on="0"/>
+ <pt x="226" y="-13" on="0"/>
+ <pt x="180" y="-13" on="1"/>
+ <pt x="125" y="-13" on="0"/>
+ <pt x="46" y="50" on="0"/>
+ </contour>
+ <contour>
+ <pt x="325" y="92" on="1"/>
+ <pt x="325" y="320" on="1"/>
+ <pt x="325" y="394" on="0"/>
+ <pt x="280" y="442" on="0"/>
+ <pt x="231" y="442" on="1"/>
+ <pt x="214" y="442" on="0"/>
+ <pt x="169" y="435" on="0"/>
+ <pt x="141" y="424" on="1"/>
+ <pt x="181" y="455" on="1"/>
+ <pt x="155" y="369" on="1"/>
+ <pt x="148" y="347" on="0"/>
+ <pt x="124" y="324" on="0"/>
+ <pt x="104" y="324" on="1"/>
+ <pt x="62" y="324" on="0"/>
+ <pt x="59" y="364" on="1"/>
+ <pt x="73" y="421" on="0"/>
+ <pt x="177" y="487" on="0"/>
+ <pt x="252" y="487" on="1"/>
+ <pt x="329" y="487" on="0"/>
+ <pt x="405" y="408" on="0"/>
+ <pt x="405" y="314" on="1"/>
+ <pt x="405" y="102" on="1"/>
+ <pt x="405" y="68" on="0"/>
+ <pt x="425" y="41" on="0"/>
+ <pt x="442" y="41" on="1"/>
+ <pt x="455" y="41" on="0"/>
+ <pt x="473" y="53" on="0"/>
+ <pt x="481" y="63" on="1"/>
+ <pt x="501" y="41" on="1"/>
+ <pt x="469" y="-10" on="0"/>
+ <pt x="416" y="-10" on="1"/>
+ <pt x="375" y="-10" on="0"/>
+ <pt x="325" y="46" 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">
+ 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-Master1
+ </namerecord>
+ <namerecord nameID="9" platformID="3" platEncID="1" langID="0x409">
+ Frank Grießhammer
+ </namerecord>
+ <namerecord nameID="17" platformID="3" platEncID="1" langID="0x409">
+ Master 1
+ </namerecord>
+ <namerecord nameID="256" platformID="3" platEncID="1" langID="0x409">
+ Weight
+ </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="uni0020"/>
+ <psName name="uni0061"/>
+ </extraNames>
+ </post>
+
+ <GDEF>
+ <Version value="0x00010003"/>
+ <GlyphClassDef>
+ <ClassDef glyph="uni0061" class="1"/>
+ </GlyphClassDef>
+ </GDEF>
+
+ <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=3 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=1 -->
+ <VarRegionIndex index="0" value="0"/>
+ <Item index="0" value="[0]"/>
+ <Item index="1" value="[-28]"/>
+ <Item index="2" value="[32]"/>
+ </VarData>
+ </VarStore>
+ </HVAR>
+
+ <MVAR>
+ <Version value="0x00010000"/>
+ <Reserved value="0"/>
+ <ValueRecordSize value="8"/>
+ <!-- ValueRecordCount=2 -->
+ <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="[8]"/>
+ <Item index="1" value="[13]"/>
+ </VarData>
+ </VarStore>
+ <ValueRecord index="0">
+ <ValueTag value="stro"/>
+ <VarIdx value="0"/>
+ </ValueRecord>
+ <ValueRecord index="1">
+ <ValueTag value="xhgt"/>
+ <VarIdx value="1"/>
+ </ValueRecord>
+ </MVAR>
+
+ <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>1000.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <gvar>
+ <version value="1"/>
+ <reserved value="0"/>
+ <glyphVariations glyph=".notdef">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="-20" y="-18"/>
+ <delta pt="2" x="0" y="-18"/>
+ <delta pt="3" x="20" y="0"/>
+ <delta pt="4" x="0" y="0"/>
+ <delta pt="5" x="-20" y="0"/>
+ <delta pt="6" x="0" y="-18"/>
+ <delta pt="7" x="20" y="-18"/>
+ <delta pt="8" x="10" y="10"/>
+ <delta pt="9" x="-10" y="10"/>
+ <delta pt="10" x="-10" y="-28"/>
+ <delta pt="11" x="10" y="-28"/>
+ <delta pt="12" x="0" y="0"/>
+ <delta pt="13" x="0" y="-18"/>
+ <delta pt="14" x="0" y="-18"/>
+ <delta pt="15" x="0" y="0"/>
+ <delta pt="16" x="0" y="0"/>
+ <delta pt="17" x="0" y="0"/>
+ <delta pt="18" x="0" y="0"/>
+ <delta pt="19" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="uni0020">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="0" y="0"/>
+ <delta pt="1" x="-28" y="0"/>
+ <delta pt="2" x="0" y="0"/>
+ <delta pt="3" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ <glyphVariations glyph="uni0061">
+ <tuple>
+ <coord axis="wght" value="1.0"/>
+ <delta pt="0" x="-21" y="16"/>
+ <delta pt="1" x="-2" y="28"/>
+ <delta pt="2" x="20" y="23"/>
+ <delta pt="3" x="19" y="20"/>
+ <delta pt="4" x="28" y="21"/>
+ <delta pt="5" x="26" y="23"/>
+ <delta pt="6" x="26" y="15"/>
+ <delta pt="7" x="24" y="12"/>
+ <delta pt="8" x="30" y="17"/>
+ <delta pt="9" x="31" y="15"/>
+ <delta pt="10" x="77" y="31"/>
+ <delta pt="11" x="66" y="36"/>
+ <delta pt="12" x="66" y="18"/>
+ <delta pt="13" x="66" y="21"/>
+ <delta pt="14" x="49" y="19"/>
+ <delta pt="15" x="37" y="19"/>
+ <delta pt="16" x="21" y="19"/>
+ <delta pt="17" x="-2" y="5"/>
+ <delta pt="18" x="-34" y="-18"/>
+ <delta pt="19" x="-6" y="3"/>
+ <delta pt="20" x="-11" y="12"/>
+ <delta pt="21" x="-29" y="-11"/>
+ <delta pt="22" x="-17" y="-2"/>
+ <delta pt="23" x="-13" y="-3"/>
+ <delta pt="24" x="-25" y="-3"/>
+ <delta pt="25" x="-29" y="-3"/>
+ <delta pt="26" x="-21" y="2"/>
+ <delta pt="27" x="-34" y="-14"/>
+ <delta pt="28" x="-34" y="17"/>
+ <delta pt="29" x="-34" y="7"/>
+ <delta pt="30" x="-18" y="7"/>
+ <delta pt="31" x="-16" y="7"/>
+ <delta pt="32" x="-18" y="7"/>
+ <delta pt="33" x="-15" y="9"/>
+ <delta pt="34" x="-21" y="12"/>
+ <delta pt="35" x="19" y="23"/>
+ <delta pt="36" x="45" y="46"/>
+ <delta pt="37" x="52" y="7"/>
+ <delta pt="38" x="26" y="-21"/>
+ <delta pt="39" x="14" y="-21"/>
+ <delta pt="40" x="-5" y="-21"/>
+ <delta pt="41" x="-17" y="-7"/>
+ <delta pt="42" x="-31" y="1"/>
+ <delta pt="43" x="-12" y="16"/>
+ <delta pt="44" x="34" y="16"/>
+ <delta pt="45" x="61" y="16"/>
+ <delta pt="46" x="70" y="4"/>
+ <delta pt="47" x="70" y="-5"/>
+ <delta pt="48" x="70" y="-22"/>
+ <delta pt="49" x="70" y="4"/>
+ <delta pt="50" x="59" y="22"/>
+ <delta pt="51" x="50" y="22"/>
+ <delta pt="52" x="43" y="22"/>
+ <delta pt="53" x="37" y="19"/>
+ <delta pt="54" x="38" y="22"/>
+ <delta pt="55" x="47" y="28"/>
+ <delta pt="56" x="46" y="-6"/>
+ <delta pt="57" x="-2" y="-6"/>
+ <delta pt="58" x="-16" y="-6"/>
+ <delta pt="59" x="-25" y="-13"/>
+ <delta pt="60" x="0" y="0"/>
+ <delta pt="61" x="32" y="0"/>
+ <delta pt="62" x="0" y="0"/>
+ <delta pt="63" x="0" y="0"/>
+ </tuple>
+ </glyphVariations>
+ </gvar>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/FeatureVars_rclt.ttx b/Tests/varLib/data/test_results/FeatureVars_rclt.ttx
index b889f3a5..43691364 100644
--- a/Tests/varLib/data/test_results/FeatureVars_rclt.ttx
+++ b/Tests/varLib/data/test_results/FeatureVars_rclt.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.29">
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.39">
<fvar>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx
new file mode 100644
index 00000000..8a73402e
--- /dev/null
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx
@@ -0,0 +1,116 @@
+<?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="xxxx"/>
+ <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 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=1 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="a"/>
+ <Value1 XAdvance="17"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MarkBasePos index="0" Format="1">
+ <MarkCoverage>
+ <Glyph value="uni0303"/>
+ </MarkCoverage>
+ <BaseCoverage>
+ <Glyph value="a"/>
+ </BaseCoverage>
+ <!-- ClassCount=1 -->
+ <MarkArray>
+ <!-- MarkCount=1 -->
+ <MarkRecord index="0">
+ <Class value="0"/>
+ <MarkAnchor Format="1">
+ <XCoordinate value="0"/>
+ <YCoordinate value="510"/>
+ </MarkAnchor>
+ </MarkRecord>
+ </MarkArray>
+ <BaseArray>
+ <!-- BaseCount=1 -->
+ <BaseRecord index="0">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="273"/>
+ <YCoordinate value="510"/>
+ </BaseAnchor>
+ </BaseRecord>
+ </BaseArray>
+ </MarkBasePos>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="7"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ContextPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ </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>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx
new file mode 100644
index 00000000..17636512
--- /dev/null
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx
@@ -0,0 +1,116 @@
+<?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="xxxx"/>
+ <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 -->
+ <PairPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ </Coverage>
+ <ValueFormat1 value="4"/>
+ <ValueFormat2 value="0"/>
+ <!-- PairSetCount=1 -->
+ <PairSet index="0">
+ <!-- PairValueCount=1 -->
+ <PairValueRecord index="0">
+ <SecondGlyph value="a"/>
+ <Value1 XAdvance="-23"/>
+ </PairValueRecord>
+ </PairSet>
+ </PairPos>
+ </Lookup>
+ <Lookup index="1">
+ <LookupType value="4"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <MarkBasePos index="0" Format="1">
+ <MarkCoverage>
+ <Glyph value="uni0303"/>
+ </MarkCoverage>
+ <BaseCoverage>
+ <Glyph value="a"/>
+ </BaseCoverage>
+ <!-- ClassCount=1 -->
+ <MarkArray>
+ <!-- MarkCount=1 -->
+ <MarkRecord index="0">
+ <Class value="0"/>
+ <MarkAnchor Format="1">
+ <XCoordinate value="0"/>
+ <YCoordinate value="500"/>
+ </MarkAnchor>
+ </MarkRecord>
+ </MarkArray>
+ <BaseArray>
+ <!-- BaseCount=1 -->
+ <BaseRecord index="0">
+ <BaseAnchor index="0" Format="1">
+ <XCoordinate value="260"/>
+ <YCoordinate value="500"/>
+ </BaseAnchor>
+ </BaseRecord>
+ </BaseArray>
+ </MarkBasePos>
+ </Lookup>
+ <Lookup index="2">
+ <LookupType value="7"/>
+ <LookupFlag value="0"/>
+ <!-- SubTableCount=1 -->
+ <ContextPos index="0" Format="1">
+ <Coverage>
+ <Glyph value="A"/>
+ </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>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/SparseCFF2-VF.ttx b/Tests/varLib/data/test_results/SparseCFF2-VF.ttx
new file mode 100644
index 00000000..4a1861c9
--- /dev/null
+++ b/Tests/varLib/data/test_results/SparseCFF2-VF.ttx
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ttFont sfntVersion="OTTO" ttLibVersion="4.42">
+
+ <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="e"/>
+ </GlyphOrder>
+
+ <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>
+ <BlueScale value="0.039625"/>
+ <BlueShift value="7"/>
+ <BlueFuzz value="1"/>
+ <LanguageGroup value="0"/>
+ <ExpansionFactor value="0.06"/>
+ </Private>
+ </FontDict>
+ </FDArray>
+ <CharStrings>
+ <CharString name=".notdef">
+ 50 -250 -100 1 blend
+ rmoveto
+ 400 1000 -400 100 100 -100 3 blend
+ hlineto
+ 50 -950 rmoveto
+ 900 300 -900 vlineto
+ </CharString>
+ <CharString name="a">
+ 468 -1 rmoveto
+ -21 435 -233 70 -205 -76 27 -91 -56 1 blend
+ 172 60 155 -40 -59 2 2 blend
+ 3 -360 56 1 blend
+ rlineto
+ 12 266 59 -2 2 blend
+ rmoveto
+ -352 -23 3 -218 139 -34 221 83 -6 63 -222 -60 -75 52 15 40 13 37 -21 5 blend
+ 2 46 294 35 -78 -30 2 blend
+ rlineto
+ </CharString>
+ <CharString name="e">
+ 1 vsindex
+ 127 228 -1 70 -25 1 2 blend
+ rmoveto
+ 449 -2 1 -45 -2 -2 2 blend
+ -5 79 -255 208 -276 -252 148 -279 338 63 -17 84 -280 -54 -82 188 170 153 163 -124 -355 6 27 0 0 -27 0 36 0 -29 0 -34 0 31 0 -1 0 2 0 -45 -2 13 28 100 37 0 13 0 -2 55 -40 -54 -32 -86 -30 -57 -85 -60 34 57 84 146 -5 0 21 blend
+ rlineto
+ </CharString>
+ </CharStrings>
+ <VarStore Format="1">
+ <Format value="1"/>
+ <VarRegionList>
+ <!-- RegionAxisCount=1 -->
+ <!-- RegionCount=3 -->
+ <Region index="0">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ <Region index="1">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.0"/>
+ <PeakCoord value="0.36365"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ <Region index="2">
+ <VarRegionAxis index="0">
+ <StartCoord value="0.36365"/>
+ <PeakCoord value="1.0"/>
+ <EndCoord value="1.0"/>
+ </VarRegionAxis>
+ </Region>
+ </VarRegionList>
+ <!-- VarDataCount=2 -->
+ <VarData index="0">
+ <!-- ItemCount=0 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=1 -->
+ <VarRegionIndex index="0" value="0"/>
+ </VarData>
+ <VarData index="1">
+ <!-- ItemCount=0 -->
+ <NumShorts value="0"/>
+ <!-- VarRegionCount=2 -->
+ <VarRegionIndex index="0" value="1"/>
+ <VarRegionIndex index="1" value="2"/>
+ </VarData>
+ </VarStore>
+ </CFFFont>
+
+ <GlobalSubrs>
+ <!-- The 'index' attribute is only for humans; it is ignored when parsed. -->
+ </GlobalSubrs>
+ </CFF2>
+
+ <fvar>
+
+ <!-- Weight -->
+ <Axis>
+ <AxisTag>wght</AxisTag>
+ <Flags>0x0</Flags>
+ <MinValue>350.0</MinValue>
+ <DefaultValue>350.0</DefaultValue>
+ <MaxValue>625.0</MaxValue>
+ <AxisNameID>256</AxisNameID>
+ </Axis>
+ </fvar>
+
+ <hmtx>
+ <mtx name=".notdef" width="500" lsb="50"/>
+ <mtx name="a" width="600" lsb="9"/>
+ <mtx name="e" width="600" lsb="40"/>
+ </hmtx>
+
+ <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="1"/>
+ <!-- VarRegionCount=1 -->
+ <VarRegionIndex index="0" value="0"/>
+ <Item index="0" value="[0]"/>
+ <Item index="1" value="[300]"/>
+ </VarData>
+ </VarStore>
+ <AdvWidthMap>
+ <Map glyph=".notdef" outer="0" inner="1"/>
+ <Map glyph="a" outer="0" inner="0"/>
+ <Map glyph="e" outer="0" inner="0"/>
+ </AdvWidthMap>
+ </HVAR>
+
+</ttFont>
diff --git a/Tests/varLib/data/test_results/SparseMasters.ttx b/Tests/varLib/data/test_results/SparseMasters.ttx
index a3f8e619..2871e24f 100644
--- a/Tests/varLib/data/test_results/SparseMasters.ttx
+++ b/Tests/varLib/data/test_results/SparseMasters.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.35">
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="4.42">
<GlyphOrder>
<!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
@@ -440,28 +440,7 @@
<Format value="1"/>
<VarRegionList>
<!-- RegionAxisCount=1 -->
- <!-- RegionCount=3 -->
- <Region index="0">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="0.36365"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="1">
- <VarRegionAxis index="0">
- <StartCoord value="0.36365"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
- <Region index="2">
- <VarRegionAxis index="0">
- <StartCoord value="0.0"/>
- <PeakCoord value="1.0"/>
- <EndCoord value="1.0"/>
- </VarRegionAxis>
- </Region>
+ <!-- RegionCount=0 -->
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
diff --git a/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
index 264a3d4a..7c3267a8 100644
--- a/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
+++ b/Tests/varLib/data/test_results/TestSparseCFF2VF.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="OTTO" ttLibVersion="3.41">
+<ttFont sfntVersion="OTTO" ttLibVersion="4.37">
<fvar>
@@ -189,7 +189,7 @@
<CharString name="cid00002" fdSelectIndex="2">
-12 83 1 126 2 blend
hstemhm
- 74 73 -57 40 -26 129 -125 122 4 blend
+ 74 73 -57 40 -26 129 -125 121.5 4 blend
vstemhm
hintmask 10100000
134 755 107 18 2 blend
@@ -208,927 +208,927 @@
</CharString>
<CharString name="cid01177" fdSelectIndex="1">
1 vsindex
- -72 30 253 30 94 30 92 30 65 30 131 45 -30 112 -99 17 -8 0 -12 59 -2 85 -64 2 -92 39 -1 56 -44 2 -63 35 -1 51 -43 1 -62 39 -1 56 -21 0 -31 56 -2 81 -56 2 -81 44 -2 63 -57 3 -81 31 -1 45 -18 0 -27 44 -2 63 16 blend
+ -72 30 253 30 94 30 92 30 65 30 131 45 -30 112 -99 17 -8 -0.02417 -12 59 -1.82175 85 -64 1.80664 -92 39 -0.88217 56 -44 1.86707 -63 35 -0.89426 51 -43 0.87009 -62 39 -0.88217 56 -21 -0.06345 -31 56 -1.83081 81 -56 1.83081 -81 44 -1.86707 63 -57 2.82779 -81 31 -0.90634 45 -18 -0.05438 -27 44 -1.86707 63 16 blend
hstemhm
- 193 30 83 30 147 30 173 30 66 30 -20 1 -28 75 -3 107 -74 3 -106 78 -4 111 -99 5 -141 81 -3 116 -79 2 -114 64 -2 92 -81 4 -115 80 -4 114 10 blend
+ 193 30 83 30 147 30 173 30 66 30 -20 0.93958 -28 75 -2.7734 107 -74 2.77643 -106 78 -3.76434 111 -99 4.70091 -141 81 -2.75528 116 -79 1.76132 -114 64 -1.80664 92 -81 3.75528 -115 80 -3.7583 114 10 blend
vstemhm
hintmask 1111100011111000
- 306 142 -19 1 -27 7 0 10 2 blend
+ 306 142 -19 0.9426 -27 7 0.02115 10 2 blend
rmoveto
- -156 45 -2 64 1 blend
+ -156 45 -1.86404 64 1 blend
vlineto
- -50 22 -8 79 -42 2 -59 8 -1 11 -18 0 -27 43 -1 62 4 blend
+ -50 22 -8 79 -42 1.87311 -59 8 -0.97583 11 -18 -0.05438 -27 43 -0.87009 62 4 blend
vhcurveto
- 17 186 8 -1 11 -68 3 -97 2 blend
- 0 18 8 0 12 1 blend
+ 17 186 8 -0.97583 11 -68 2.79456 -97 2 blend
+ 0 18 8 0.02417 12 1 blend
hhcurveto
- 70 13 25 114 5 22 -1 31 17 -1 24 2 1 4 0 -1 -1 7 0 10 5 blend
+ 70 13 25 114 5 22 -0.93353 31 17 -0.94864 24 2 1 4 0 -1 -1 7 0.02115 10 5 blend
hvcurveto
- -9 3 -12 4 -9 6 -20 1 -28 3 0 4 -31 1 -45 10 0 15 -13 0 -19 9 -1 13 6 blend
+ -9 3 -12 4 -9 6 -20 0.93958 -28 3 0 4 -31 0.90634 -45 10 0.03021 15 -13 -0.03928 -19 9 -0.97281 13 6 blend
rrcurveto
- -109 -4 -7 -13 -49 -38 -156 33 -1 47 -1 0 -1 1 0 1 2 0 3 10 0 15 9 0 13 60 -3 85 7 blend
- 0 -27 5 0 8 1 blend
+ -109 -4 -7 -13 -49 -38 -156 33 -0.9003 47 -1 0 -1 1 0 1 2 0 3 10 0.03021 15 9 0.02719 13 60 -2.81873 85 7 blend
+ 0 -27 5 0.0151 8 1 blend
hhcurveto
- -59 -10 5 22 11 0 16 2 -1 2 -1 0 -2 4 0 6 4 blend
+ -59 -10 5 22 11 0.03323 16 2 -1 2 -1 0 -2 4 0.01208 6 4 blend
hvcurveto
- 157 -47 2 -67 1 blend
+ 157 -47 1.858 -67 1 blend
vlineto
- 63 34 -74 3 -106 -25 1 -36 2 blend
+ 63 34 -74 2.77643 -106 -25 0.92447 -36 2 blend
rmoveto
- 65 -30 74 -47 37 -37 -7 1 -10 5 -1 7 -3 0 -4 5 0 7 -3 0 -4 6 0 9 6 blend
+ 65 -30 74 -47 37 -37 -7 0.97885 -10 5 -0.9849 7 -3 0 -4 5 0.0151 7 -3 0 -4 6 0.01813 9 6 blend
rrcurveto
- 20 22 -37 36 -75 47 -65 28 48 -2 68 47 -2 67 -1 0 -1 -6 1 -8 2 0 3 -8 0 -11 8 -1 11 -6 0 -9 8 blend
+ 20 22 -37 36 -75 47 -65 28 48 -1.85498 68 47 -1.858 67 -1 0 -1 -6 0.98187 -8 2 0 3 -8 -0.02417 -11 8 -0.97583 11 -6 -0.01813 -9 8 blend
rlinecurve
- 320 -64 -49 3 -69 -32 1 -46 2 blend
+ 320 -64 -49 2.85196 -69 -32 0.90332 -46 2 blend
rmoveto
- 76 -49 83 -75 38 -12 0 -18 -6 1 -8 -13 0 -19 -4 0 -6 -9 1 -12 5 blend
+ 76 -49 83 -75 38 -12 -0.03625 -18 -6 0.98187 -8 -13 -0.03928 -19 -4 -0.01208 -6 -9 0.97281 -12 5 blend
-54 rrcurveto
- 23 19 -38 54 -84 73 -76 49 69 -3 98 35 -2 50 5 0 8 2 0 3 11 0 16 2 0 2 13 -1 18 2 0 4 8 blend
+ 23 19 -38 54 -84 73 -76 49 69 -2.79153 98 35 -1.89426 50 5 0.0151 8 2 0 3 11 0.03323 16 2 0 2 13 -0.96072 18 2 0 4 8 blend
rlinecurve
- -557 -5 -85 4 -121 -6 0 -9 2 blend
+ -557 -5 -85 3.74321 -121 -6 -0.01813 -9 2 blend
rmoveto
- -28 -68 -50 -72 -77 -40 5 -1 6 1 0 1 3 1 5 6 0 9 13 -1 18 1 0 1 6 blend
+ -28 -68 -50 -72 -77 -40 5 -0.9849 6 1 0 1 3 1 5 6 0.01813 9 13 -0.96072 18 1 0 1 6 blend
rrcurveto
- 24 -17 79 42 47 74 31 71 62 -3 89 -42 2 -60 -7 1 -10 5 -1 7 -6 0 -8 1 0 1 -2 -1 -4 4 1 7 8 blend
+ 24 -17 79 42 47 74 31 71 62 -2.81268 89 -42 1.87311 -60 -7 0.97885 -10 5 -0.9849 7 -6 -0.01813 -8 1 0 1 -2 -1 -4 4 1.01208 7 8 blend
rlinecurve
- -117 625 -26 2 -36 42 -3 59 2 blend
+ -117 625 -26 1.92145 -36 42 -2.87311 59 2 blend
rmoveto
- -30 775 30 -57 3 -81 -3 0 -5 57 -3 81 3 blend
+ -30 775 30 -57 2.82779 -81 -3 0 -5 57 -2.82779 81 3 blend
vlineto
- -818 -176 -1 0 -2 12 0 18 2 blend
+ -818 -176 -1 0 -2 12 0.03625 18 2 blend
rmoveto
- -30 869 30 -56 2 -81 3 0 5 56 -2 81 3 blend
+ -30 869 30 -56 1.83081 -81 3 0 5 56 -1.83081 81 3 blend
vlineto
hintmask 0000001000100000
- -455 258 -40 2 -57 -38 2 -54 2 blend
+ -455 258 -40 1.87915 -57 -38 1.8852 -54 2 blend
rmoveto
hintmask 0000000100100000
- -99 30 -18 0 -27 81 -3 116 2 blend
+ -99 30 -18 -0.05438 -27 81 -2.75528 116 2 blend
vlineto
hintmask 0000001000100000
- 99 18 0 27 1 blend
+ 99 18 0.05438 27 1 blend
vlineto
hintmask 0111010010001000
- -236 -127 -60 2 -86 -18 0 -27 2 blend
+ -236 -127 -60 1.81873 -86 -18 -0.05438 -27 2 blend
rmoveto
- 26 -40 25 -53 9 -36 -12 1 -17 10 0 15 -10 -1 -15 13 0 19 -4 0 -6 11 -1 15 6 blend
+ 26 -40 25 -53 9 -36 -12 0.96375 -17 10 0.03021 15 -10 -1.03021 -15 13 0.03928 19 -4 -0.01208 -6 11 -0.96677 15 6 blend
rrcurveto
- 29 12 -10 35 -25 53 -27 39 76 -3 109 12 0 18 3 1 5 -10 0 -15 10 -1 14 -15 1 -21 11 0 16 -11 0 -16 8 blend
+ 29 12 -10 35 -25 53 -27 39 76 -2.77039 109 12 0.03625 18 3 1 5 -10 -0.03021 -15 10 -0.96979 14 -15 0.95468 -21 11 0.03323 16 -11 -0.03323 -16 8 blend
rlinecurve
- 393 2 -112 4 -161 -1 0 -2 2 blend
+ 393 2 -112 3.66164 -161 -1 0 -2 2 blend
rmoveto
- -16 -38 -31 -57 -23 -35 7 0 11 12 -1 17 13 -1 18 19 0 28 10 0 14 8 -1 11 6 blend
+ -16 -38 -31 -57 -23 -35 7 0.02115 11 12 -0.96375 17 13 -0.96072 18 19 0.0574 28 10 0.03021 14 8 -0.97583 11 6 blend
rrcurveto
- 27 -12 24 36 26 48 23 46 70 -2 101 -10 1 -14 -8 0 -12 -13 1 -18 -6 -1 -9 -17 0 -25 -1 1 -1 -10 1 -14 8 blend
+ 27 -12 24 36 26 48 23 46 70 -1.78851 101 -10 0.96979 -14 -8 -0.02417 -12 -13 0.96072 -18 -6 -1.01813 -9 -17 -0.05136 -25 -1 1 -1 -10 0.96979 -14 8 blend
rlinecurve
- -504 -378 27 -1 39 -8 0 -12 2 blend
+ -504 -378 27 -0.91843 39 -8 -0.02417 -12 2 blend
rmoveto
- 559 -94 -559 -110 5 -157 44 -2 63 110 -5 157 3 blend
+ 559 -94 -559 -110 4.66768 -157 44 -1.86707 63 110 -4.66768 157 3 blend
hlineto
- 216 -52 2 -74 1 blend
+ 216 -52 1.8429 -74 1 blend
vmoveto
- 559 -92 -559 -110 5 -157 43 -1 62 110 -5 157 3 blend
+ 559 -92 -559 -110 4.66768 -157 43 -0.87009 62 110 -4.66768 157 3 blend
hlineto
- -30 122 -75 3 -107 -4 0 -6 2 blend
+ -30 122 -75 2.7734 -107 -4 -0.01208 -6 2 blend
rmoveto
- -276 619 276 -26 0 -38 45 -2 64 26 0 38 3 blend
+ -276 619 276 -26 -0.07855 -38 45 -1.86404 64 26 0.07855 38 3 blend
vlineto
</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 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
+ -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.20721 -26 67 1.6036 76 -98 -1.88289 -111 42 0.37837 47 -13 -0.11711 -14 13 0.11711 14 -33 -0.2973 -37 11 0.0991 13 -11 -0.0991 -13 7.5 0.06757 8.5 -7.5 -0.06757 -8.5 53 0.47748 60 -32 -0.28828 -36 32 0.28828 36 -52 -0.46848 -59 57 0.51352 65 -33 -0.2973 -38 53 0.47748 60 -83 -0.74774 -93 54 0.48648 60 -6 -19.05405 -24 33 19.2973 55 -76 -0.68468 -86 76 0.68468 86 -76 -0.68468 -86 59 0.53152 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
+ 77 30 42 30 139 30 23 30 71 10 74 30 15 30 16 30 158 30 28 30 -4 29 -14 -0.12613 -16 88 0.79279 99 -82 -0.73874 -92 87 0.78378 98 -130 -1.17117 -146 102 0.91891 114 -73 -0.65765 -82 74 1.66667 84 -112 -2 -126 27 0.24324 30 13 0.11711 15 90 0.8108 101 -126 -1.13513 -142 75 0.67567 84 -68 -0.61261 -76 102 0.91891 115 -144 -1.2973 -162 94 0.84685 105 -79 -0.71172 -88 95 0.85585 106 -81 -0.72974 -91 74 0.66667 83 22 blend
vstemhm
hintmask 110001011101011101101101
- 53 761 -3 0 -3 31 0 35 2 blend
+ 53 761 -3 -0.02702 -3 31 0.27928 35 2 blend
rmoveto
- -30 896 30 -76 -1 -86 5 0 5 76 1 86 3 blend
+ -30 896 30 -76 -0.68468 -86 5 0.04504 5 76 0.68468 86 3 blend
vlineto
- -802 -461 2 0 2 -23 0 -26 2 blend
+ -802 -461 2 0.01802 2 -23 -0.20721 -26 2 blend
rmoveto
- -30 703 30 -53 0 -60 3 0 4 53 0 60 3 blend
+ -30 703 30 -53 -0.47748 -60 3 0.02702 4 53 0.47748 60 3 blend
vlineto
hintmask 000000000000100100000000
- -532 539 -58 -1 -65 6 0 7 2 blend
+ -532 539 -58 -0.52252 -65 6 0.05405 7 2 blend
rmoveto
hintmask 000000000010000100000000
- -171 30 -16 -19 -36 102 1 114 2 blend
+ -171 30 -16 -19.14415 -36 102 0.91891 114 2 blend
vlineto
hintmask 000000000000100100001000
- 171 16 19 36 1 blend
+ 171 16 19.14415 36 1 blend
vlineto
- 299 -100 -1 -112 1 blend
+ 299 -100 -0.9009 -112 1 blend
hmoveto
hintmask 000000000010000000001000
- -171 30 -16 -19 -36 102 1 115 2 blend
+ -171 30 -16 -19.14415 -36 102 0.91891 115 2 blend
vlineto
hintmask 000000000000100000001000
- 171 16 19 36 1 blend
+ 171 16 19.14415 36 1 blend
vlineto
hintmask 000000111100011010010100
- -46 -219 -34 0 -39 -64 -1 -72 2 blend
+ -46 -219 -34 -0.3063 -39 -64 -0.57658 -72 2 blend
rmoveto
- 204 -121 -204 -110 -1 -123 83 1 93 110 1 123 3 blend
+ 204 -121 -204 -110 -1 -123 83 0.74774 93 110 1 123 3 blend
hlineto
- -230 121 33 1 38 -83 -1 -93 2 blend
+ -230 121 33 1.2973 38 -83 -0.74774 -93 2 blend
rmoveto
- 200 -121 -200 -108 -2 -122 83 1 93 108 2 122 3 blend
+ 200 -121 -200 -108 -1.97298 -122 83 0.74774 93 108 1.97298 122 3 blend
hlineto
- -222 121 27 -1 30 -83 -1 -93 2 blend
+ -222 121 27 -0.75676 30 -83 -0.74774 -93 2 blend
rmoveto
- 192 -121 -192 -101 -1 -114 83 1 93 101 1 114 3 blend
+ 192 -121 -192 -101 -0.90991 -114 83 0.74774 93 101 0.90991 114 3 blend
hlineto
- -30 151 -87 -1 -98 -29 0 -33 2 blend
+ -30 151 -87 -0.78378 -98 -29 -0.26126 -33 2 blend
rmoveto
- -181 716 181 -24 0 -27 11 0 12 24 0 27 3 blend
+ -181 716 181 -24 -0.21622 -27 11 0.0991 12 24 0.21622 27 3 blend
vlineto
- -788 -240 -17 0 -19 9 0 11 2 blend
+ -788 -240 -17 -0.15315 -19 9 0.08109 11 2 blend
rmoveto
- -130 30 100 -37 0 -42 88 1 99 -20 0 -23 3 blend
+ -130 30 100 -37 -0.33333 -42 88 0.79279 99 -20 -0.18018 -23 3 blend
vlineto
hintmask 000000110000000000000010
- 786 -100 30 130 -150 -1 -168 20 0 23 95 1 106 37 0 42 4 blend
+ 786 -100 30 130 -150 -1.35135 -168 20 0.18018 23 95 0.85585 106 37 0.33333 42 4 blend
hlineto
hintmask 000010000000000100000000
- -610 -123 -56 -1 -63 -44 0 -50 2 blend
+ -610 -123 -56 -0.5045 -63 -44 -0.3964 -50 2 blend
rmoveto
- -50 -62 -93 -73 -118 -54 8 -4 10 -9 6 -7 9 0 11 13 0 15 19 0 21 29 0 32 9 0 10 22 0 25 12 0 14 -11 0 -12 19 0 21 -26 0 -30 7 0 8 -16 0 -18 12 blend
+ -50 -62 -93 -73 -118 -54 8 -4 10 -9 6 -7 9 0.08109 11 13 0.11711 15 19 0.17117 21 29 0.26126 32 9 0.08109 10 22 0.1982 25 12 0.10811 14 -11 -0.0991 -12 19 0.17117 21 -26 -0.23424 -30 7 0.06306 8 -16 -0.14415 -18 12 blend
rrcurveto
hintmask 010000000000000001000000
- 121 58 92 75 59 70 3 0 3 -13 0 -14 -10 0 -11 -19 0 -21 -2 0 -2 8 0 8 6 blend
+ 121 58 92 75 59 70 3 0.02702 3 -13 -0.11711 -14 -10 -0.09009 -11 -19 -0.17117 -21 -2 -0.01802 -2 8 0.07207 8 6 blend
rrcurveto
- 124 -78 -89 -1 -100 32 0 36 2 blend
+ 124 -78 -89 -0.8018 -100 32 0.28828 36 2 blend
rmoveto
- -7 -6 0 -6 1 blend
+ -7 -6 -0.05405 -6 1 blend
vlineto
- -65 -139 -176 -81 -162 -31 6 -6 8 -12 3 -8 16 0 17 30 0 34 36 0 41 26 0 29 -7 0 -8 12 0 13 12 0 14 -16 0 -18 15 0 16 -30 0 -33 5 0 6 -18 0 -21 12 blend
+ -65 -139 -176 -81 -162 -31 6 -6 8 -12 3 -8 16 0.14415 17 30 0.27026 34 36 0.32433 41 26 0.23424 29 -7 -0.06306 -8 12 0.10811 13 12 0.10811 14 -16 -0.14415 -18 15 0.13513 16 -30 -0.27026 -33 5 0.04504 6 -18 -0.16216 -21 12 blend
rrcurveto
hintmask 001000000000000001000000
- 168 37 178 84 72 154 26 0 29 -5 0 -5 -23 0 -26 -12 0 -14 -5 0 -5 6 0 7 6 blend
+ 168 37 178 84 72 154 26 0.23424 29 -5 -0.04504 -5 -23 -0.20721 -26 -12 -0.10811 -14 -5 -0.04504 -5 6 0.05405 7 6 blend
rrcurveto
hintmask 110100000000000001100001
- -19 11 -6 -2 -47 0 -53 13 0 15 -13 0 -15 0 0 -1 4 blend
+ -19 11 -6 -2 -47 -0.42342 -53 13 0.11711 15 -13 -0.11711 -15 0 0 -1 4 blend
rlineto
- -333 -72 75 1 85 -55 0 -61 2 blend
+ -333 -72 75 0.67567 85 -55 -0.4955 -61 2 blend
rmoveto
- 65 -25 75 -46 38 -35 -35 0 -40 8 0 9 -38 0 -42 15 0 17 -18 0 -21 14 0 15 6 blend
+ 65 -25 75 -46 38 -35 -35 -0.31532 -40 8 0.07207 9 -38 -0.34235 -42 15 0.13513 17 -18 -0.16216 -21 14 0.12613 15 6 blend
rrcurveto
- 26 19 -39 34 -76 45 -64 25 49 0 56 31 0 35 19 0 21 -14 0 -16 39 0 44 -18 0 -20 32 0 36 -9 0 -10 8 blend
+ 26 19 -39 34 -76 45 -64 25 49 0.44144 56 31 0.27928 35 19 0.17117 21 -14 -0.12613 -16 39 0.35135 44 -18 -0.16216 -20 32 0.28828 36 -9 -0.08109 -10 8 blend
rlinecurve
- 72 55 -55 0 -62 28 0 31 2 blend
+ 72 55 -55 -0.4955 -62 28 0.25226 31 2 blend
rmoveto
- -30 -30 -42 0 -47 -42 0 -47 2 blend
+ -30 -30 -42 -0.37837 -47 -42 -0.37837 -47 2 blend
rlineto
- 269 30 -14 0 -16 42 0 47 2 blend
+ 269 30 -14 -0.12613 -16 42 0.37837 47 2 blend
hlineto
- 74 74 13 0 15 -22 0 -24 2 blend
+ 74 74 13 0.11711 15 -22 -0.1982 -24 2 blend
rmoveto
- -276 80 1 90 1 blend
+ -276 80 0.72072 90 1 blend
vlineto
- -52 21 -9 77 -48 0 -54 8 0 9 -21 0 -24 44 0 49 4 blend
+ -52 21 -9 77 -48 -0.43243 -54 8 0.07207 9 -21 -0.1892 -24 44 0.3964 49 4 blend
vhcurveto
- 16 182 8 0 9 -90 -1 -101 2 blend
- 0 18 8 0 9 1 blend
+ 16 182 8 0.07207 9 -90 -0.8108 -101 2 blend
+ 0 18 8 0.07207 9 1 blend
hhcurveto
- 62 12 21 88 4 25 0 28 20 0 22 6 0 7 10 0 11 9 0 10 5 blend
+ 62 12 21 88 4 25 0.22522 28 20 0.18018 22 6 0.05405 7 10 0.09009 11 9 0.08109 10 5 blend
hvcurveto
- -9 2 -12 5 -8 6 -24 0 -26 4 0 5 -34 0 -39 12 0 13 -16 0 -18 10 0 11 6 blend
+ -9 2 -12 5 -8 6 -24 -0.21622 -26 4 0.03604 5 -34 -0.3063 -39 12 0.10811 13 -16 -0.14415 -18 10 0.09009 11 6 blend
rrcurveto
- -81 25 0 28 1 blend
- -4 -6 -11 -41 -37 -154 -1 0 -1 0 1 1 11 -1 12 15 1 17 79 1 89 5 blend
- 0 -26 9 0 10 1 blend
+ -81 25 0.22522 28 1 blend
+ -4 -6 -11 -41 -37 -154 -1 0 -1 0 1 1 11 -0.9009 12 15 1.13513 17 79 0.71172 89 5 blend
+ 0 -26 9 0.08109 10 1 blend
hhcurveto
- -56 -9 6 25 17 0 19 2 0 3 -1 -1 -2 4 0 5 4 blend
+ -56 -9 6 25 17 0.15315 19 2 0.01802 3 -1 -1 -2 4 0.03604 5 4 blend
hvcurveto
- 276 -81 -1 -91 1 blend
+ 276 -81 -0.72974 -91 1 blend
vlineto
- 278 -62 -114 -1 -128 32 0 36 2 blend
+ 278 -62 -114 -1.02702 -128 32 0.28828 36 2 blend
rmoveto
- -66 -32 -126 -33 -107 -23 5 -7 5 -10 2 -7 110 22 126 32 81 36 10 0 11 7 0 8 30 0 34 11 0 12 21 0 23 9 0 10 7 0 8 -14 0 -16 9 0 10 -27 0 -30 3 0 4 -15 0 -17 -15 0 -17 -10 0 -11 -12 0 -14 -11 0 -12 3 0 4 -3 0 -4 18 blend
+ -66 -32 -126 -33 -107 -23 5 -7 5 -10 2 -7 110 22 126 32 81 36 10 0.09009 11 7 0.06306 8 30 0.27026 34 11 0.0991 12 21 0.1892 23 9 0.08109 10 7 0.06306 8 -14 -0.12613 -16 9 0.08109 10 -27 -0.24324 -30 3 0.02702 4 -15 -0.13513 -17 -15 -0.13513 -17 -10 -0.09009 -11 -12 -0.10811 -14 -11 -0.0991 -12 3 0.02702 4 -3 -0.02702 -4 18 blend
rrcurveto
</CharString>
<CharString name="cid06821" fdSelectIndex="1">
3 vsindex
- -58 30 100 30 70 22 -22 30 94 30 19 31 -17 28 152 20 -20 30 -12 12 66 30 -30 89 -5 30 -30 121 -11 0 -24 36 0 81 -32 0 -74 22 0 52 -17 0 -39 16 1 37 -16 -1 -37 21 0 48 -27 0 -63 21 0 49 -11 0 -26 41 0 93 -47 0 -107 24 0 56 -34 0 -78 11 0 26 -11 0 -26 17 0 39 -15 0 -35 15 0 35 -19 0 -43 12 0 26 -12 0 -26 4 0 8 -5 0 -11 28 0 65 -28 0 -65 23 0 52 28 blend
+ -58 30 100 30 70 22 -22 30 94 30 19 31 -17 28 152 20 -20 30 -12 12 66 30 -30 89 -5 30 -30 121 -11 -0.0196 -24 36 0.06418 81 -32 -0.05704 -74 22 0.03922 52 -17 -0.0303 -39 16 1.02852 37 -16 -1.02852 -37 21 0.03743 48 -27 -0.04813 -63 21 0.03743 49 -11 -0.0196 -26 41 0.07309 93 -47 -0.08379 -107 24 0.04279 56 -34 -0.06061 -78 11 0.0196 26 -11 -0.0196 -26 17 0.0303 39 -15 -0.02673 -35 15 0.02673 35 -19 -0.03387 -43 12 0.0214 26 -12 -0.0214 -26 4 0 8 -5 0 -11 28 0.04991 65 -28 -0.04991 -65 23 0.041 52 28 blend
hstemhm
- 127 30 -18 18 199 30 -20 20 -20 30 -24 14 97 30 -11 11 72 31 202 30 87 29 -12 0 -27 44 1 101 -19 -1 -45 19 1 45 -46 -1 -106 37 0 85 -31 0 -71 31 0 71 -31 0 -71 40 0 91 -27 0 -62 18 0 42 -47 0 -108 51 0 117 -27 0 -62 27 0 62 -53 0 -122 43 0 99 -60 -1 -138 52 1 120 -32 0 -73 32 0 72 22 blend
+ 127 30 -18 18 199 30 -20 20 -20 30 -24 14 97 30 -11 11 72 31 202 30 87 29 -12 -0.0214 -27 44 1.07843 101 -19 -1.03387 -45 19 1.03387 45 -46 -1.082 -106 37 0.06595 85 -31 -0.05525 -71 31 0.05525 71 -31 -0.05525 -71 40 0.0713 91 -27 -0.04813 -62 18 0.03209 42 -47 -0.08379 -108 51 0.09091 117 -27 -0.04813 -62 27 0.04813 62 -53 -0.09447 -122 43 0.07664 99 -60 -1.10695 -138 52 1.0927 120 -32 -0.05704 -73 32 0.05704 72 22 blend
vstemhm
hintmask 00011000000000000000000100000000
- 193 296 41 0 93 -8 0 -19 2 blend
+ 193 296 41 0.07309 93 -8 -0.01427 -19 2 blend
rmoveto
- 625 -94 -625 -84 -1 -192 27 0 63 84 1 192 3 blend
+ 625 -94 -625 -84 -1.14973 -192 27 0.04813 63 84 1.14973 192 3 blend
hlineto
- -30 124 -48 0 -110 -6 0 -14 2 blend
+ -30 124 -48 -0.08556 -110 -6 -0.0107 -14 2 blend
rmoveto
- -154 685 154 -15 0 -34 16 0 38 15 0 34 3 blend
+ -154 685 154 -15 -0.02673 -34 16 0.02852 38 15 0.02673 34 3 blend
vlineto
hintmask 00100000000000000000100000000000
- -365 -132 -33 0 -76 1 1 3 2 blend
+ -365 -132 -33 -0.05882 -76 1 1 3 2 blend
rmoveto
- -232 -7 -1 -16 1 blend
+ -232 -7 -1.01248 -16 1 blend
vlineto
- 30 -5 51 0 117 -11 0 -27 2 blend
+ 30 -5 51 0.09091 117 -11 -0.0196 -27 2 blend
rlineto
- 237 18 1 43 1 blend
+ 237 18 1.03209 43 1 blend
vlineto
hintmask 01000000000010010000010000000000
- -11 -92 -27 0 -62 1 -1 2 2 blend
+ -11 -92 -27 -0.04813 -62 1 -1 2 2 blend
rmoveto
- -30 397 30 -22 0 -52 -12 0 -27 22 0 52 3 blend
+ -30 397 30 -22 -0.03922 -52 -12 -0.0214 -27 22 0.03922 52 3 blend
vlineto
- -760 647 25 0 56 -4 0 -9 2 blend
+ -760 647 25 0.04456 56 -4 0 -9 2 blend
rmoveto
- -30 811 30 -28 0 -65 -12 0 -27 28 0 65 3 blend
+ -30 811 30 -28 -0.04991 -65 -12 -0.0214 -27 28 0.04991 65 3 blend
vlineto
hintmask 00000000000010100000000000000000
- -823 -13 0 -29 1 blend
+ -823 -13 -0.02318 -29 1 blend
hmoveto
- -143 12 0 27 1 blend
+ -143 12 0.0214 27 1 blend
vlineto
-83 -13 -107 -75 -82 4 0 9 3 0 6 5 1 12 -1 0 -1 5 -1 11 5 blend
vhcurveto
- 7 -4 11 -9 5 -6 10 0 21 -5 0 -12 20 0 46 -17 0 -38 6 0 15 -8 0 -18 6 blend
+ 7 -4 11 -9 5 -6 10 0.01782 21 -5 0 -12 20 0.03564 46 -17 -0.0303 -38 6 0.0107 15 -8 -0.01427 -18 6 blend
rrcurveto
79 5 0 11 1 blend
- 85 16 118 88 1 1 3 9 0 19 6 0 15 3 blend
+ 85 16 118 88 1 1 3 9 0.01604 19 6 0.0107 15 3 blend
vvcurveto
- 143 -11 0 -25 1 blend
+ 143 -11 -0.0196 -25 1 blend
vlineto
hintmask 00000000010100001000000000000000
- 199 -25 -46 -1 -106 -23 0 -54 2 blend
+ 199 -25 -46 -1.082 -106 -23 -0.041 -54 2 blend
rmoveto
-167 vlineto
hintmask 00000000010100000100000000000000
- 30 37 0 85 1 blend
+ 30 37 0.06595 85 1 blend
167 hlineto
hintmask 00000000101000000001000000000000
- -14 -59 -18 0 -42 8 0 18 2 blend
+ -14 -59 -18 -0.03209 -42 8 0.01427 18 2 blend
rmoveto
- -30 185 30 -12 0 -26 -4 0 -9 12 0 26 3 blend
+ -30 185 30 -12 -0.0214 -26 -4 0 -9 12 0.0214 26 3 blend
vlineto
- -365 -96 10 0 22 7 0 17 2 blend
+ -365 -96 10 0.01782 22 7 0.01248 17 2 blend
rmoveto
- -30 392 30 -17 0 -39 -4 0 -9 17 0 39 3 blend
+ -30 392 30 -17 -0.0303 -39 -4 0 -9 17 0.0303 39 3 blend
vlineto
hintmask 00000011000000000100000000000000
- -218 -10 -15 0 -33 -6 0 -13 2 blend
+ -218 -10 -15 -0.02673 -33 -6 -0.0107 -13 2 blend
rmoveto
- -160 23 0 51 1 blend
+ -160 23 0.041 51 1 blend
vlineto
-8 -2 0 0 -1 1 blend
-3 -11 -1 1 0 3 0 0 1 2 blend
vhcurveto
-11 -1 -30 2 0 4 1 0 1 4 0 10 3 blend
- 0 -47 13 0 30 1 blend
- 1 5 -9 6 -10 2 -9 4 0 8 -6 0 -13 6 0 13 -11 0 -25 2 0 6 -8 0 -19 6 blend
+ 0 -47 13 0.02318 30 1 blend
+ 1 5 -9 6 -10 2 -9 4 0 8 -6 -0.0107 -13 6 0.0107 13 -11 -0.0196 -25 2 0 6 -8 -0.01427 -19 6 blend
rrcurveto
hintmask 00000011000001000010001000000000
50 30 -5 0 -11 1 0 2 2 blend
0 6 17 3 0 8 5 1 12 2 blend
hvcurveto
- 17 5 4 9 21 6 -1 12 4 0 9 1 0 3 4 0 8 11 0 25 5 blend
+ 17 5 4 9 21 6 -0.9893 12 4 0 9 1 0 3 4 0 8 11 0.0196 25 5 blend
vvcurveto
- 159 -21 0 -46 1 blend
+ 159 -21 -0.03743 -46 1 blend
vlineto
- -132 -50 -39 0 -88 1 0 1 2 blend
+ -132 -50 -39 -0.06952 -88 1 0 1 2 blend
rmoveto
- -25 -42 -40 -39 -44 -30 8 -4 13 -10 5 -4 41 6 0 12 3 0 8 7 0 16 3 0 5 5 0 13 1 0 4 6 0 13 -3 0 -8 10 0 22 -6 0 -14 5 0 12 -5 0 -10 -3 0 -8 13 blend
- 30 45 -7 0 -14 1 blend
+ -25 -42 -40 -39 -44 -30 8 -4 13 -10 5 -4 41 6 0.0107 12 3 0 8 7 0.01248 16 3 0 5 5 0 13 1 0 4 6 0.0107 13 -3 0 -8 10 0.01782 22 -6 -0.0107 -14 5 0 12 -5 0 -10 -3 0 -8 13 blend
+ 30 45 -7 -0.01248 -14 1 blend
47 26 45 -3 0 -8 1 0 1 2 blend
rrcurveto
- 153 -7 -13 0 -30 -1 0 -2 2 blend
+ 153 -7 -13 -0.02318 -30 -1 0 -2 2 blend
rmoveto
- 35 -27 38 -39 18 -28 -8 3 -11 3 -5 -3 -9 3 -14 6 -7 -3 -5 1 -9 4 -4 0 6 blend
+ 35 -27 38 -39 18 -28 -8 2.98573 -11 3 -5 -3 -9 2.98396 -14 6 -6.9893 -3 -5 1 -9 4 -4 0 6 blend
rrcurveto
- 24 18 -18 27 -39 39 -34 25 23 1 55 6 -1 12 4 -1 8 -3 4 1 9 -3 13 -6 7 2 7 -3 9 -4 5 4 8 blend
+ 24 18 -18 27 -39 39 -34 25 23 1.041 55 6 -0.9893 12 4 -1 8 -3 4 1 9 -2.98396 13 -6 6.9893 2 7 -2.98752 9 -4 5 4 8 blend
rlinecurve
- 115 330 -53 -1 -124 9 1 21 2 blend
+ 115 330 -53 -1.09447 -124 9 1.01604 21 2 blend
rmoveto
hintmask 10000101000001000000001010000000
- 14 -286 131 -209 160 0 50 1 18 34 6 108 -9 3 -11 5 -9 7 -4 -92 -9 -34 -31 -1 -137 -2 -126 185 -12 281 3 0 8 6 0 14 5 0 10 -10 0 -22 -3 0 -6 0 0 -1 14 0 33 -1 0 -1 11 0 23 -3 0 -8 5 0 12 10 0 24 -10 0 -23 3 0 7 -14 0 -32 8 0 18 -8 0 -17 8 0 17 0 0 -1 11 0 26 0 0 1 4 0 9 5 0 11 1 0 1 29 0 67 0 1 2 8 0 17 0 -1 -1 -2 0 -4 -37 0 -85 30 blend
+ 14 -286 131 -209 160 0 50 1 18 34 6 108 -9 3 -11 5 -9 7 -4 -92 -9 -34 -31 -1 -137 -2 -126 185 -12 281 3 0 8 6 0.0107 14 5 0 10 -10 -0.01782 -22 -3 0 -6 0 0 -1 14 0.02495 33 -1 0 -1 11 0.0196 23 -3 0 -8 5 0 12 10 0.01782 24 -10 -0.01782 -23 3 0 7 -14 -0.02495 -32 8 0.01427 18 -8 -0.01427 -17 8 0.01427 17 0 0 -1 11 0.0196 26 0 0 1 4 0 9 5 0 11 1 0 1 29 0.0517 67 0 1 2 8 0.01427 17 0 -1 -1 -2 0 -4 -37 -0.06595 -85 30 blend
rrcurveto
- 207 -169 -37 0 -85 -4 0 -9 2 blend
+ 207 -169 -37 -0.06595 -85 -4 0 -9 2 blend
rmoveto
- -61 -129 -111 -108 -121 -69 7 -5 12 -11 5 -6 119 74 113 110 66 136 4 15 19 8 14 33 4 28 29 8 5 22 2 28 27 6 2 17 8 1 20 -6 0 -14 14 1 34 -13 -1 -31 6 0 15 -7 0 -17 0 -28 -23 -4 -2 -10 0 -27 -20 -1 -3 -5 -1 -16 -12 -1 -15 -19 18 blend
+ -61 -129 -111 -108 -121 -69 7 -5 12 -11 5 -6 119 74 113 110 66 136 4 15 19 8 14.01427 33 4 28 29 8 5.01427 22 2 28 27 6 2.0107 17 8 1.01427 20 -6 -0.0107 -14 14 1.02495 34 -13 -1.02318 -31 6 0.0107 15 -7 -0.01248 -17 0 -28 -23 -4 -2 -10 0 -27 -20 -1 -3 -5 -1 -16 -12 -1 -15 -19 18 blend
rrcurveto
- -156 153 -20 -2 -49 -2 0 -3 2 blend
+ -156 153 -20 -2.03564 -49 -2 0 -3 2 blend
rmoveto
52 -15 63 -26 34 -1 0 -3 -1 0 -1 0 0 1 0 0 -2 0 0 -2 5 blend
-21 rrcurveto
- 15 27 -34 20 -64 24 -51 14 21 0 48 20 0 47 -1 0 -1 1 0 1 0 0 -1 0 0 1 1 0 3 -1 0 -2 8 blend
+ 15 27 -34 20 -64 24 -51 14 21 0.03743 48 20 0.03564 47 -1 0 -1 1 0 1 0 0 -1 0 0 1 1 0 3 -1 0 -2 8 blend
rlinecurve
- -453 -763 1 0 2 12 0 27 2 blend
+ -453 -763 1 0 2 12 0.0214 27 2 blend
rmoveto
- -25 -16 -31 0 -71 -7 0 -17 2 blend
+ -25 -16 -31 -0.05525 -71 -7 -0.01248 -17 2 blend
rlineto
- -100 89 146 -18 233 -21 0 -46 -5 0 -12 -13 0 -29 -4 0 -9 -8 0 -18 5 blend
+ -100 89 146 -18 233 -21 -0.03743 -46 -5 0 -12 -13 -0.02318 -29 -4 0 -9 -8 -0.01427 -18 5 blend
hhcurveto
- 249 23 0 53 1 blend
+ 249 23 0.03743 53 1 blend
hlineto
- 2 8 6 14 6 8 -35 0 -207 2 -1 3 11 0 25 5 1 12 17 0 38 4 0 10 8 0 18 -16 0 -37 -1 0 -3 -1 0 -2 9 blend
- 0 -22 -14 0 -32 1 blend
- 0 -214 0 -150 15 -78 89 24 0 55 0 0 1 18 0 40 -2 0 -5 12 0 28 -1 0 -2 6 blend
+ 2 8 6 14 6 8 -35 0 -207 2 -1 3 11 0.0196 25 5 1 12 17 0.0303 38 4 0 10 8 0.01427 18 -16 -0.02852 -37 -1 0 -3 -1 0 -2 9 blend
+ 0 -22 -14 -0.02495 -32 1 blend
+ 0 -214 0 -150 15 -78 89 24 0.04279 55 0 0 1 18 0.03209 40 -2 0 -5 12 0.0214 28 -1 0 -2 6 blend
rrcurveto
- 5 62 -50 0 -114 -10 0 -22 2 blend
+ 5 62 -50 -0.08913 -114 -10 -0.01782 -22 2 blend
rmoveto
- -30 -97 -92 -60 -107 -36 8 -6 12 -11 4 -6 105 41 99 65 32 106 5 0 12 7 0 15 15 0 34 1 0 3 7 0 16 1 0 2 10 0 22 -6 0 -15 18 0 41 -17 0 -37 8 0 18 -8 0 -19 -2 0 -5 4 0 9 -12 0 -27 7 0 16 -1 0 -2 6 0 14 18 blend
+ -30 -97 -92 -60 -107 -36 8 -6 12 -11 4 -6 105 41 99 65 32 106 5 0 12 7 0.01248 15 15 0.02673 34 1 0 3 7 0.01248 16 1 0 2 10 0.01782 22 -6 -0.0107 -15 18 0.03209 41 -17 -0.0303 -37 8 0.01427 18 -8 -0.01427 -19 -2 0 -5 4 0 9 -12 -0.0214 -27 7 0.01248 16 -1 0 -2 6 0.0107 14 18 blend
rrcurveto
</CharString>
<CharString name="cid07253" fdSelectIndex="1">
1 vsindex
- -80 27 95 49 -48 48 -45 45 -30 30 -16 16 -13 13 49 30 48 30 47 19 -19 30 53 30 -18 18 51 11 -11 30 -22 22 62 30 60 30 15 81 -30 30 -30 102 -10 1 -14 41 -2 59 -53 2 -76 27 -1 38 -26 1 -37 26 -1 37 -27 1 -39 27 -1 39 -27 1 -39 27 -1 39 -13 0 -19 13 0 19 -14 0 -20 14 0 20 -19 1 -27 13 -1 19 -18 1 -26 13 0 19 -18 0 -26 18 0 26 -18 0 -26 23 -1 33 -21 1 -30 42 -2 60 -29 1 -42 29 -1 42 -19 1 -27 7 0 10 -7 0 -10 26 -1 37 -24 1 -34 24 -1 34 -27 1 -39 24 -1 34 -26 1 -37 26 -1 37 -40 1 -45 53 -2 66 -44 2 -62 44 -2 62 -44 2 -62 18 0 23 42 blend
+ -80 27 95 49 -48 48 -45 45 -30 30 -16 16 -13 13 49 30 48 30 47 19 -19 30 53 30 -18 18 51 11 -11 30 -22 22 62 30 60 30 15 81 -30 30 -30 102 -10 0.96979 -14 41 -1.87613 59 -53 1.83987 -76 27 -0.91843 38 -26 0.92145 -37 26 -0.92145 37 -27 0.91843 -39 27 -0.91843 39 -27 0.91843 -39 27 -0.91843 39 -13 -0.03928 -19 13 0.03928 19 -14 -0.0423 -20 14 0.0423 20 -19 0.9426 -27 13 -0.96072 19 -18 0.94562 -26 13 0.03928 19 -18 -0.05438 -26 18 0.05438 26 -18 -0.05438 -26 23 -0.93051 33 -21 0.93655 -30 42 -1.87311 60 -29 0.91238 -42 29 -0.91238 42 -19 0.9426 -27 7 0.02115 10 -7 -0.02115 -10 26 -0.92145 37 -24 0.92749 -34 24 -0.92749 34 -27 0.91843 -39 24 -0.92749 34 -26 0.92145 -37 26 -0.92145 37 -40 0.87915 -45 53 -1.83987 66 -44 1.86707 -62 44 -1.86707 62 -44 1.86707 -62 18 0.05438 23 42 blend
hstemhm
- 193 30 -1 30 -15 15 106 29 96 30 142 30 109 30 5 10 -28 1 -40 71 -2 102 -56 2 -80 75 -4 106 -21 2 -29 21 -2 29 -104 5 -148 55 -3 78 -42 3 -59 69 -4 98 -84 4 -120 79 -3 113 -94 3 -135 76 -3 109 -51 2 -73 25 -1 36 16 blend
+ 193 30 -1 30 -15 15 106 29 96 30 142 30 109 30 5 10 -28 0.9154 -40 71 -1.78549 102 -56 1.83081 -80 75 -3.7734 106 -21 1.93655 -29 21 -1.93655 29 -104 4.6858 -148 55 -2.83383 78 -42 2.87311 -59 69 -3.79153 98 -84 3.74623 -120 79 -2.76132 113 -94 2.71602 -135 76 -2.77039 109 -51 1.84592 -73 25 -0.92447 36 16 blend
vstemhm
hintmask 10000011101100101101000101110000
- 55 767 2 0 3 37 -2 55 2 blend
+ 55 767 2 0 3 37 -1.88821 55 2 blend
rmoveto
- -30 892 30 -44 2 -62 -6 0 -9 44 -2 62 3 blend
+ -30 892 30 -44 1.86707 -62 -6 -0.01813 -9 44 -1.86707 62 3 blend
vlineto
hintmask 00000000000000000000100000000000
- -637 72 -28 1 -40 -26 2 -39 2 blend
+ -637 72 -28 0.9154 -40 -26 1.92145 -39 2 blend
rmoveto
hintmask 00000000000000000010000000000000
- -153 30 -27 0 -27 77 -2 111 2 blend
+ -153 30 -27 -0.08157 -27 77 -1.76736 111 2 blend
vlineto
hintmask 00000000000000000000100000100000
- 153 27 0 27 1 blend
+ 153 27 0.08157 27 1 blend
vlineto
- 315 -89 3 -128 1 blend
+ 315 -89 2.73112 -128 1 blend
hmoveto
hintmask 00000000000000000010000000100000
- -153 30 -27 0 -27 79 -3 113 2 blend
+ -153 30 -27 -0.08157 -27 79 -2.76132 113 2 blend
vlineto
hintmask 00000000000100101100110000110000
- 153 27 0 27 1 blend
+ 153 27 0.08157 27 1 blend
vlineto
- -462 -288 8 0 12 -11 0 -16 2 blend
+ -462 -288 8 0.02417 12 -11 -0.03323 -16 2 blend
rmoveto
- 571 -62 -571 -102 3 -147 27 -1 39 102 -3 147 3 blend
+ 571 -62 -571 -102 2.69185 -147 27 -0.91843 39 102 -2.69185 147 3 blend
hlineto
- 152 -29 1 -42 1 blend
+ 152 -29 0.91238 -42 1 blend
vmoveto
- 571 -60 -571 -102 3 -147 26 -1 37 102 -3 147 3 blend
+ 571 -60 -571 -102 2.69185 -147 26 -0.92145 37 102 -2.69185 147 3 blend
hlineto
- -30 -71 2 -102 1 blend
+ -30 -71 1.78549 -102 1 blend
90 rmoveto
- -212 631 212 -23 1 -32 45 -2 64 23 -1 32 3 blend
+ -212 631 212 -23 0.93051 -32 45 -1.86404 64 23 -0.93051 32 3 blend
vlineto
- -776 -263 -22 1 -31 -4 0 -5 2 blend
+ -776 -263 -22 0.93353 -31 -4 -0.01208 -5 2 blend
rmoveto
- -30 905 30 -42 2 -60 10 0 14 42 -2 60 3 blend
+ -30 905 30 -42 1.87311 -60 10 0.03021 14 42 -1.87311 60 3 blend
vlineto
hintmask 00000001100000000000000100000000
- -716 -160 36 -1 52 -26 2 -37 2 blend
+ -716 -160 36 -0.89124 52 -26 1.92145 -37 2 blend
rmoveto
- -30 554 30 -13 0 -19 -59 2 -85 13 0 19 3 blend
+ -30 554 30 -13 -0.03928 -19 -59 1.82175 -85 13 0.03928 19 3 blend
vlineto
- -554 -78 59 -2 85 5 -1 7 2 blend
+ -554 -78 59 -1.82175 85 5 -0.9849 7 2 blend
rmoveto
- -30 563 30 -13 1 -19 -56 1 -81 13 -1 19 3 blend
+ -30 563 30 -13 0.96072 -19 -56 0.83081 -81 13 -0.96072 19 3 blend
vlineto
hintmask 00000010000000000000001000000000
- -578 -79 2 1 4 6 0 8 2 blend
+ -578 -79 2 1 4 6 0.01813 8 2 blend
rmoveto
hintmask 00001000000000000000001000001000
- -30 617 -27 1 -39 4 -1 5 2 blend
+ -30 617 -27 0.91843 -39 4 -0.98792 5 2 blend
vlineto
hintmask 00000010000001000000000000001000
- 30 27 -1 39 1 blend
+ 30 27 -0.91843 39 1 blend
vlineto
- -477 382 -24 2 -34 8 0 12 2 blend
+ -477 382 -24 1.92749 -34 8 0.02417 12 2 blend
rmoveto
- -46 -92 -113 -104 -167 -65 7 -5 10 -9 5 -8 6 -1 8 -5 -1 -8 17 0 25 11 0 16 -3 -1 -5 6 0 9 12 0 18 -11 0 -16 18 0 26 -27 1 -39 6 -1 8 -16 1 -23 12 blend
+ -46 -92 -113 -104 -167 -65 7 -5 10 -9 5 -8 6 -0.98187 8 -5 -1.0151 -8 17 0.05136 25 11 0.03323 16 -3 -1 -5 6 0.01813 9 12 0.03625 18 -11 -0.03323 -16 18 0.05438 26 -27 0.91843 -39 6 -0.98187 8 -16 0.95166 -23 12 blend
rrcurveto
hintmask 00000100010010010000000001000000
- 172 70 111 106 55 101 14 0 20 3 0 5 -6 0 -8 1 0 1 3 0 4 28 -1 41 6 blend
+ 172 70 111 106 55 101 14 0.0423 20 3 0 5 -6 -0.01813 -8 1 0 1 3 0 4 28 -0.9154 41 6 blend
rrcurveto
- 298 -65 -24 0 -35 3 0 4 2 blend
+ 298 -65 -24 -0.07251 -35 3 0 4 2 blend
rmoveto
- -25 -12 -55 2 -79 -15 0 -22 2 blend
+ -25 -12 -55 1.83383 -79 -15 -0.04532 -22 2 blend
rlineto
- 62 -80 121 -81 100 -38 5 8 9 11 7 6 -101 33 -119 76 -59 77 2 0 3 -14 1 -20 -10 1 -14 2 0 3 20 0 29 1 0 2 9 -1 13 18 -1 25 20 -1 28 26 -1 38 14 0 21 14 -1 19 -13 0 -19 -7 0 -10 9 0 13 -18 2 -25 4 -1 5 -7 0 -10 18 blend
+ 62 -80 121 -81 100 -38 5 8 9 11 7 6 -101 33 -119 76 -59 77 2 0 3 -14 0.9577 -20 -10 0.96979 -14 2 0 3 20 0.06042 29 1 0 2 9 -0.97281 13 18 -0.94562 25 20 -0.93958 28 26 -0.92145 38 14 0.0423 21 14 -0.9577 19 -13 -0.03928 -19 -7 -0.02115 -10 9 0.02719 13 -18 1.94562 -25 4 -0.98792 5 -7 -0.02115 -10 18 blend
rrcurveto
- -211 -88 -39 3 -55 -12 1 -17 2 blend
+ -211 -88 -39 2.88217 -55 -12 0.96375 -17 2 blend
rmoveto
- -239 30 239 -2 -1 -4 69 -4 98 2 1 4 3 blend
+ -239 30 239 -2 -1 -4 69 -3.79153 98 2 1 4 3 blend
vlineto
hintmask 10000010000000000000000000001000
- 316 -223 -74 3 -106 11 -1 15 2 blend
+ 316 -223 -74 2.77643 -106 11 -0.96677 15 2 blend
rmoveto
- -6 -4 0 -6 1 blend
+ -6 -4 -0.01208 -6 1 blend
vlineto
- -8 -87 -7 -34 -10 -10 2 0 3 24 -1 35 -1 1 -1 6 0 9 1 -1 1 1 0 1 6 blend
+ -8 -87 -7 -34 -10 -10 2 0 3 24 -0.92749 35 -1 1 -1 6 0.01813 9 1 -1 1 1 0 1 6 blend
rrcurveto
-6 -1 0 -1 1 blend
-6 -6 -1 -12 2 0 3 1 blend
hhcurveto
- -11 -31 1 0 1 10 0 15 2 blend
- 1 3 -34 0 -1 -1 9 0 13 2 blend
+ -11 -31 1 0 1 10 0.03021 15 2 blend
+ 1 3 -34 0 -1 -1 9 0.02719 13 2 blend
hvcurveto
- 5 -8 3 -13 6 -1 8 -11 1 -16 5 0 8 -19 1 -26 4 blend
- 1 -8 28 -2 30 -1 14 1 -14 1 -20 7 -1 9 1 0 1 2 0 3 2 -1 2 3 1 5 0 1 1 7 blend
- 21 0 10 4 10 9 16 15 7 35 2 -1 2 8 -1 11 2 0 3 5 0 7 5 0 8 3 -1 4 3 0 4 2 1 4 3 0 4 9 blend
- 9 89 -15 1 -21 1 blend
+ 5 -8 3 -13 6 -0.98187 8 -11 0.96677 -16 5 0.0151 8 -19 0.9426 -26 4 blend
+ 1 -8 28 -2 30 -1 14 1 -14 0.9577 -20 7 -0.97885 9 1 0 1 2 0 3 2 -1 2 3 1 5 0 1 1 7 blend
+ 21 0 10 4 10 9 16 15 7 35 2 -1 2 8 -0.97583 11 2 0 3 5 0.0151 7 5 0.0151 8 3 -1 4 3 0 4 2 1 4 3 0 4 9 blend
+ 9 89 -15 0.95468 -21 1 blend
rrcurveto
- 7 1 1 12 6 -1 8 1 0 1 1 -1 1 9 0 13 4 blend
+ 7 1 1 12 6 -0.98187 8 1 0 1 1 -1 1 9 0.02719 13 4 blend
0 hhcurveto
- -660 -34 -57 3 -82 -8 0 -11 2 blend
+ -660 -34 -57 2.82779 -82 -8 -0.02417 -11 2 blend
rmoveto
- -17 -46 1 0 2 7 0 10 2 blend
- -32 -46 -46 5 0 7 5 0 7 2 blend
- -23 20 -21 56 -2 81 -24 0 -35 2 blend
+ -17 -46 1 0 2 7 0.02115 10 2 blend
+ -32 -46 -46 5 0.0151 7 5 0.0151 7 2 blend
+ -23 20 -21 56 -1.83081 81 -24 -0.07251 -35 2 blend
rcurveline
hintmask 10010000000000000000000000000000
- 52 28 31 51 17 46 -4 -1 -7 0 1 1 -4 1 -5 -7 0 -10 1 -1 1 0 0 -1 6 blend
+ 52 28 31 51 17 46 -4 -1.01208 -7 0 1 1 -4 0.98792 -5 -7 -0.02115 -10 1 -1 1 0 0 -1 6 blend
rrcurveto
hintmask 00100000000000000000000010000000
- 110 -3 -67 3 -96 1 0 2 2 blend
+ 110 -3 -67 2.79758 -96 1 0 2 2 blend
rmoveto
- 13 -38 10 -49 0 -32 -3 0 -4 4 -1 5 -2 0 -3 2 1 4 -1 1 -1 3 0 4 6 blend
+ 13 -38 10 -49 0 -32 -3 0 -4 4 -0.98792 5 -2 0 -3 2 1 4 -1 1 -1 3 0 4 6 blend
rrcurveto
- 29 6 55 -3 78 8 -1 11 2 blend
- -1 31 -10 50 -15 37 -3 0 -4 0 1 1 -4 0 -6 3 -1 4 -4 1 -5 5 blend
+ 29 6 55 -2.83383 78 8 -0.97583 11 2 blend
+ -1 31 -10 50 -15 37 -3 0 -4 0 1 1 -4 -0.01208 -6 3 -1 4 -4 0.98792 -5 5 blend
rlinecurve
hintmask 01000000000000000000000000100000
- 113 -6 -56 3 -80 -7 0 -10 2 blend
+ 113 -6 -56 2.83081 -80 -7 -0.02115 -10 2 blend
rmoveto
22 -32 20 -44 7 -30 2 0 3 1 -1 1 3 -1 3 1 0 1 2 0 3 5 blend
rrcurveto
- 28 10 -8 29 -21 44 -23 32 48 -2 69 15 0 22 -2 1 -2 -1 0 -2 0 -1 -1 -5 1 -6 -1 1 -1 -4 0 -6 8 blend
+ 28 10 -8 29 -21 44 -23 32 48 -1.85498 69 15 0.04532 22 -2 1 -2 -1 0 -2 0 -1 -1 -5 0.9849 -6 -1 1 -1 -4 -0.01208 -6 8 blend
rlinecurve
hintmask 00010000001000000000001000000000
- 117 -5 -45 1 -65 -17 1 -24 2 blend
+ 117 -5 -45 0.86404 -65 -17 0.94864 -24 2 blend
rmoveto
25 -23 -1 0 -1 2 -1 2 2 blend
27 -32 13 -23 -2 1 -2 1 0 2 2 blend
rrcurveto
- 21 14 -12 44 -2 63 20 -1 28 0 0 -1 3 blend
+ 21 14 -12 44 -1.86707 63 20 -0.93958 28 0 0 -1 3 blend
22 -27 32 -26 22 -2 0 -2 -2 0 -3 1 0 1 -2 1 -2 4 blend
rlinecurve
- -381 267 39 -1 56 7 -1 10 2 blend
+ -381 267 39 -0.88217 56 7 -0.97885 10 2 blend
rmoveto
- -16 -30 -33 1 -47 -23 1 -33 2 blend
+ -16 -30 -33 0.9003 -47 -23 0.93051 -33 2 blend
rlineto
- 498 30 -42 1 -61 23 -1 33 2 blend
+ 498 30 -42 0.87311 -61 23 -0.93051 33 2 blend
hlineto
- -516 -23 21 0 31 -14 0 -21 2 blend
+ -516 -23 21 0.06345 31 -14 -0.0423 -21 2 blend
rmoveto
hintmask 00000010000000000000001000000000
- -224 6 0 9 1 blend
+ -224 6 0.01813 9 1 blend
vlineto
hintmask 00000010001000000000000100000000
- 30 247 75 -4 106 10 0 14 2 blend
+ 30 247 75 -3.7734 106 10 0.03021 14 2 blend
hlineto
</CharString>
<CharString name="cid13393" fdSelectIndex="1">
4 vsindex
- -50 30 -19 19 114 30 44 30 23 30 -30 114 35 30 316 30 -10 10 37 12 -21 0 -26 66 0 82 -29 21 -10 29 -21 10 -64 0 -80 55 0 69 -79 0 -99 75 0 94 -46 0 -58 56 0 71 -56 0 -71 26 21 59 -18 -25 -54 54 0 68 -76 8 -85 58 0 73 -24 0 -31 24 0 31 -46 -4 -63 30 0 37 20 blend
+ -50 30 -19 19 114 30 44 30 23 30 -30 114 35 30 316 30 -10 10 37 12 -21 -0.10448 -26 66 0.32835 82 -29 20.85573 -10 29 -20.85573 10 -64 -0.3184 -80 55 0.27364 69 -79 -0.39304 -99 75 0.37314 94 -46 -0.22885 -58 56 0.27861 71 -56 -0.27861 -71 26 21.12935 59 -18 -25.08955 -54 54 0.26866 68 -76 7.62189 -85 58 0.28856 73 -24 -0.1194 -31 24 0.1194 31 -46 -4.22885 -63 30 0.14925 37 20 blend
hstemhm
- 82 30 197 30 -26 8 317 30 168 13 -13 0 -16 77 0 96 -109 -1 -136 78 0 97 -77 0 -96 29 0 36 -10 0 -12 84 0 105 -86 0 -108 21 0 27 10 blend
+ 82 30 197 30 -26 8 317 30 168 13 -13 -0.06468 -16 77 0.38309 96 -109 -0.54228 -136 78 0.38806 97 -77 -0.38309 -96 29 0.14427 36 -10 -0.04974 -12 84 0.41791 105 -86 -0.42786 -108 21 0.10448 27 10 blend
vstemhm
hintmask 1010101101110110
- 529 746 23 0 29 30 4 43 2 blend
+ 529 746 23 0.11443 29 30 4.14925 43 2 blend
rmoveto
- -30 320 30 -58 0 -73 -29 0 -36 58 0 73 3 blend
+ -30 320 30 -58 -0.28856 -73 -29 -0.14427 -36 58 0.28856 73 3 blend
vlineto
- -397 -495 15 0 18 12 -4 10 2 blend
+ -397 -495 15 0.07463 18 12 -3.94029 10 2 blend
rmoveto
- -30 442 30 -56 0 -71 21 0 27 56 0 71 3 blend
+ -30 442 30 -56 -0.27861 -71 21 0.10448 27 56 0.27861 71 3 blend
vlineto
- -420 149 -6 0 -8 6 -4 2 2 blend
+ -420 149 -6 -0.02985 -8 6 -3.97015 2 2 blend
rmoveto
- -30 374 30 -54 0 -68 -25 0 -31 54 0 68 3 blend
+ -30 374 30 -54 -0.26866 -68 -25 -0.12437 -31 54 0.26866 68 3 blend
vlineto
- -514 -420 34 0 42 -3 4 1 2 blend
+ -514 -420 34 0.16916 42 -3 3.98508 1 2 blend
rmoveto
- -30 626 30 -66 0 -82 -29 0 -36 66 0 82 3 blend
+ -30 626 30 -66 -0.32835 -82 -29 -0.14427 -36 66 0.32835 82 3 blend
vlineto
- -531 144 15 0 19 -9 0 -11 2 blend
+ -531 144 15 0.07463 19 -9 -0.04477 -11 2 blend
rmoveto
- -30 460 30 -55 0 -69 -4 0 -5 55 0 69 3 blend
+ -30 460 30 -55 -0.27364 -69 -4 -0.0199 -5 55 0.27364 69 3 blend
vlineto
- -53 622 -42 0 -53 -6 4 -2 2 blend
+ -53 622 -42 -0.20895 -53 -6 3.97015 -2 2 blend
rmoveto
- -7 -9 0 -12 1 blend
+ -7 -9 -0.04477 -12 1 blend
vlineto
- -86 -171 -222 -118 -188 -45 7 -7 8 -11 3 -8 14 0 18 37 0 46 27 0 34 19 0 24 -7 0 -9 5 0 7 15 0 18 -16 0 -20 17 0 22 -32 0 -40 9 0 11 -19 0 -24 12 blend
+ -86 -171 -222 -118 -188 -45 7 -7 8 -11 3 -8 14 0.06966 18 37 0.18408 46 27 0.13432 34 19 0.09453 24 -7 -0.03482 -9 5 0.02487 7 15 0.07463 18 -16 -0.0796 -20 17 0.08458 22 -32 -0.15921 -40 9 0.04477 11 -19 -0.09453 -24 12 blend
rrcurveto
hintmask 0000000010000010
- 192 51 224 119 94 187 21 0 26 3 0 3 -17 0 -21 -9 0 -11 2 0 2 -3 0 -4 6 blend
+ 192 51 224 119 94 187 21 0.10448 26 3 0.01492 3 -17 -0.08458 -21 -9 -0.04477 -11 2 0 2 -3 -0.01492 -4 6 blend
rrcurveto
hintmask 0100010100000110
- -19 12 -6 -2 -55 0 -68 27 0 34 -12 0 -15 -3 0 -3 4 blend
+ -19 12 -6 -2 -55 -0.27364 -68 27 0.13432 34 -12 -0.05971 -15 -3 -0.01492 -3 4 blend
rlineto
- -323 -32 55 0 69 -25 0 -32 2 blend
+ -323 -32 55 0.27364 69 -25 -0.12437 -32 2 blend
rmoveto
- -25 -11 -68 0 -86 -23 0 -28 2 blend
+ -25 -11 -68 -0.3383 -86 -23 -0.11443 -28 2 blend
rlineto
- 83 -154 177 -116 201 -44 4 8 9 12 7 6 -200 39 -177 113 -79 147 11 0 14 12 0 15 -18 0 -22 21 0 26 -1 0 -1 4 0 5 11 0 13 21 0 26 21 0 27 32 0 40 17 0 21 16 0 20 9 0 11 -10 0 -12 17 0 21 -36 0 -45 1 0 2 -37 0 -47 18 blend
+ 83 -154 177 -116 201 -44 4 8 9 12 7 6 -200 39 -177 113 -79 147 11 0.05473 14 12 0.05971 15 -18 -0.08955 -22 21 0.10448 26 -1 0 -1 4 0.0199 5 11 0.05473 13 21 0.10448 26 21 0.10448 27 32 0.15921 40 17 0.08458 21 16 0.0796 20 9 0.04477 11 -10 -0.04974 -12 17 0.08458 21 -36 -0.17911 -45 1 0 2 -37 -0.18408 -47 18 blend
rrcurveto
- 59 127 -46 0 -58 9 -4 6 2 blend
+ 59 127 -46 -0.22885 -58 9 -3.95523 6 2 blend
rmoveto
- -40 -82 -80 -104 -112 -75 8 -4 10 -9 6 -7 115 80 2 0 2 8 0 10 7 0 9 23 0 29 2 0 3 16 0 20 16 0 20 -12 0 -15 26 0 32 -30 0 -37 10 0 13 -18 0 -23 8 0 10 -4 0 -5 14 blend
- 80 106 47 90 -13 0 -16 11 0 13 14 0 17 3 blend
+ -40 -82 -80 -104 -112 -75 8 -4 10 -9 6 -7 115 80 2 0 2 8 0.0398 10 7 0.03482 9 23 0.11443 29 2 0 3 16 0.0796 20 16 0.0796 20 -12 -0.05971 -15 26 0.12935 32 -30 -0.14925 -37 10 0.04974 13 -18 -0.08955 -23 8 0.0398 10 -4 -0.0199 -5 14 blend
+ 80 106 47 90 -13 -0.06468 -16 11 0.05473 13 14 0.06966 17 3 blend
rrcurveto
- -129 -493 -106 -5 -137 21 6 34 2 blend
+ -129 -493 -106 -4.52736 -137 21 6.10448 34 2 blend
rmoveto
- -27 -73 -43 -71 -51 -50 8 -5 13 -9 5 -5 49 52 47 77 29 77 6 0 8 11 0 14 7 0 8 8 0 10 5 0 7 8 0 10 16 0 20 -8 0 -10 28 0 35 -17 -1 -22 15 0 18 -11 0 -14 -3 0 -4 -4 0 -5 -2 0 -2 -1 0 -1 -3 0 -4 -3 1 -3 18 blend
+ -27 -73 -43 -71 -51 -50 8 -5 13 -9 5 -5 49 52 47 77 29 77 6 0.02985 8 11 0.05473 14 7 0.03482 8 8 0.0398 10 5 0.02487 7 8 0.0398 10 16 0.0796 20 -8 -0.0398 -10 28 0.1393 35 -17 -1.08458 -22 15 0.07463 18 -11 -0.05473 -14 -3 -0.01492 -4 -4 -0.0199 -5 -2 0 -2 -1 0 -1 -3 -0.01492 -4 -3 0.98508 -3 18 blend
rrcurveto
- 124 -1 -66 4 -77 10 15 31 2 blend
+ 124 -1 -66 3.67165 -77 10 15.04974 31 2 blend
rmoveto
- -374 30 374 4 0 5 84 0 105 -4 0 -5 3 blend
+ -374 30 374 4 0.0199 5 84 0.41791 105 -4 -0.0199 -5 3 blend
vlineto
hintmask 0000000000101000
- -586 460 -72 0 -90 2 -21 -24 2 blend
+ -586 460 -72 -0.35822 -90 2 -21 -24 2 blend
rmoveto
- -875 30 845 209 30 -27 0 -33 77 0 96 -53 0 -66 -79 0 -99 80 0 99 5 blend
+ -875 30 845 209 30 -27 -0.13432 -33 77 0.38309 96 -53 -0.26369 -66 -79 -0.39304 -99 80 0.39801 99 5 blend
vlineto
- -8 -29 0 -36 1 blend
+ -8 -29 -0.14427 -36 1 blend
hmoveto
- -7 -29 0 -36 1 blend
+ -7 -29 -0.14427 -36 1 blend
vlineto
- -28 -75 -43 -102 -46 -95 14 0 17 10 0 13 11 0 14 -41 0 -51 17 0 22 4 0 5 6 blend
+ -28 -75 -43 -102 -46 -95 14 0.06966 17 10 0.04974 13 11 0.05473 14 -41 -0.20398 -51 17 0.08458 22 4 0.0199 5 6 blend
rrcurveto
hintmask 0001000000010000
- 89 -91 24 -74 -63 -32 0 -40 23 0 28 -11 0 -14 10 0 13 17 0 21 5 blend
+ 89 -91 24 -74 -63 -32 -0.15921 -40 23 0.11443 28 -11 -0.05473 -14 10 0.04974 13 17 0.08458 21 5 blend
vvcurveto
- -33 -6 -35 -19 -13 3 0 4 1 0 1 15 0 18 7 0 9 4 0 5 5 blend
+ -33 -6 -35 -19 -13 3 0.01492 4 1 0 1 15 0.07463 18 7 0.03482 9 4 0.0199 5 5 blend
vhcurveto
- -10 -6 -12 3 0 3 0 0 1 1 0 2 3 blend
- -3 -14 -1 -20 -2 -26 1 -29 4 0 5 1 0 1 7 0 9 2 0 2 13 0 16 -1 0 -1 11 0 14 7 blend
- 2 7 -9 4 -13 11 0 13 -21 0 -26 5 0 6 -33 0 -41 4 blend
- 1 -8 22 -2 27 0 22 -21 0 -27 1 0 2 1 0 1 -3 0 -4 0 0 1 -4 0 -5 6 blend
- 2 19 2 17 5 12 9 3 0 4 2 0 2 3 0 3 2 0 2 4 0 5 3 0 4 6 blend
+ -10 -6 -12 3 0.01492 3 0 0 1 1 0 2 3 blend
+ -3 -14 -1 -20 -2 -26 1 -29 4 0.0199 5 1 0 1 7 0.03482 9 2 0 2 13 0.06468 16 -1 0 -1 11 0.05473 14 7 blend
+ 2 7 -9 4 -13 11 0.05473 13 -21 -0.10448 -26 5 0.02487 6 -33 -0.16418 -41 4 blend
+ 1 -8 22 -2 27 0 22 -21 -0.10448 -27 1 0 2 1 0 1 -3 -0.01492 -4 0 0 1 -4 -0.0199 -5 6 blend
+ 2 19 2 17 5 12 9 3 0.01492 4 2 0 2 3 0.01492 3 2 0 2 4 0.0199 5 3 0.01492 4 6 blend
rrcurveto
- 25 17 10 7 0 9 5 0 7 4 0 5 3 blend
- 43 44 22 0 27 1 blend
+ 25 17 10 7 0.03482 9 5 0.02487 7 4 0.0199 5 3 blend
+ 43 44 22 0.10945 27 1 blend
vvcurveto
- 67 -22 76 -86 89 -8 0 -10 9 0 12 -6 0 -8 24 0 30 -11 0 -13 5 blend
+ 67 -22 76 -86 89 -8 -0.0398 -10 9 0.04477 12 -6 -0.02985 -8 24 0.1194 30 -11 -0.05473 -13 5 blend
vhcurveto
hintmask 0000000001001000
- 39 84 42 98 33 81 -10 0 -13 -4 0 -5 -8 0 -10 14 0 17 -6 0 -8 7 0 9 6 blend
+ 39 84 42 98 33 81 -10 -0.04974 -13 -4 -0.0199 -5 -8 -0.0398 -10 14 0.06966 17 -6 -0.02985 -8 7 0.03482 9 6 blend
rrcurveto
hintmask 0000000000001000
- -20 14 -6 -2 -60 0 -75 32 0 40 -12 0 -14 -2 0 -3 4 blend
+ -20 14 -6 -2 -60 -0.29851 -75 32 0.15921 40 -12 -0.05971 -14 -2 0 -3 4 blend
rlineto
</CharString>
<CharString name="cid17290" fdSelectIndex="1">
5 vsindex
- 121 30 -22 22 148 30 -30 136 23 30 129 30 116 30 -21 4 -29 52 3 92 -32 23 -21 32 -23 21 -54 9 -83 50 4 90 -50 -4 -90 22 27 62 -2 -43 -47 41 0 69 -44 0 -74 37 0 62 -50 0 -84 36 0 61 14 blend
+ 121 30 -22 22 148 30 -30 136 23 30 129 30 116 30 -21 3.94763 -29 52 3.12967 92 -32 22.9202 -21 32 -22.9202 21 -54 8.86534 -83 50 4.1247 90 -50 -4.1247 -90 22 27.05486 62 -2 -43 -47 41 0.10225 69 -44 -0.10973 -74 37 0.09227 62 -50 -0.1247 -84 36 0.08978 61 14 blend
hstemhm
- 167 30 129 30 -16 16 123 30 48 30 -6 29 -29 111 -30 30 -16 16 201 30 1 29 -29 0 -49 64 0 108 -34 0 -57 51 0 85 -29 0 -48 29 0 48 -72 -2 -123 60 2 103 -69 0 -115 46 0 77 -42 0 -70 42 0 70 -42 0 -70 67 0 111 -51 0 -85 51 0 85 -29 0 -48 29 0 48 -79 0 -132 47 0 79 -45 0 -75 42 0 70 22 blend
+ 167 30 129 30 -16 16 123 30 48 30 -6 29 -29 111 -30 30 -16 16 201 30 1 29 -29 -0.07233 -49 64 0.1596 108 -34 -0.0848 -57 51 0.12718 85 -29 -0.07233 -48 29 0.07233 48 -72 -2.17955 -123 60 2.14963 103 -69 -0.17207 -115 46 0.11472 77 -42 -0.10474 -70 42 0.10474 70 -42 -0.10474 -70 67 0.16708 111 -51 -0.12718 -85 51 0.12718 85 -29 -0.07233 -48 29 0.07233 48 -79 -0.197 -132 47 0.1172 79 -45 -0.11221 -75 42 0.10474 70 22 blend
vstemhm
hintmask 011011111011001010000000
- 326 793 1 0 2 17 0 29 2 blend
+ 326 793 1 0 2 17 0.04239 29 2 blend
rmoveto
- -280 24 0 40 1 blend
+ -280 24 0.05984 40 1 blend
vlineto
- -47 16 -8 59 -31 0 -53 6 0 10 -13 0 -21 20 0 33 4 blend
+ -47 16 -8 59 -31 -0.0773 -53 6 0.01497 10 -13 -0.03242 -21 20 0.04988 33 4 blend
vhcurveto
hintmask 000010000000100000000000
- 13 120 4 0 6 -46 0 -76 2 blend
+ 13 120 4 0 6 -46 -0.11472 -76 2 blend
0 13 4 0 7 1 blend
hhcurveto
- 49 10 20 82 4 12 0 19 12 0 20 3 0 5 2 0 3 4 0 8 5 blend
+ 49 10 20 82 4 12 0.02992 19 12 0.02992 20 3 0 5 2 0 3 4 0 8 5 blend
hvcurveto
hintmask 101010101000010000000000
- -10 2 -11 5 -8 6 -12 0 -21 3 0 5 -21 0 -35 6 0 11 -9 0 -14 7 0 11 6 blend
+ -10 2 -11 5 -8 6 -12 -0.02992 -21 3 0 5 -21 -0.05237 -35 6 0.01497 11 -9 -0.02245 -14 7 0.01746 11 6 blend
rrcurveto
- -75 19 0 32 1 blend
- -3 -5 -10 -29 -24 -102 1 0 1 1 0 2 7 0 12 9 0 14 42 0 70 5 blend
- 0 -18 6 0 10 1 blend
+ -75 19 0.04738 32 1 blend
+ -3 -5 -10 -29 -24 -102 1 0 1 1 0 2 7 0.01746 12 9 0.02245 14 42 0.10474 70 5 blend
+ 0 -18 6 0.01497 10 1 blend
hhcurveto
- -38 -6 4 21 10 0 18 2 0 3 0 0 -1 4 0 7 4 blend
+ -38 -6 4 21 10 0.02493 18 2 0 3 0 0 -1 4 0 7 4 blend
hvcurveto
- 280 -25 0 -41 1 blend
+ 280 -25 -0.06235 -41 1 blend
vlineto
- -41 -464 -40 -8 -74 10 20 41 2 blend
+ -41 -464 -40 -8.09975 -74 10 20.02493 41 2 blend
rmoveto
- -30 617 30 -50 -4 -90 -5 12 5 50 4 90 3 blend
+ -30 617 30 -50 -4.1247 -90 -5 11.98753 5 50 4.1247 90 3 blend
vlineto
- -661 -178 11 -4 12 4 -13 -7 2 blend
+ -661 -178 11 -3.97256 12 4 -13 -7 2 blend
rmoveto
- -30 689 30 -52 -3 -92 -11 0 -18 52 3 92 3 blend
+ -30 689 30 -52 -3.12967 -92 -11 -0.02744 -18 52 3.12967 92 3 blend
vlineto
hintmask 010101100111001000000000
- -481 284 -27 -2 -48 -32 36 -21 2 blend
+ -481 284 -27 -2.06734 -48 -32 35.9202 -21 2 blend
rmoveto
- -306 30 306 0 -13 0 60 2 103 0 13 0 3 blend
+ -306 30 306 0 -13 0 60 2.14963 103 0 13 0 3 blend
vlineto
- 218 0 -61 0 -102 -1 0 -1 2 blend
+ 218 0 -61 -0.15211 -102 -1 0 -1 2 blend
rmoveto
- -306 30 306 0 -13 0 61 1 104 0 13 0 3 blend
+ -306 30 306 0 -13 0 61 1.15211 104 0 13 0 3 blend
vlineto
- -417 358 -17 -1 -30 19 -43 -12 2 blend
+ -417 358 -17 -1.04239 -30 19 -42.95262 -12 2 blend
rmoveto
- -30 217 -116 -217 -30 247 176 -36 0 -61 -52 0 -87 50 0 84 52 0 87 -37 0 -62 -6 0 -10 23 0 39 7 blend
+ -30 217 -116 -217 -30 247 176 -36 -0.08978 -61 -52 -0.12967 -87 50 0.1247 84 52 0.12967 87 -37 -0.09227 -62 -6 -0.01497 -10 23 0.05736 39 7 blend
vlineto
- 75 -26 0 -44 1 blend
+ 75 -26 -0.06483 -44 1 blend
hmoveto
hintmask 000010100000001001000000
- -280 24 0 40 1 blend
+ -280 24 0.05984 40 1 blend
vlineto
- -47 17 -8 60 -31 0 -53 5 0 9 -13 0 -21 20 0 33 4 blend
+ -47 17 -8 60 -31 -0.0773 -53 5 0.01247 9 -13 -0.03242 -21 20 0.04988 33 4 blend
vhcurveto
- 12 125 5 0 8 -47 0 -78 2 blend
+ 12 125 5 0.01247 8 -47 -0.1172 -78 2 blend
0 14 4 0 7 1 blend
hhcurveto
- 49 11 20 82 3 12 0 20 12 0 19 3 1 6 2 1 6 5 0 9 5 blend
+ 49 11 20 82 3 12 0.02992 20 12 0.02992 19 3 1 6 2 1 6 5 0.01247 9 5 blend
hvcurveto
- -9 2 -12 4 -8 7 -14 1 -22 3 0 5 -19 -1 -34 7 0 12 -9 0 -14 6 0 10 6 blend
+ -9 2 -12 4 -8 7 -14 0.96509 -22 3 0 5 -19 -1.04738 -34 7 0.01746 12 -9 -0.02245 -14 6 0.01497 10 6 blend
rrcurveto
- -75 19 -1 29 1 blend
- -3 -5 -10 -30 -25 -105 1 -1 1 8 0 13 8 0 14 42 0 70 4 blend
- 0 -18 6 0 9 1 blend
+ -75 19 -0.95262 29 1 blend
+ -3 -5 -10 -30 -25 -105 1 -1 1 8 0.01994 13 8 0.01994 14 42 0.10474 70 4 blend
+ 0 -18 6 0.01497 9 1 blend
hhcurveto
- -40 -6 4 21 11 0 19 2 0 3 0 0 -1 4 1 8 4 blend
+ -40 -6 4 21 11 0.02744 19 2 0 3 0 0 -1 4 1 8 4 blend
hvcurveto
- 280 -25 -1 -42 1 blend
+ 280 -25 -1.06235 -42 1 blend
vlineto
hintmask 000001110000000110000000
- -16 -29 0 -48 1 blend
+ -16 -29 -0.07233 -48 1 blend
hmoveto
- -30 217 -116 -217 -30 247 176 -36 0 -61 -50 0 -84 50 0 84 50 0 84 -37 0 -62 -3 0 -5 23 0 39 7 blend
+ -30 217 -116 -217 -30 247 176 -36 -0.08978 -61 -50 -0.1247 -84 50 0.1247 84 50 0.1247 84 -37 -0.09227 -62 -3 0 -5 23 0.05736 39 7 blend
vlineto
- -424 -714 -19 0 -32 -12 0 -21 2 blend
+ -424 -714 -19 -0.04738 -32 -12 -0.02992 -21 2 blend
rmoveto
- -52 -54 -91 -49 -81 -33 8 -5 11 -13 4 -6 80 36 94 56 56 58 7 0 11 9 0 15 5 0 9 11 0 18 -2 0 -3 9 0 15 13 0 22 -11 0 -18 24 0 39 -22 0 -36 11 0 19 -12 0 -21 4 0 7 -4 0 -6 2 0 2 -2 0 -4 -1 0 -1 3 0 5 18 blend
+ -52 -54 -91 -49 -81 -33 8 -5 11 -13 4 -6 80 36 94 56 56 58 7 0.01746 11 9 0.02245 15 5 0.01247 9 11 0.02744 18 -2 0 -3 9 0.02245 15 13 0.03242 22 -11 -0.02744 -18 24 0.05984 39 -22 -0.05486 -36 11 0.02744 19 -12 -0.02992 -21 4 0 7 -4 0 -6 2 0 2 -2 0 -4 -1 0 -1 3 0 5 18 blend
rrcurveto
- 200 -7 -92 0 -154 -5 0 -8 2 blend
+ 200 -7 -92 -0.22943 -154 -5 -0.01247 -8 2 blend
rmoveto
- 76 -41 90 -62 46 -42 -6 0 -10 5 0 8 -5 0 -7 6 0 10 -4 0 -7 4 0 7 6 blend
+ 76 -41 90 -62 46 -42 -6 -0.01497 -10 5 0.01247 8 -5 -0.01247 -7 6 0.01497 10 -4 0 -7 4 0 7 6 blend
rrcurveto
- 22 23 -46 42 -91 60 -75 39 60 0 100 29 0 48 0 0 -1 -3 0 -5 3 0 5 -7 0 -11 6 0 11 -7 0 -11 8 blend
+ 22 23 -46 42 -91 60 -75 39 60 0.14963 100 29 0.07233 48 0 0 -1 -3 0 -5 3 0 5 -7 -0.01746 -11 6 0.01497 11 -7 -0.01746 -11 8 blend
rlinecurve
- -499 750 -48 0 -81 6 0 10 2 blend
+ -499 750 -48 -0.1197 -81 6 0.01497 10 2 blend
rmoveto
- -54 -167 -87 -164 -96 -108 7 -6 11 -12 4 -6 98 116 88 165 58 175 7 0 13 15 0 25 10 0 16 14 0 22 11 0 19 10 0 17 9 0 15 -20 0 -33 15 0 24 -44 0 -73 4 0 7 -18 0 -30 4 0 6 4 0 6 3 0 6 19 0 32 0 0 -1 1 0 1 18 blend
+ -54 -167 -87 -164 -96 -108 7 -6 11 -12 4 -6 98 116 88 165 58 175 7 0.01746 13 15 0.0374 25 10 0.02493 16 14 0.03491 22 11 0.02744 19 10 0.02493 17 9 0.02245 15 -20 -0.04988 -33 15 0.0374 24 -44 -0.10973 -73 4 0 7 -18 -0.04489 -30 4 0 6 4 0 6 3 0 6 19 0.04738 32 0 0 -1 1 0 1 18 blend
rrcurveto
- -113 -214 -60 0 -100 -23 0 -37 2 blend
+ -113 -214 -60 -0.14963 -100 -23 -0.05736 -37 2 blend
rmoveto
- -691 30 718 20 0 33 64 0 108 43 0 72 3 blend
+ -691 30 718 20 0.04988 33 64 0.1596 108 43 0.10724 72 3 blend
vlineto
-1 -1 0 -3 1 blend
2 rlineto
</CharString>
<CharString name="cid17852" fdSelectIndex="1">
5 vsindex
- -67 29 219 30 154 30 -16 16 150 30 -30 122 -85 30 -18 18 87 30 -30 140 -122 12 -14 0 -22 46 0 78 -59 -3 -106 46 0 77 -53 -9 -92 46 2 81 -18 20 -1 18 -20 1 -54 13 -80 46 2 81 -46 -2 -81 25 31 61 -14 -34 -48 60 0 100 -64 0 -107 64 0 107 -55 0 -92 54 0 90 -54 0 -90 36 0 59 -19 0 -31 37 0 62 22 blend
+ -67 29 219 30 154 30 -16 16 150 30 -30 122 -85 30 -18 18 87 30 -30 140 -122 12 -14 -0.03491 -22 46 0.11472 78 -59 -3.14713 -106 46 0.11472 77 -53 -9.13217 -92 46 2.11472 81 -18 19.95511 -1 18 -19.95511 1 -54 12.86534 -80 46 2.11472 81 -46 -2.11472 -81 25 31.06235 61 -14 -34.03491 -48 60 0.14963 100 -64 -0.1596 -107 64 0.1596 107 -55 -0.13716 -92 54 0.13466 90 -54 -0.13466 -90 36 0.08978 59 -19 -0.04738 -31 37 0.09227 62 22 blend
hstemhm
- 51 188 -30 30 -30 149 21 30 -18 18 -13 13 66 30 -12 12 135 30 41 30 172 30 -6 28 -8 0 -14 30 0 50 -62 0 -103 62 0 103 -62 0 -103 32 0 53 -5 0 -7 59 0 98 -24 0 -41 24 0 41 -16 0 -27 16 0 27 -32 0 -53 53 0 88 -33 0 -56 33 0 56 -87 0 -146 63 0 106 -42 0 -70 54 0 90 -99 0 -165 55 0 91 -42 0 -70 45 0 75 24 blend
+ 51 188 -30 30 -30 149 21 30 -18 18 -13 13 66 30 -12 12 135 30 41 30 172 30 -6 28 -8 -0.01994 -14 30 0.07481 50 -62 -0.15462 -103 62 0.15462 103 -62 -0.15462 -103 32 0.0798 53 -5 -0.01247 -7 59 0.14713 98 -24 -0.05984 -41 24 0.05984 41 -16 -0.0399 -27 16 0.0399 27 -32 -0.0798 -53 53 0.13217 88 -33 -0.08229 -56 33 0.08229 56 -87 -0.21696 -146 63 0.1571 106 -42 -0.10474 -70 54 0.13466 90 -99 -0.24689 -165 55 0.13716 91 -42 -0.10474 -70 45 0.11221 75 24 blend
vstemhm
hintmask 000000100001000000000000
- 51 612 -8 0 -14 29 0 49 2 blend
+ 51 612 -8 -0.01994 -14 29 0.07233 49 2 blend
rmoveto
- -30 -60 0 -100 1 blend
+ -30 -60 -0.14963 -100 1 blend
vlineto
hintmask 000000100000010000000000
- 307 30 60 0 100 1 blend
+ 307 30 60 0.14963 100 1 blend
hlineto
hintmask 000000010010100100000000
- -149 228 -32 0 -53 -20 0 -34 2 blend
+ -149 228 -32 -0.0798 -53 -20 -0.04988 -34 2 blend
rmoveto
- -918 30 918 -19 0 -32 62 0 103 19 0 32 3 blend
+ -918 30 918 -19 -0.04738 -32 62 0.15462 103 19 0.04738 32 3 blend
vlineto
- -36 -238 -55 0 -91 -32 0 -53 2 blend
+ -36 -238 -55 -0.13716 -91 -32 -0.0798 -53 2 blend
rmoveto
- -31 -160 -74 -193 -68 -95 7 -5 10 -11 6 -8 70 101 74 203 33 160 6 0 10 25 0 42 13 0 21 23 0 37 4 0 7 1 0 2 8 0 14 -18 0 -30 13 0 21 -27 0 -44 4 0 7 -19 0 -32 1 0 2 6 0 10 -12 0 -20 -2 0 -3 -2 0 -4 -1 0 -2 18 blend
+ -31 -160 -74 -193 -68 -95 7 -5 10 -11 6 -8 70 101 74 203 33 160 6 0.01497 10 25 0.06235 42 13 0.03242 21 23 0.05736 37 4 0 7 1 0 2 8 0.01994 14 -18 -0.04489 -30 13 0.03242 21 -27 -0.06734 -44 4 0 7 -19 -0.04738 -32 1 0 2 6 0.01497 10 -12 -0.02992 -20 -2 0 -3 -2 0 -4 -1 0 -2 18 blend
rrcurveto
- 4 -143 19 0 32 77 0 128 2 blend
+ 4 -143 19 0.04738 32 77 0.19202 128 2 blend
rmoveto
- -21 -16 25 -26 72 -92 21 -33 -23 0 -38 -34 0 -57 1 0 2 -15 0 -24 -12 0 -21 -6 0 -11 2 0 3 -18 0 -29 8 blend
+ -21 -16 25 -26 72 -92 21 -33 -23 -0.05736 -38 -34 -0.0848 -57 1 0 2 -15 -0.0374 -24 -12 -0.02992 -21 -6 -0.01497 -11 2 0 3 -18 -0.04489 -29 8 blend
rlinecurve
- 24 24 -18 25 -81 96 -22 22 28 0 48 63 0 105 2 0 2 -1 0 -2 1 0 3 10 0 16 1 0 1 1 0 2 8 blend
+ 24 24 -18 25 -81 96 -22 22 28 0.06982 48 63 0.1571 105 2 0 2 -1 0 -2 1 0 3 10 0.02493 16 1 0 1 1 0 2 8 blend
rlinecurve
- 157 278 1 0 1 -14 0 -23 2 blend
+ 157 278 1 0 1 -14 -0.03491 -23 2 blend
rmoveto
hintmask 000000001000000100000000
- -30 559 -54 0 -90 -17 3 -23 2 blend
+ -30 559 -54 -0.13466 -90 -17 2.95761 -23 2 blend
vlineto
hintmask 010000000010000000100000
- 30 54 0 90 1 blend
+ 30 54 0.13466 90 1 blend
vlineto
- -457 -518 29 -3 43 -9 -3 -20 2 blend
+ -457 -518 29 -2.92767 43 -9 -3.02245 -20 2 blend
rmoveto
- -30 176 30 -46 0 -77 -17 0 -27 46 0 77 3 blend
+ -30 176 30 -46 -0.11472 -77 -17 -0.04239 -27 46 0.11472 77 3 blend
vlineto
hintmask 000000000100000001010000
- -194 120 -3 0 -5 -42 37 -35 2 blend
+ -194 120 -3 0 -5 -42 36.89526 -35 2 blend
rmoveto
- -365 30 365 38 -29 45 53 0 88 -38 29 -45 3 blend
+ -365 30 365 38 -28.90524 45 53 0.13217 88 -38 28.90524 -45 3 blend
vlineto
- 135 508 -87 0 -146 33 -34 24 2 blend
+ 135 508 -87 -0.21696 -146 33 -33.91771 24 2 blend
rmoveto
hintmask 000000000010000000010000
- -122 30 -19 0 -31 63 0 106 2 blend
+ -122 30 -19 -0.04738 -31 63 0.1571 106 2 blend
vlineto
hintmask 000101000100000000010000
- 122 19 0 31 1 blend
+ 122 19 0.04738 31 1 blend
vlineto
- -115 -172 -60 0 -100 -27 34 -19 2 blend
+ -115 -172 -60 -0.14963 -100 -27 33.93266 -19 2 blend
rmoveto
- -288 30 288 11 -24 18 50 0 83 -11 24 -18 3 blend
+ -288 30 288 11 -23.97256 18 50 0.1247 83 -11 23.97256 -18 3 blend
vlineto
- 148 -62 -2 -106 1 blend
+ 148 -62 -2.15462 -106 1 blend
hmoveto
- -288 30 288 11 -24 18 50 0 83 -11 24 -18 3 blend
+ -288 30 288 11 -23.97256 18 50 0.1247 83 -11 23.97256 -18 3 blend
vlineto
- 156 -394 -30 2 -47 19 -34 6 2 blend
+ 156 -394 -30 1.92519 -47 19 -33.95262 6 2 blend
rmoveto
- -52 -36 -89 -48 -61 -29 7 0 12 2 0 4 14 0 23 3 0 4 11 0 18 4 0 8 6 blend
+ -52 -36 -89 -48 -61 -29 7 0.01746 12 2 0 4 14 0.03491 23 3 0 4 11 0.02744 18 4 0 8 6 blend
rrcurveto
- 15 -21 62 28 86 41 57 44 25 0 42 -39 0 -66 -10 0 -17 -4 0 -6 -12 0 -19 -3 0 -5 -6 0 -11 -5 0 -9 8 blend
+ 15 -21 62 28 86 41 57 44 25 0.06235 42 -39 -0.09726 -66 -10 -0.02493 -17 -4 0 -6 -12 -0.02992 -19 -3 0 -5 -6 -0.01497 -11 -5 -0.01247 -9 8 blend
rlinecurve
hintmask 101010000000000010001100
- -541 323 10 0 17 44 5 84 2 blend
+ -541 323 10 0.02493 17 44 5.10973 84 2 blend
rmoveto
- -30 517 -150 -517 -30 547 210 -46 -2 -81 -74 0 -123 54 -13 80 74 0 123 -46 -2 -81 -19 0 -32 38 17 82 7 blend
+ -30 517 -150 -517 -30 547 210 -46 -2.11472 -81 -74 -0.18454 -123 54 -12.86534 80 74 0.18454 123 -46 -2.11472 -81 -19 -0.04738 -32 38 17.09476 82 7 blend
vlineto
- -232 -242 -10 0 -16 -28 29 -27 2 blend
+ -232 -242 -10 -0.02493 -16 -28 28.93018 -27 2 blend
rmoveto
- -344 58 -32 71 1 blend
+ -344 58 -31.85536 71 1 blend
vlineto
- -47 15 -9 54 -33 -2 -58 3 0 4 -15 0 -25 22 0 37 4 blend
+ -47 15 -9 54 -33 -2.08229 -58 3 0 4 -15 -0.0374 -25 22 0.05486 37 4 blend
vhcurveto
hintmask 100000000010001000001010
- 12 100 3 0 5 -47 0 -78 2 blend
+ 12 100 3 0 5 -47 -0.1172 -78 2 blend
0 12 4 0 6 1 blend
hhcurveto
- 48 10 25 102 3 12 0 20 11 0 19 4 1 9 11 -1 16 5 0 8 5 blend
+ 48 10 25 102 3 12 0.02992 20 11 0.02744 19 4 1 9 11 -0.97256 16 5 0.01247 8 5 blend
hvcurveto
- -9 3 -11 4 -8 6 -14 0 -23 3 -1 5 -23 1 -37 8 1 15 -8 -1 -15 8 0 12 6 blend
+ -9 3 -11 4 -8 6 -14 -0.03491 -23 3 -1 5 -23 0.94264 -37 8 1.01994 15 -8 -1.01994 -15 8 0.01994 12 6 blend
rrcurveto
- -97 11 1 20 1 blend
- -3 -4 -14 -29 -21 -84 0 0 1 1 -1 1 10 0 16 10 1 17 43 -1 71 5 blend
- 0 -16 7 0 12 1 blend
+ -97 11 1.02744 20 1 blend
+ -3 -4 -14 -29 -21 -84 0 0 1 1 -1 1 10 0.02493 16 10 1.02493 17 43 -0.89276 71 5 blend
+ 0 -16 7 0.01746 12 1 blend
hhcurveto
- -33 -6 5 22 13 0 22 3 0 5 -1 0 -2 4 0 7 4 blend
+ -33 -6 5 22 13 0.03242 22 3 0 5 -1 0 -2 4 0 7 4 blend
hvcurveto
- 344 -59 34 -71 1 blend
+ 344 -59 33.85287 -71 1 blend
vlineto
- -346 -371 -24 0 -41 65 -34 78 2 blend
+ -346 -371 -24 -0.05984 -41 65 -33.8379 78 2 blend
rmoveto
- 10 -31 77 16 100 22 99 21 3 0 5 -54 0 -90 -2 0 -3 -3 0 -5 -9 0 -15 -6 0 -10 -10 0 -17 -5 0 -8 8 blend
+ 10 -31 77 16 100 22 99 21 3 0 5 -54 -0.13466 -90 -2 0 -3 -3 0 -5 -9 -0.02245 -15 -6 -0.01497 -10 -10 -0.02493 -17 -5 -0.01247 -8 8 blend
rlinecurve
- -2 29 -108 -22 -104 -22 -72 -13 -3 0 -5 52 0 86 9 0 16 6 0 10 8 0 13 6 0 11 4 0 6 4 0 6 8 blend
+ -2 29 -108 -22 -104 -22 -72 -13 -3 0 -5 52 0.12967 86 9 0.02245 16 6 0.01497 10 8 0.01994 13 6 0.01497 11 4 0 6 4 0 6 8 blend
rlinecurve
- -16 767 -44 0 -72 -13 0 -21 2 blend
+ -16 767 -44 -0.10973 -72 -13 -0.03242 -21 2 blend
rmoveto
- -316 -6 0 -11 1 blend
+ -316 -6 -0.01497 -11 1 blend
vlineto
- -142 -7 -194 -74 -141 2 0 2 -2 0 -2 2 0 4 6 0 9 4 blend
+ -142 -7 -194 -74 -141 2 0 2 -2 0 -2 2 0 4 6 0.01497 9 4 blend
vhcurveto
- 8 -3 13 -7 5 -6 13 0 21 -7 0 -11 25 0 43 -20 0 -34 11 0 17 -10 0 -17 6 blend
+ 8 -3 13 -7 5 -6 13 0.03242 21 -7 -0.01746 -11 25 0.06235 43 -20 -0.04988 -34 11 0.02744 17 -10 -0.02493 -17 6 blend
rrcurveto
- 75 143 10 205 145 4 0 7 3 0 5 2 0 4 21 0 35 9 0 15 5 blend
+ 75 143 10 205 145 4 0 7 3 0 5 2 0 4 21 0.05237 35 9 0.02245 15 5 blend
vvcurveto
- 316 6 0 11 1 blend
+ 316 6 0.01497 11 1 blend
vlineto
</CharString>
<CharString name="cid18480" fdSelectIndex="1">
3 vsindex
- -71 30 427 30 153 30 33 111 -30 30 -30 126 -6 0 -13 45 0 102 -58 0 -132 38 0 87 -48 0 -111 38 0 87 -4 -2 -13 21 2 53 -43 0 -99 43 0 99 -43 0 -99 24 0 55 12 blend
+ -71 30 427 30 153 30 33 111 -30 30 -30 126 -6 -0.0107 -13 45 0.08022 102 -58 -0.1034 -132 38 0.06773 87 -48 -0.08556 -111 38 0.06773 87 -4 -2 -13 21 2.03743 53 -43 -0.07664 -99 43 0.07664 99 -43 -0.07664 -99 24 0.04279 55 12 blend
hstemhm
- 159 30 -19 19 126 30 -6 30 281 30 160 30 18 31 -7 0 -16 50 0 114 -18 0 -42 18 0 42 -71 0 -161 50 0 114 -26 0 -61 48 0 111 -66 0 -150 51 0 115 -68 0 -154 50 0 114 -36 -1 -84 44 1 101 14 blend
+ 159 30 -19 19 126 30 -6 30 281 30 160 30 18 31 -7 -0.01248 -16 50 0.08913 114 -18 -0.03209 -42 18 0.03209 42 -71 -0.12656 -161 50 0.08913 114 -26 -0.04634 -61 48 0.08556 111 -66 -0.11765 -150 51 0.09091 115 -68 -0.12122 -154 50 0.08913 114 -36 -1.06418 -84 44 1.07843 101 14 blend
vstemhm
hintmask 1110100101110000
- 58 743 -1 0 -2 26 0 60 2 blend
+ 58 743 -1 0 -2 26 0.04634 60 2 blend
rmoveto
- -30 887 30 -43 0 -99 2 0 5 43 0 99 3 blend
+ -30 887 30 -43 -0.07664 -99 2 0 5 43 0.07664 99 3 blend
vlineto
hintmask 0000010010000000
- -630 96 -29 0 -66 -19 0 -44 2 blend
+ -630 96 -29 -0.0517 -66 -19 -0.03387 -44 2 blend
rmoveto
hintmask 0001000010000000
- -207 30 -2 -2 -9 50 0 114 2 blend
+ -207 30 -2 -2 -9 50 0.08913 114 2 blend
vlineto
hintmask 0000010010100000
207 2 2 9 1 blend
vlineto
- 305 -44 0 -100 1 blend
+ 305 -44 -0.07843 -100 1 blend
hmoveto
hintmask 0001000000100000
- -207 30 -2 -2 -9 51 0 115 2 blend
+ -207 30 -2 -2 -9 51 0.09091 115 2 blend
vlineto
hintmask 0010011000100000
207 2 2 9 1 blend
vlineto
- -521 -240 -36 0 -82 2 0 4 2 blend
+ -521 -240 -36 -0.06418 -82 2 0 4 2 blend
rmoveto
-206 -5 0 -10 1 blend
vlineto
- -137 -15 -184 -109 -136 5 0 11 3 0 6 5 0 10 -1 0 -1 8 0 19 5 blend
+ -137 -15 -184 -109 -136 5 0 11 3 0 6 5 0 10 -1 0 -1 8 0.01427 19 5 blend
vhcurveto
- 7 -3 12 -9 5 -6 12 0 27 -6 0 -13 22 0 51 -15 0 -35 10 0 21 -8 0 -19 6 blend
+ 7 -3 12 -9 5 -6 12 0.0214 27 -6 -0.0107 -13 22 0.03922 51 -15 -0.02673 -35 10 0.01782 21 -8 -0.01427 -19 6 blend
rrcurveto
hintmask 1110000101010000
- 112 139 18 194 141 3 0 7 -4 0 -8 1 0 3 11 0 24 4 0 10 5 blend
+ 112 139 18 194 141 3 0 7 -4 0 -8 1 0 3 11 0.0196 24 4 0 10 5 blend
vvcurveto
207 5 0 11 1 blend
vlineto
- -19 -18 0 -42 1 blend
+ -19 -18 -0.03209 -42 1 blend
hmoveto
- -30 670 -153 -670 -30 700 213 -38 0 -87 -64 0 -144 48 0 111 64 0 144 -38 0 -87 -14 0 -30 28 0 63 7 blend
+ -30 670 -153 -670 -30 700 213 -38 -0.06773 -87 -64 -0.11407 -144 48 0.08556 111 64 0.11407 144 -38 -0.06773 -87 -14 -0.02495 -30 28 0.04991 63 7 blend
vlineto
- -531 -249 -15 0 -36 -23 0 -51 2 blend
+ -531 -249 -15 -0.02673 -36 -23 -0.041 -51 2 blend
rmoveto
- -343 50 0 112 1 blend
+ -343 50 0.08913 112 1 blend
vlineto
- -66 31 -12 105 -29 0 -66 6 0 14 -13 0 -28 29 0 66 4 blend
+ -66 31 -12 105 -29 -0.0517 -66 6 0.0107 14 -13 -0.02318 -28 29 0.0517 66 4 blend
vhcurveto
- 23 278 5 0 12 -59 0 -134 2 blend
- 0 24 6 0 14 1 blend
+ 23 278 5 0 12 -59 -0.10516 -134 2 blend
+ 0 24 6 0.0107 14 1 blend
hhcurveto
hintmask 1000000001001000
- 96 15 31 123 8 20 0 44 11 0 26 4 0 8 14 0 32 5 0 11 5 blend
+ 96 15 31 123 8 20 0.03564 44 11 0.0196 26 4 0 8 14 0.02495 32 5 0 11 5 blend
hvcurveto
- -9 3 -13 4 -9 7 -13 0 -30 2 0 5 -21 0 -48 8 0 17 -10 -1 -23 6 0 15 6 blend
+ -9 3 -13 4 -9 7 -13 -0.02318 -30 2 0 5 -21 -0.03743 -48 8 0.01427 17 -10 -1.01782 -23 6 0.0107 15 6 blend
rrcurveto
- -117 -6 -11 -21 -69 -56 -236 8 0 18 -1 1 -1 1 -1 1 3 0 7 3 1 9 7 0 15 49 0 112 7 blend
+ -117 -6 -11 -21 -69 -56 -236 8 0.01427 18 -1 1 -1 1 -1 1 3 0 7 3 1 9 7 0.01248 15 49 0.08734 112 7 blend
0 -41 4 0 8 1 blend
hhcurveto
-84 -16 11 37 4 0 10 2 0 5 -3 0 -7 1 0 2 4 blend
hvcurveto
- 343 -51 0 -115 1 blend
+ 343 -51 -0.09091 -115 1 blend
vlineto
- 444 -47 -59 0 -135 26 0 59 2 blend
+ 444 -47 -59 -0.10516 -135 26 0.04634 59 2 blend
rmoveto
- -101 -52 -195 -56 -169 -40 4 -7 5 -10 3 -7 172 40 193 54 120 56 4 0 8 3 0 7 18 0 43 9 0 19 12 0 26 8 0 19 5 0 12 -10 0 -22 7 0 15 -18 0 -41 1 0 3 -11 0 -25 -8 0 -19 -9 0 -21 -8 0 -18 -8 0 -19 5 0 11 0 0 1 18 blend
+ -101 -52 -195 -56 -169 -40 4 -7 5 -10 3 -7 172 40 193 54 120 56 4 0 8 3 0 7 18 0.03209 43 9 0.01604 19 12 0.0214 26 8 0.01427 19 5 0 12 -10 -0.01782 -22 7 0.01248 15 -18 -0.03209 -41 1 0 3 -11 -0.0196 -25 -8 -0.01427 -19 -9 -0.01604 -21 -8 -0.01427 -18 -8 -0.01427 -19 5 0 11 0 0 1 18 blend
rrcurveto
</CharString>
<CharString name="cid22370" fdSelectIndex="1">
2 vsindex
- 64 30 77 30 76 30 74 30 72 30 109 30 25 84 -30 30 -30 108 -2 0 -2 42 0 47 -48 0 -54 38 0 43 -48 0 -54 38 0 43 -46 0 -52 42 0 47 -43 0 -48 56 1 63 -72 -1 -81 57 1 64 -8 -32 -41 30 32 65 -65 -1 -73 65 1 73 -65 -1 -73 43 0 49 18 blend
+ 64 30 77 30 76 30 74 30 72 30 109 30 25 84 -30 30 -30 108 -2 -0.01802 -2 42 0.37837 47 -48 -0.43243 -54 38 0.34235 43 -48 -0.43243 -54 38 0.34235 43 -46 -0.41441 -52 42 0.37837 47 -43 -0.38739 -48 56 0.5045 63 -72 -0.64865 -81 57 0.51352 64 -8 -32.07207 -41 30 32.27026 65 -65 -0.58559 -73 65 0.58559 73 -65 -0.58559 -73 43 0.38739 49 18 blend
hstemhm
- 135 30 21 30 102 30 14 30 205 30 17 30 113 30 19 30 -19 0 -21 87 2 98 -86 -2 -97 99 1 111 -125 -1 -141 98 1 111 -79 -1 -89 75 1 84 -99 -1 -111 75 1 84 -77 -1 -86 100 1 112 -127 -1 -143 105 1 118 -102 -1 -114 94 1 105 16 blend
+ 135 30 21 30 102 30 14 30 205 30 17 30 113 30 19 30 -19 -0.17117 -21 87 1.78378 98 -86 -1.77478 -97 99 0.89189 111 -125 -1.12613 -141 98 0.88289 111 -79 -0.71172 -89 75 0.67567 84 -99 -0.89189 -111 75 0.67567 84 -77 -0.6937 -86 100 0.9009 112 -127 -1.14415 -143 105 0.94595 118 -102 -0.91891 -114 94 0.84685 105 16 blend
vstemhm
hintmask 111111010011001100000000
- 53 761 -3 0 -3 36 0 40 2 blend
+ 53 761 -3 -0.02702 -3 36 0.32433 40 2 blend
rmoveto
- -30 896 30 -65 -1 -73 5 0 5 65 1 73 3 blend
+ -30 896 30 -65 -0.58559 -73 5 0.04504 5 65 0.58559 73 3 blend
vlineto
hintmask 000000001001000000000000
- -631 78 -46 0 -52 -22 0 -24 2 blend
+ -631 78 -46 -0.41441 -52 -22 -0.1982 -24 2 blend
rmoveto
hintmask 000000100001000000000000
- -162 30 -8 -32 -41 98 1 111 2 blend
+ -162 30 -8 -32.07207 -41 98 0.88289 111 2 blend
vlineto
hintmask 000000001001001000000000
- 162 8 32 41 1 blend
+ 162 8 32.07207 41 1 blend
vlineto
- 296 -105 -1 -118 1 blend
+ 296 -105 -0.94595 -118 1 blend
hmoveto
hintmask 000000100000001000000000
- -162 30 -8 -32 -41 100 1 112 2 blend
+ -162 30 -8 -32.07207 -41 100 0.9009 112 2 blend
vlineto
hintmask 000000001000001000000000
- 162 8 32 41 1 blend
+ 162 8 32.07207 41 1 blend
vlineto
hintmask 000011000100110010000000
- -47 -217 -23 0 -26 -57 -1 -64 2 blend
+ -47 -217 -23 -0.20721 -26 -57 -0.51352 -64 2 blend
rmoveto
- 209 -109 -209 -101 -1 -113 72 1 81 101 1 113 3 blend
+ 209 -109 -209 -101 -0.90991 -113 72 0.64865 81 101 0.90991 113 3 blend
hlineto
- -235 109 24 0 27 -72 -1 -81 2 blend
+ -235 109 24 0.21622 27 -72 -0.64865 -81 2 blend
rmoveto
- 205 -109 -205 -99 -1 -111 72 1 81 99 1 111 3 blend
+ 205 -109 -205 -99 -0.89189 -111 72 0.64865 81 99 0.89189 111 3 blend
hlineto
- -227 109 18 1 21 -72 -1 -81 2 blend
+ -227 109 18 1.16216 21 -72 -0.64865 -81 2 blend
rmoveto
- 197 -109 -197 -93 -2 -105 72 1 81 93 2 105 3 blend
+ 197 -109 -197 -93 -1.83784 -105 72 0.64865 81 93 1.83784 105 3 blend
hlineto
- -30 139 -87 -2 -98 -15 0 -17 2 blend
+ -30 139 -87 -1.78378 -98 -15 -0.13513 -17 2 blend
rmoveto
- -169 731 169 -41 0 -46 38 0 42 41 0 46 3 blend
+ -169 731 169 -41 -0.36937 -46 38 0.34235 42 41 0.36937 46 3 blend
vlineto
hintmask 111100000010000100000000
- -650 -375 62 1 70 -32 0 -36 2 blend
+ -650 -375 62 0.55856 70 -32 -0.28828 -36 2 blend
rmoveto
- 571 -76 -571 -159 -1 -179 48 0 54 159 1 179 3 blend
+ 571 -76 -571 -159 -1.43243 -179 48 0.43243 54 159 1.43243 179 3 blend
hlineto
- -30 -38 0 -43 1 blend
+ -30 -38 -0.34235 -43 1 blend
vmoveto
- 571 -77 -571 -159 -1 -179 48 0 54 159 1 179 3 blend
+ 571 -77 -571 -159 -1.43243 -179 48 0.43243 54 159 1.43243 179 3 blend
hlineto
- 287 -66 -1 -74 1 blend
+ 287 -66 -0.59459 -74 1 blend
vmoveto
- 571 -74 -571 -159 -1 -179 46 0 52 159 1 179 3 blend
+ 571 -74 -571 -159 -1.43243 -179 46 0.41441 52 159 1.43243 179 3 blend
hlineto
- -30 104 -99 -1 -111 -4 0 -5 2 blend
+ -30 104 -99 -0.89189 -111 -4 -0.03604 -5 2 blend
rmoveto
- -347 631 347 -18 0 -20 45 0 50 18 0 20 3 blend
+ -347 631 347 -18 -0.16216 -20 45 0.40541 50 18 0.16216 20 3 blend
vlineto
- -216 -389 -86 -1 -96 -31 0 -35 2 blend
+ -216 -389 -86 -0.77478 -96 -31 -0.27928 -35 2 blend
rmoveto
- 127 -34 121 -39 72 -31 -17 0 -19 2 0 2 -13 0 -15 -2 0 -2 -13 0 -15 3 0 3 6 blend
+ 127 -34 121 -39 72 -31 -17 -0.15315 -19 2 0.01802 2 -13 -0.11711 -15 -2 -0.01802 -2 -13 -0.11711 -15 3 0.02702 3 6 blend
rrcurveto
- 31 22 -78 32 -126 39 -121 136 1 153 39 0 44 1 0 1 -3 0 -3 -8 0 -9 4 0 5 9 0 10 7 blend
+ 31 22 -78 32 -126 39 -121 136 1.22522 153 39 0.35135 44 1 0 1 -3 -0.02702 -3 -8 -0.07207 -9 4 0.03604 5 9 0.08109 10 7 blend
31 rlinecurve
- -258 -1 -67 -1 -75 0 0 -1 2 blend
+ -258 -1 -67 -0.6036 -75 0 0 -1 2 blend
rmoveto
- -81 -39 -128 -36 -107 -23 8 -6 12 -12 5 -6 103 25 130 41 86 43 9 0 10 6 0 7 3 0 4 7 0 8 -4 0 -5 7 0 8 19 0 22 -14 0 -16 32 0 36 -32 0 -36 17 0 19 -19 0 -21 3 0 3 -1 0 -1 5 0 6 2 0 2 1 0 1 4 0 5 18 blend
+ -81 -39 -128 -36 -107 -23 8 -6 12 -12 5 -6 103 25 130 41 86 43 9 0.08109 10 6 0.05405 7 3 0.02702 4 7 0.06306 8 -4 -0.03604 -5 7 0.06306 8 19 0.17117 22 -14 -0.12613 -16 32 0.28828 36 -32 -0.28828 -36 17 0.15315 19 -19 -0.17117 -21 3 0.02702 3 -1 0 -1 5 0.04504 6 2 0.01802 2 1 0 1 4 0.03604 5 18 blend
rrcurveto
</CharString>
</CharStrings>
diff --git a/Tests/varLib/data/test_results/TestVVAR.ttx b/Tests/varLib/data/test_results/TestVVAR.ttx
index 53c038c1..c16266d3 100644
--- a/Tests/varLib/data/test_results/TestVVAR.ttx
+++ b/Tests/varLib/data/test_results/TestVVAR.ttx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<ttFont sfntVersion="OTTO" ttLibVersion="3.39">
+<ttFont sfntVersion="OTTO" ttLibVersion="4.42">
<VVAR>
<Version value="0x00010000"/>
@@ -7,14 +7,7 @@
<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>
+ <!-- RegionCount=0 -->
</VarRegionList>
<!-- VarDataCount=1 -->
<VarData index="0">
diff --git a/Tests/varLib/featureVars_test.py b/Tests/varLib/featureVars_test.py
index 89675af2..7a3a6650 100644
--- a/Tests/varLib/featureVars_test.py
+++ b/Tests/varLib/featureVars_test.py
@@ -1,36 +1,45 @@
-from fontTools.varLib.featureVars import (
- overlayFeatureVariations)
+from fontTools.varLib.featureVars import overlayFeatureVariations, overlayBox
-def test_linear(n = 10):
+def _test_linear(n):
conds = []
for i in range(n):
end = i / n
- start = end - 1.
- region = [{'X': (start, end)}]
- subst = {'g%.2g'%start: 'g%.2g'%end}
+ start = end - 1.0
+ region = [{"X": (start, end)}]
+ subst = {"g%.2g" % start: "g%.2g" % end}
conds.append((region, subst))
overlaps = overlayFeatureVariations(conds)
assert len(overlaps) == 2 * n - 1, overlaps
return conds, overlaps
-def test_quadratic(n = 10):
+
+def test_linear():
+ _test_linear(10)
+
+
+def _test_quadratic(n):
conds = []
for i in range(1, n + 1):
- region = [{'X': (0, i / n),
- 'Y': (0, (n + 1 - i) / n)}]
+ region = [{"X": (0, i / n), "Y": (0, (n + 1 - i) / n)}]
subst = {str(i): str(n + 1 - i)}
conds.append((region, subst))
overlaps = overlayFeatureVariations(conds)
assert len(overlaps) == n * (n + 1) // 2, overlaps
return conds, overlaps
+
+def test_quadratic():
+ _test_quadratic(10)
+
+
def _merge_substitutions(substitutions):
merged = {}
for subst in substitutions:
merged.update(subst)
return merged
+
def _match_condition(location, overlaps):
for box, substitutions in overlaps:
for tag, coord in location.items():
@@ -39,59 +48,69 @@ def _match_condition(location, overlaps):
return _merge_substitutions(substitutions)
return {} # no match
+
def test_overlaps_1():
# https://github.com/fonttools/fonttools/issues/1400
conds = [
- ([{'abcd': (4, 9)}], {0: 0}),
- ([{'abcd': (5, 10)}], {1: 1}),
- ([{'abcd': (0, 8)}], {2: 2}),
- ([{'abcd': (3, 7)}], {3: 3}),
+ ([{"abcd": (4, 9)}], {0: 0}),
+ ([{"abcd": (5, 10)}], {1: 1}),
+ ([{"abcd": (0, 8)}], {2: 2}),
+ ([{"abcd": (3, 7)}], {3: 3}),
]
overlaps = overlayFeatureVariations(conds)
- subst = _match_condition({'abcd': 0}, overlaps)
+ subst = _match_condition({"abcd": 0}, overlaps)
assert subst == {2: 2}
- subst = _match_condition({'abcd': 1}, overlaps)
+ subst = _match_condition({"abcd": 1}, overlaps)
assert subst == {2: 2}
- subst = _match_condition({'abcd': 3}, overlaps)
+ subst = _match_condition({"abcd": 3}, overlaps)
assert subst == {2: 2, 3: 3}
- subst = _match_condition({'abcd': 4}, overlaps)
+ subst = _match_condition({"abcd": 4}, overlaps)
assert subst == {0: 0, 2: 2, 3: 3}
- subst = _match_condition({'abcd': 5}, overlaps)
+ subst = _match_condition({"abcd": 5}, overlaps)
assert subst == {0: 0, 1: 1, 2: 2, 3: 3}
- subst = _match_condition({'abcd': 7}, overlaps)
+ subst = _match_condition({"abcd": 7}, overlaps)
assert subst == {0: 0, 1: 1, 2: 2, 3: 3}
- subst = _match_condition({'abcd': 8}, overlaps)
+ subst = _match_condition({"abcd": 8}, overlaps)
assert subst == {0: 0, 1: 1, 2: 2}
- subst = _match_condition({'abcd': 9}, overlaps)
+ subst = _match_condition({"abcd": 9}, overlaps)
assert subst == {0: 0, 1: 1}
- subst = _match_condition({'abcd': 10}, overlaps)
+ subst = _match_condition({"abcd": 10}, overlaps)
assert subst == {1: 1}
+
def test_overlaps_2():
# https://github.com/fonttools/fonttools/issues/1400
conds = [
- ([{'abcd': (1, 9)}], {0: 0}),
- ([{'abcd': (8, 10)}], {1: 1}),
- ([{'abcd': (3, 4)}], {2: 2}),
- ([{'abcd': (1, 10)}], {3: 3}),
+ ([{"abcd": (1, 9)}], {0: 0}),
+ ([{"abcd": (8, 10)}], {1: 1}),
+ ([{"abcd": (3, 4)}], {2: 2}),
+ ([{"abcd": (1, 10)}], {3: 3}),
]
overlaps = overlayFeatureVariations(conds)
- subst = _match_condition({'abcd': 0}, overlaps)
+ subst = _match_condition({"abcd": 0}, overlaps)
assert subst == {}
- subst = _match_condition({'abcd': 1}, overlaps)
+ subst = _match_condition({"abcd": 1}, overlaps)
assert subst == {0: 0, 3: 3}
- subst = _match_condition({'abcd': 2}, overlaps)
+ subst = _match_condition({"abcd": 2}, overlaps)
assert subst == {0: 0, 3: 3}
- subst = _match_condition({'abcd': 3}, overlaps)
+ subst = _match_condition({"abcd": 3}, overlaps)
assert subst == {0: 0, 2: 2, 3: 3}
- subst = _match_condition({'abcd': 5}, overlaps)
+ subst = _match_condition({"abcd": 5}, overlaps)
assert subst == {0: 0, 3: 3}
- subst = _match_condition({'abcd': 10}, overlaps)
+ subst = _match_condition({"abcd": 10}, overlaps)
assert subst == {1: 1, 3: 3}
-def run(test, n, quiet):
+def test_overlayBox():
+ # https://github.com/fonttools/fonttools/issues/3003
+ top = {"opsz": (0.75, 1.0), "wght": (0.5, 1.0)}
+ bot = {"wght": (0.25, 1.0)}
+ intersection, remainder = overlayBox(top, bot)
+ assert intersection == {"opsz": (0.75, 1.0), "wght": (0.5, 1.0)}
+ assert remainder == {"wght": (0.25, 1.0)}
+
+def run(test, n, quiet):
print()
print("%s:" % test.__name__)
input, output = test(n)
@@ -106,16 +125,18 @@ def run(test, n, quiet):
pprint(output)
print()
+
if __name__ == "__main__":
import sys
from pprint import pprint
+
quiet = False
n = 3
- if len(sys.argv) > 1 and sys.argv[1] == '-q':
+ if len(sys.argv) > 1 and sys.argv[1] == "-q":
quiet = True
del sys.argv[1]
if len(sys.argv) > 1:
n = int(sys.argv[1])
- run(test_linear, n=n, quiet=quiet)
- run(test_quadratic, n=n, quiet=quiet)
+ run(_test_linear, n=n, quiet=quiet)
+ run(_test_quadratic, n=n, quiet=quiet)
diff --git a/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx
index 268b5068..2f1754b0 100644
--- a/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx
+++ b/Tests/varLib/instancer/data/PartialInstancerTest-VF.ttx
@@ -728,7 +728,7 @@
<AxisOrdering value="2"/>
</Axis>
</DesignAxisRecord>
- <!-- AxisValueCount=5 -->
+ <!-- AxisValueCount=7 -->
<AxisValueArray>
<AxisValue index="0" Format="1">
<AxisIndex value="0"/>
@@ -743,7 +743,13 @@
<Value value="400.0"/>
<LinkedValue value="700.0"/>
</AxisValue>
- <AxisValue index="2" Format="2">
+ <AxisValue index="2" Format="1">
+ <AxisIndex value="0"/>
+ <Flags value="0"/>
+ <ValueNameID value="262"/> <!-- Medium -->
+ <Value value="500.0"/>
+ </AxisValue>
+ <AxisValue index="3" Format="2">
<AxisIndex value="0"/>
<Flags value="0"/>
<ValueNameID value="266"/> <!-- Black -->
@@ -751,7 +757,7 @@
<RangeMinValue value="801.0"/>
<RangeMaxValue value="900.0"/>
</AxisValue>
- <AxisValue index="3" Format="4">
+ <AxisValue index="4" Format="4">
<!-- AxisCount=1 -->
<Flags value="0"/>
<ValueNameID value="279"/> <!-- Condensed -->
@@ -760,14 +766,14 @@
<Value value="79.0"/>
</AxisValueRecord>
</AxisValue>
- <AxisValue index="4" Format="3">
+ <AxisValue index="5" Format="3">
<AxisIndex value="2"/>
<Flags value="2"/>
<ValueNameID value="295"/> <!-- Upright -->
<Value value="0.0"/>
<LinkedValue value="1.0"/>
</AxisValue>
- <AxisValue index="3" Format="4">
+ <AxisValue index="6" Format="4">
<!-- AxisCount=1 -->
<Flags value="2"/>
<ValueNameID value="297"/> <!-- Normal -->
@@ -781,6 +787,7 @@
</STAT>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>
diff --git a/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx b/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx
index cd7ffa05..3acbf56d 100644
--- a/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx
+++ b/Tests/varLib/instancer/data/PartialInstancerTest2-VF.ttx
@@ -1139,6 +1139,7 @@
</STAT>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.7969"/>
diff --git a/Tests/varLib/instancer/data/STATInstancerTest.ttx b/Tests/varLib/instancer/data/STATInstancerTest.ttx
index eee24d82..e4506cec 100644
--- a/Tests/varLib/instancer/data/STATInstancerTest.ttx
+++ b/Tests/varLib/instancer/data/STATInstancerTest.ttx
@@ -1336,6 +1336,7 @@
</STAT>
<avar>
+ <version major="1" minor="0"/>
<segment axis="wght">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.6667" to="-0.74194"/>
diff --git a/Tests/varLib/instancer/data/SinglePos.ttx b/Tests/varLib/instancer/data/SinglePos.ttx
index 64ffd9f5..dda441e3 100644
--- a/Tests/varLib/instancer/data/SinglePos.ttx
+++ b/Tests/varLib/instancer/data/SinglePos.ttx
@@ -213,6 +213,7 @@
</GPOS>
<avar>
+ <version major="1" minor="0"/>
<segment axis="opsz">
<mapping from="-1.0" to="-1.0"/>
<mapping from="-0.01" to="-0.9"/>
diff --git a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx
index 776a92f1..c89949c2 100644
--- a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,100.ttx
@@ -74,7 +74,7 @@
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
- <xAvgCharWidth value="577"/>
+ <xAvgCharWidth value="502"/>
<usWeightClass value="100"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000000"/>
diff --git a/Tests/varLib/instancer/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 61bc41cc..a78019f8 100644
--- a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-100,62.5.ttx
@@ -74,7 +74,7 @@
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
- <xAvgCharWidth value="577"/>
+ <xAvgCharWidth value="383"/>
<usWeightClass value="100"/>
<usWidthClass value="2"/>
<fsType value="00000000 00000000"/>
diff --git a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx
index c2d20571..635acd71 100644
--- a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,100.ttx
@@ -74,7 +74,7 @@
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
- <xAvgCharWidth value="577"/>
+ <xAvgCharWidth value="543"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000000"/>
diff --git a/Tests/varLib/instancer/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 63eeb0e7..fcafe91c 100644
--- a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-400,62.5.ttx
@@ -74,7 +74,7 @@
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
- <xAvgCharWidth value="577"/>
+ <xAvgCharWidth value="428"/>
<usWeightClass value="400"/>
<usWidthClass value="2"/>
<fsType value="00000000 00000000"/>
diff --git a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx
index 013ba1e7..61c3b2bb 100644
--- a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,100.ttx
@@ -74,7 +74,7 @@
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
- <xAvgCharWidth value="577"/>
+ <xAvgCharWidth value="609"/>
<usWeightClass value="900"/>
<usWidthClass value="5"/>
<fsType value="00000000 00000000"/>
diff --git a/Tests/varLib/instancer/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 45e34cbf..fa31886a 100644
--- a/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx
+++ b/Tests/varLib/instancer/data/test_results/PartialInstancerTest2-VF-instance-900,62.5.ttx
@@ -74,7 +74,7 @@
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="4"/>
- <xAvgCharWidth value="577"/>
+ <xAvgCharWidth value="506"/>
<usWeightClass value="900"/>
<usWidthClass value="2"/>
<fsType value="00000000 00000000"/>
diff --git a/Tests/varLib/instancer/instancer_test.py b/Tests/varLib/instancer/instancer_test.py
index db224cca..20d9194f 100644
--- a/Tests/varLib/instancer/instancer_test.py
+++ b/Tests/varLib/instancer/instancer_test.py
@@ -1,4 +1,5 @@
from fontTools.misc.fixedTools import floatToFixedToFloat
+from fontTools.misc.roundTools import noRound
from fontTools.misc.testTools import stripVariableItemsFromTTX
from fontTools.misc.textTools import Tag
from fontTools import ttLib
@@ -51,7 +52,15 @@ def fvarAxes():
def _get_coordinates(varfont, glyphname):
# converts GlyphCoordinates to a list of (x, y) tuples, so that pytest's
# assert will give us a nicer diff
- return list(varfont["glyf"].getCoordinatesAndControls(glyphname, varfont)[0])
+ return list(
+ varfont["glyf"]._getCoordinatesAndControls(
+ glyphname,
+ varfont["hmtx"].metrics,
+ varfont["vmtx"].metrics,
+ # the tests expect float coordinates
+ round=noRound,
+ )[0]
+ )
class InstantiateGvarTest(object):
@@ -112,6 +121,8 @@ class InstantiateGvarTest(object):
],
)
def test_pin_and_drop_axis(self, varfont, glyph_name, location, expected, optimize):
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateGvar(varfont, location, optimize=optimize)
assert _get_coordinates(varfont, glyph_name) == expected[glyph_name]
@@ -124,9 +135,9 @@ class InstantiateGvarTest(object):
)
def test_full_instance(self, varfont, optimize):
- instancer.instantiateGvar(
- varfont, {"wght": 0.0, "wdth": -0.5}, optimize=optimize
- )
+ location = instancer.NormalizedAxisLimits(wght=0.0, wdth=-0.5)
+
+ instancer.instantiateGvar(varfont, location, optimize=optimize)
assert _get_coordinates(varfont, "hyphen") == [
(33.5, 229),
@@ -169,7 +180,7 @@ class InstantiateGvarTest(object):
assert hmtx["minus"] == (422, 40)
assert vmtx["minus"] == (536, 229)
- location = {"wght": -1.0, "wdth": -1.0}
+ location = instancer.NormalizedAxisLimits(wght=-1.0, wdth=-1.0)
instancer.instantiateGvar(varfont, location)
@@ -206,6 +217,8 @@ class InstantiateCvarTest(object):
],
)
def test_pin_and_drop_axis(self, varfont, location, expected):
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateCvar(varfont, location)
assert list(varfont["cvt "].values) == expected
@@ -217,7 +230,9 @@ class InstantiateCvarTest(object):
)
def test_full_instance(self, varfont):
- instancer.instantiateCvar(varfont, {"wght": -0.5, "wdth": -0.5})
+ location = instancer.NormalizedAxisLimits(wght=-0.5, wdth=-0.5)
+
+ instancer.instantiateCvar(varfont, location)
assert list(varfont["cvt "].values) == [500, -400, 165, 225]
@@ -272,6 +287,8 @@ class InstantiateMVARTest(object):
assert mvar.VarStore.VarData[1].VarRegionCount == 1
assert all(len(item) == 1 for item in mvar.VarStore.VarData[1].Item)
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateMVAR(varfont, location)
for mvar_tag, expected_value in expected.items():
@@ -312,6 +329,8 @@ class InstantiateMVARTest(object):
],
)
def test_full_instance(self, varfont, location, expected):
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateMVAR(varfont, location)
for mvar_tag, expected_value in expected.items():
@@ -344,6 +363,8 @@ class InstantiateHVARTest(object):
],
)
def test_partial_instance(self, varfont, location, expectedRegions, expectedDeltas):
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateHVAR(varfont, location)
assert "HVAR" in varfont
@@ -376,7 +397,9 @@ class InstantiateHVARTest(object):
assert varStore.VarData[varIdx >> 16].Item[varIdx & 0xFFFF] == expectedDeltas
def test_full_instance(self, varfont):
- instancer.instantiateHVAR(varfont, {"wght": 0, "wdth": 0})
+ location = instancer.NormalizedAxisLimits(wght=0, wdth=0)
+
+ instancer.instantiateHVAR(varfont, location)
assert "HVAR" not in varfont
@@ -390,7 +413,9 @@ class InstantiateHVARTest(object):
axis.axisTag = "TEST"
fvar.axes.append(axis)
- instancer.instantiateHVAR(varfont, {"wght": 0, "wdth": 0})
+ location = instancer.NormalizedAxisLimits(wght=0, wdth=0)
+
+ instancer.instantiateHVAR(varfont, location)
assert "HVAR" in varfont
@@ -452,6 +477,8 @@ class InstantiateItemVariationStoreTest(object):
def test_instantiate_default_deltas(
self, varStore, fvarAxes, location, expected_deltas, num_regions
):
+ location = instancer.NormalizedAxisLimits(location)
+
defaultDeltas = instancer.instantiateItemVariationStore(
varStore, fvarAxes, location
)
@@ -504,8 +531,9 @@ class TupleVarStoreAdapterTest(object):
adapter = instancer._TupleVarStoreAdapter(
regions, axisOrder, tupleVarData, itemCounts=[2, 2]
)
+ location = instancer.NormalizedAxisLimits(wght=0.5)
- defaultDeltaArray = adapter.instantiate({"wght": 0.5})
+ defaultDeltaArray = adapter.instantiate(location)
assert defaultDeltaArray == [[15, 45], [0, 0]]
assert adapter.regions == [{"wdth": (-1.0, -1.0, 0)}]
@@ -747,6 +775,8 @@ class InstantiateOTLTest(object):
vf = varfontGDEF
assert "GDEF" in vf
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateOTL(vf, location)
assert "GDEF" in vf
@@ -778,6 +808,8 @@ class InstantiateOTLTest(object):
vf = varfontGDEF
assert "GDEF" in vf
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateOTL(vf, location)
assert "GDEF" in vf
@@ -806,6 +838,8 @@ class InstantiateOTLTest(object):
assert "GDEF" in vf
assert "GPOS" in vf
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateOTL(vf, location)
gdef = vf["GDEF"].table
@@ -839,6 +873,8 @@ class InstantiateOTLTest(object):
assert "GDEF" in vf
assert "GPOS" in vf
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateOTL(vf, location)
assert "GDEF" not in vf
@@ -870,6 +906,8 @@ class InstantiateOTLTest(object):
assert "GDEF" in vf
assert "GPOS" in vf
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateOTL(vf, location)
v1, v2 = expected
@@ -915,6 +953,8 @@ class InstantiateOTLTest(object):
assert "GDEF" in vf
assert "GPOS" in vf
+ location = instancer.NormalizedAxisLimits(location)
+
instancer.instantiateOTL(vf, location)
v1, v2 = expected
@@ -955,7 +995,7 @@ class InstantiateOTLTest(object):
# check that MutatorMerger for ValueRecord doesn't raise AttributeError
# when XAdvDevice is present but there's no corresponding XAdvance.
- instancer.instantiateOTL(vf, {"wght": 0.5})
+ instancer.instantiateOTL(vf, instancer.NormalizedAxisLimits(wght=0.5))
pairPos = vf["GPOS"].table.LookupList.Lookup[0].SubTable[0]
assert pairPos.ValueFormat1 == 0x4
@@ -967,12 +1007,16 @@ class InstantiateOTLTest(object):
class InstantiateAvarTest(object):
@pytest.mark.parametrize("location", [{"wght": 0.0}, {"wdth": 0.0}])
def test_pin_and_drop_axis(self, varfont, location):
+ location = instancer.AxisLimits(location)
+
instancer.instantiateAvar(varfont, location)
assert set(varfont["avar"].segments).isdisjoint(location)
def test_full_instance(self, varfont):
- instancer.instantiateAvar(varfont, {"wght": 0.0, "wdth": 0.0})
+ location = instancer.AxisLimits(wght=0.0, wdth=0.0)
+
+ instancer.instantiateAvar(varfont, location)
assert "avar" not in varfont
@@ -1139,6 +1183,8 @@ class InstantiateAvarTest(object):
],
)
def test_limit_axes(self, varfont, axisLimits, expectedSegments):
+ axisLimits = instancer.AxisLimits(axisLimits)
+
instancer.instantiateAvar(varfont, axisLimits)
newSegments = varfont["avar"].segments
@@ -1162,8 +1208,10 @@ class InstantiateAvarTest(object):
def test_drop_invalid_segment_map(self, varfont, invalidSegmentMap, caplog):
varfont["avar"].segments["wght"] = invalidSegmentMap
+ axisLimits = instancer.AxisLimits(wght=(100, 400))
+
with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"):
- instancer.instantiateAvar(varfont, {"wght": (100, 400)})
+ instancer.instantiateAvar(varfont, axisLimits)
assert "Invalid avar" in caplog.text
assert "wght" not in varfont["avar"].segments
@@ -1210,6 +1258,8 @@ class InstantiateFvarTest(object):
],
)
def test_pin_and_drop_axis(self, varfont, location, instancesLeft):
+ location = instancer.AxisLimits(location)
+
instancer.instantiateFvar(varfont, location)
fvar = varfont["fvar"]
@@ -1224,20 +1274,51 @@ class InstantiateFvarTest(object):
] == instancesLeft
def test_full_instance(self, varfont):
- instancer.instantiateFvar(varfont, {"wght": 0.0, "wdth": 0.0})
+ location = instancer.AxisLimits({"wght": 0.0, "wdth": 0.0})
+
+ instancer.instantiateFvar(varfont, location)
assert "fvar" not in varfont
+ @pytest.mark.parametrize(
+ "location, expected",
+ [
+ ({"wght": (30, 40, 700)}, (100, 100, 700)),
+ ({"wght": (30, 40, None)}, (100, 100, 900)),
+ ({"wght": (30, None, 700)}, (100, 400, 700)),
+ ({"wght": (None, 200, 700)}, (100, 200, 700)),
+ ({"wght": (40, None, None)}, (100, 400, 900)),
+ ({"wght": (None, 40, None)}, (100, 100, 900)),
+ ({"wght": (None, None, 700)}, (100, 400, 700)),
+ ({"wght": (None, None, None)}, (100, 400, 900)),
+ ],
+ )
+ def test_axis_limits(self, varfont, location, expected):
+ location = instancer.AxisLimits(location)
+
+ varfont = instancer.instantiateVariableFont(varfont, location)
+
+ fvar = varfont["fvar"]
+ axes = {a.axisTag: a for a in fvar.axes}
+ assert axes["wght"].minValue == expected[0]
+ assert axes["wght"].defaultValue == expected[1]
+ assert axes["wght"].maxValue == expected[2]
+
class InstantiateSTATTest(object):
@pytest.mark.parametrize(
"location, expected",
[
({"wght": 400}, ["Regular", "Condensed", "Upright", "Normal"]),
- ({"wdth": 100}, ["Thin", "Regular", "Black", "Upright", "Normal"]),
+ (
+ {"wdth": 100},
+ ["Thin", "Regular", "Medium", "Black", "Upright", "Normal"],
+ ),
],
)
def test_pin_and_drop_axis(self, varfont, location, expected):
+ location = instancer.AxisLimits(location)
+
instancer.instantiateSTAT(varfont, location)
stat = varfont["STAT"].table
@@ -1256,7 +1337,7 @@ class InstantiateSTATTest(object):
def test_skip_table_no_axis_value_array(self, varfont):
varfont["STAT"].table.AxisValueArray = None
- instancer.instantiateSTAT(varfont, {"wght": 100})
+ instancer.instantiateSTAT(varfont, instancer.AxisLimits(wght=100))
assert len(varfont["STAT"].table.DesignAxisRecord.Axis) == 3
assert varfont["STAT"].table.AxisValueArray is None
@@ -1318,7 +1399,9 @@ class InstantiateSTATTest(object):
return result
def test_limit_axes(self, varfont2):
- instancer.instantiateSTAT(varfont2, {"wght": (400, 500), "wdth": (75, 100)})
+ axisLimits = instancer.AxisLimits({"wght": (400, 500), "wdth": (75, 100)})
+
+ instancer.instantiateSTAT(varfont2, axisLimits)
assert len(varfont2["STAT"].table.AxisValueArray.AxisValue) == 5
assert self.get_STAT_axis_values(varfont2["STAT"].table) == [
@@ -1344,11 +1427,11 @@ class InstantiateSTATTest(object):
axisValue.AxisValueRecord.append(rec)
stat.AxisValueArray.AxisValue.append(axisValue)
- instancer.instantiateSTAT(varfont2, {"wght": (100, 600)})
+ instancer.instantiateSTAT(varfont2, instancer.AxisLimits(wght=(100, 600)))
assert axisValue in varfont2["STAT"].table.AxisValueArray.AxisValue
- instancer.instantiateSTAT(varfont2, {"wdth": (62.5, 87.5)})
+ instancer.instantiateSTAT(varfont2, instancer.AxisLimits(wdth=(62.5, 87.5)))
assert axisValue not in varfont2["STAT"].table.AxisValueArray.AxisValue
@@ -1359,7 +1442,7 @@ class InstantiateSTATTest(object):
stat.AxisValueArray.AxisValue.append(axisValue)
with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"):
- instancer.instantiateSTAT(varfont2, {"wght": 400})
+ instancer.instantiateSTAT(varfont2, instancer.AxisLimits(wght=400))
assert "Unknown AxisValue table format (5)" in caplog.text
assert axisValue in varfont2["STAT"].table.AxisValueArray.AxisValue
@@ -1452,6 +1535,18 @@ class InstantiateVariableFontTest(object):
assert _dump_ttx(instance) == expected
+ def test_move_weight_width_axis_default(self, varfont2):
+ # https://github.com/fonttools/fonttools/issues/2885
+ assert varfont2["OS/2"].usWeightClass == 400
+ assert varfont2["OS/2"].usWidthClass == 5
+
+ varfont = instancer.instantiateVariableFont(
+ varfont2, {"wght": (100, 500, 900), "wdth": 87.5}
+ )
+
+ assert varfont["OS/2"].usWeightClass == 500
+ assert varfont["OS/2"].usWidthClass == 4
+
@pytest.mark.parametrize(
"overlap, wght",
[
@@ -1482,20 +1577,39 @@ class InstantiateVariableFontTest(object):
location = {"wght": 280, "opsz": 18}
instance = instancer.instantiateVariableFont(
- varfont, location,
+ varfont,
+ location,
)
- expected = _get_expected_instance_ttx(
- "SinglePos", *location.values()
- )
+ expected = _get_expected_instance_ttx("SinglePos", *location.values())
assert _dump_ttx(instance) == expected
+ def test_varComposite(self):
+ input_path = os.path.join(
+ TESTDATA, "..", "..", "..", "ttLib", "data", "varc-ac00-ac01.ttf"
+ )
+ varfont = ttLib.TTFont(input_path)
+
+ location = {"wght": 600}
+
+ instance = instancer.instantiateVariableFont(
+ varfont,
+ location,
+ )
+
+ location = {"0000": 0.5}
+
+ instance = instancer.instantiateVariableFont(
+ varfont,
+ location,
+ )
def _conditionSetAsDict(conditionSet, axisOrder):
result = {}
- for cond in conditionSet.ConditionTable:
+ conditionSets = conditionSet.ConditionTable if conditionSet is not None else []
+ for cond in conditionSets:
assert cond.Format == 1
axisTag = axisOrder[cond.AxisIndex]
result[axisTag] = (cond.FilterRangeMinValue, cond.FilterRangeMaxValue)
@@ -1541,10 +1655,11 @@ class InstantiateFeatureVariationsTest(object):
({"wght": 0}, {}, [({"cntr": (0.75, 1.0)}, {"uni0041": "uni0061"})]),
(
{"wght": -1.0},
- {},
+ {"uni0061": "uni0041"},
[
({"cntr": (0, 0.25)}, {"uni0061": "uni0041"}),
({"cntr": (0.75, 1.0)}, {"uni0041": "uni0061"}),
+ ({}, {}),
],
),
(
@@ -1554,7 +1669,8 @@ class InstantiateFeatureVariationsTest(object):
(
{"cntr": (0.75, 1.0)},
{"uni0024": "uni0024.nostroke", "uni0041": "uni0061"},
- )
+ ),
+ ({}, {}),
],
),
(
@@ -1572,7 +1688,66 @@ class InstantiateFeatureVariationsTest(object):
(
{"wght": (0.20886, 1.0)},
{"uni0024": "uni0024.nostroke", "uni0041": "uni0061"},
- )
+ ),
+ ({}, {}),
+ ],
+ ),
+ (
+ {"cntr": (-0.5, 0, 1.0)},
+ {},
+ [
+ (
+ {"wght": (0.20886, 1.0), "cntr": (0.75, 1)},
+ {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"},
+ ),
+ (
+ {"wght": (-1.0, -0.45654), "cntr": (0, 0.25)},
+ {"uni0061": "uni0041"},
+ ),
+ (
+ {"cntr": (0.75, 1.0)},
+ {"uni0041": "uni0061"},
+ ),
+ (
+ {"wght": (0.20886, 1.0)},
+ {"uni0024": "uni0024.nostroke"},
+ ),
+ ],
+ ),
+ (
+ {"cntr": (0.8, 0.9, 1.0)},
+ {"uni0041": "uni0061"},
+ [
+ (
+ {"wght": (0.20886, 1.0)},
+ {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"},
+ ),
+ (
+ {},
+ {"uni0041": "uni0061"},
+ ),
+ ],
+ ),
+ (
+ {"cntr": (0.7, 0.9, 1.0)},
+ {"uni0041": "uni0061"},
+ [
+ (
+ {"cntr": (-0.7499999999999999, 1.0), "wght": (0.20886, 1.0)},
+ {"uni0024": "uni0024.nostroke", "uni0041": "uni0061"},
+ ),
+ (
+ {"cntr": (-0.7499999999999999, 1.0)},
+ {"uni0041": "uni0061"},
+ ),
+ (
+ {"wght": (0.20886, 1.0)},
+ {"uni0024": "uni0024.nostroke"},
+ ),
+ (
+ {},
+ {},
+ ),
],
),
],
@@ -1589,25 +1764,30 @@ class InstantiateFeatureVariationsTest(object):
]
)
- instancer.instantiateFeatureVariations(font, location)
+ limits = instancer.NormalizedAxisLimits(location)
+ instancer.instantiateFeatureVariations(font, limits)
gsub = font["GSUB"].table
featureVariations = gsub.FeatureVariations
assert featureVariations.FeatureVariationCount == len(expectedRecords)
- axisOrder = [a.axisTag for a in font["fvar"].axes if a.axisTag not in location]
+ axisOrder = [
+ a.axisTag
+ for a in font["fvar"].axes
+ if a.axisTag not in location or isinstance(location[a.axisTag], tuple)
+ ]
for i, (expectedConditionSet, expectedSubs) in enumerate(expectedRecords):
rec = featureVariations.FeatureVariationRecord[i]
conditionSet = _conditionSetAsDict(rec.ConditionSet, axisOrder)
- assert conditionSet == expectedConditionSet
+ assert conditionSet == expectedConditionSet, i
subsRecord = rec.FeatureTableSubstitution.SubstitutionRecord[0]
lookupIndices = subsRecord.Feature.LookupListIndex
substitutions = _getSubstitutions(gsub, lookupIndices)
- assert substitutions == expectedSubs
+ assert substitutions == expectedSubs, i
appliedLookupIndices = gsub.FeatureList.FeatureRecord[0].Feature.LookupListIndex
@@ -1638,11 +1818,16 @@ class InstantiateFeatureVariationsTest(object):
),
]
)
+ gsub = font["GSUB"].table
+ assert gsub.FeatureVariations
+ assert gsub.Version == 0x00010001
+
+ location = instancer.NormalizedAxisLimits(location)
instancer.instantiateFeatureVariations(font, location)
- gsub = font["GSUB"].table
assert not hasattr(gsub, "FeatureVariations")
+ assert gsub.Version == 0x00010000
if appliedSubs:
lookupIndices = gsub.FeatureList.FeatureRecord[0].Feature.LookupListIndex
@@ -1650,6 +1835,24 @@ class InstantiateFeatureVariationsTest(object):
else:
assert not gsub.FeatureList.FeatureRecord
+ def test_null_conditionset(self):
+ # A null ConditionSet offset should be treated like an empty ConditionTable, i.e.
+ # all contexts are matched; see https://github.com/fonttools/fonttools/issues/3211
+ font = makeFeatureVarsFont(
+ [([{"wght": (-1.0, 1.0)}], {"uni0024": "uni0024.nostroke"})]
+ )
+ gsub = font["GSUB"].table
+ gsub.FeatureVariations.FeatureVariationRecord[0].ConditionSet = None
+
+ location = instancer.NormalizedAxisLimits({"wght": 0.5})
+ instancer.instantiateFeatureVariations(font, location)
+
+ assert not hasattr(gsub, "FeatureVariations")
+ assert gsub.Version == 0x00010000
+
+ lookupIndices = gsub.FeatureList.FeatureRecord[0].Feature.LookupListIndex
+ assert _getSubstitutions(gsub, lookupIndices) == {"uni0024": "uni0024.nostroke"}
+
def test_unsupported_condition_format(self, caplog):
font = makeFeatureVarsFont(
[
@@ -1665,7 +1868,9 @@ class InstantiateFeatureVariationsTest(object):
rec1.ConditionSet.ConditionTable[0].Format = 2
with caplog.at_level(logging.WARNING, logger="fontTools.varLib.instancer"):
- instancer.instantiateFeatureVariations(font, {"wdth": 0})
+ instancer.instantiateFeatureVariations(
+ font, instancer.NormalizedAxisLimits(wdth=0)
+ )
assert (
"Condition table 0 of FeatureVariationRecord 0 "
@@ -1695,7 +1900,7 @@ class InstantiateFeatureVariationsTest(object):
class LimitTupleVariationAxisRangesTest:
def check_limit_single_var_axis_range(self, var, axisTag, axisRange, expected):
- result = instancer.limitTupleVariationAxisRange(var, axisTag, axisRange)
+ result = instancer.changeTupleVariationAxisLimit(var, axisTag, axisRange)
print(result)
assert len(result) == len(expected)
@@ -1758,8 +1963,8 @@ class LimitTupleVariationAxisRangesTest:
"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.5, 1.0)}, [100, 100]),
+ TupleVariation({"wght": (0.5, 1.0, 1.0)}, [75, 75]),
],
),
(
@@ -1777,7 +1982,7 @@ class LimitTupleVariationAxisRangesTest:
],
)
def test_positive_var(self, var, axisTag, newMax, expected):
- axisRange = instancer.NormalizedAxisRange(0, newMax)
+ axisRange = instancer.NormalizedAxisTripleAndDistances(0, 0, newMax)
self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected)
@pytest.mark.parametrize(
@@ -1837,8 +2042,8 @@ class LimitTupleVariationAxisRangesTest:
"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.5, -0.0)}, [100, 100]),
+ TupleVariation({"wght": (-1.0, -1.0, -0.5)}, [75, 75]),
],
),
(
@@ -1856,30 +2061,30 @@ class LimitTupleVariationAxisRangesTest:
],
)
def test_negative_var(self, var, axisTag, newMin, expected):
- axisRange = instancer.NormalizedAxisRange(newMin, 0)
+ axisRange = instancer.NormalizedAxisTripleAndDistances(newMin, 0, 0, 1, 1)
self.check_limit_single_var_axis_range(var, axisTag, axisRange, expected)
@pytest.mark.parametrize(
- "oldRange, newRange, expected",
+ "oldRange, newLimit, 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?
+ ((1.0, -1.0), (-1.0, 0, 1.0), None), # invalid oldRange min > max
+ ((0.6, 1.0), (0, 0, 0.5), None),
+ ((-1.0, -0.6), (-0.5, 0, 0), None),
+ ((0.4, 1.0), (0, 0, 0.5), (0.8, 1.0)),
+ ((-1.0, -0.4), (-0.5, 0, 0), (-1.0, -0.8)),
+ ((0.4, 1.0), (0, 0, 0.4), (1.0, 1.0)),
+ ((-1.0, -0.4), (-0.4, 0, 0), (-1.0, -1.0)),
+ ((-0.5, 0.5), (-0.4, 0, 0.4), (-1.0, 1.0)),
+ ((0, 1.0), (-1.0, 0, 0), (0, 0)), # or None?
+ ((-1.0, 0), (0, 0, 1.0), (0, 0)), # or None?
],
)
-def test_limitFeatureVariationConditionRange(oldRange, newRange, expected):
+def test_limitFeatureVariationConditionRange(oldRange, newLimit, expected):
condition = featureVars.buildConditionTable(0, *oldRange)
- result = instancer._limitFeatureVariationConditionRange(
- condition, instancer.NormalizedAxisRange(*newRange)
+ result = instancer.featureVars._limitFeatureVariationConditionRange(
+ condition, instancer.NormalizedAxisTripleAndDistances(*newLimit, 1, 1)
)
assert result == expected
@@ -1890,12 +2095,33 @@ def test_limitFeatureVariationConditionRange(oldRange, newRange, expected):
[
(["wght=400", "wdth=100"], {"wght": 400, "wdth": 100}),
(["wght=400:900"], {"wght": (400, 900)}),
- (["slnt=11.4"], {"slnt": pytest.approx(11.399994)}),
+ (["wght=400:700:900"], {"wght": (400, 700, 900)}),
+ (["slnt=11.4"], {"slnt": 11.399994}),
(["ABCD=drop"], {"ABCD": None}),
+ (["wght=:500:"], {"wght": (None, 500, None)}),
+ (["wght=::700"], {"wght": (None, None, 700)}),
+ (["wght=200::"], {"wght": (200, None, None)}),
+ (["wght=200:300:"], {"wght": (200, 300, None)}),
+ (["wght=:300:500"], {"wght": (None, 300, 500)}),
+ (["wght=300::700"], {"wght": (300, None, 700)}),
+ (["wght=300:700"], {"wght": (300, None, 700)}),
+ (["wght=:700"], {"wght": (None, None, 700)}),
+ (["wght=200:"], {"wght": (200, None, None)}),
],
)
def test_parseLimits(limits, expected):
- assert instancer.parseLimits(limits) == expected
+ limits = instancer.parseLimits(limits)
+ expected = instancer.AxisLimits(expected)
+
+ assert limits.keys() == expected.keys()
+ for axis, triple in limits.items():
+ expected_triple = expected[axis]
+ if expected_triple is None:
+ assert triple is None
+ else:
+ assert isinstance(triple, instancer.AxisTriple)
+ assert isinstance(expected_triple, instancer.AxisTriple)
+ assert triple == pytest.approx(expected_triple)
@pytest.mark.parametrize(
@@ -1906,27 +2132,35 @@ def test_parseLimits_invalid(limits):
instancer.parseLimits(limits)
-def test_normalizeAxisLimits_tuple(varfont):
- normalized = instancer.normalizeAxisLimits(varfont, {"wght": (100, 400)})
- assert normalized == {"wght": (-1.0, 0)}
+@pytest.mark.parametrize(
+ "limits, expected",
+ [
+ # 300, 500 come from the font having 100,400,900 fvar axis limits.
+ ({"wght": (100, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}),
+ ({"wght": (100, 400, 400)}, {"wght": (-1.0, 0, 0, 300, 500)}),
+ ({"wght": (100, 300, 400)}, {"wght": (-1.0, -0.5, 0, 300, 500)}),
+ ],
+)
+def test_normalizeAxisLimits(varfont, limits, expected):
+ limits = instancer.AxisLimits(limits)
+ normalized = limits.normalize(varfont)
-def test_normalizeAxisLimits_unsupported_range(varfont):
- with pytest.raises(NotImplementedError, match="Unsupported range"):
- instancer.normalizeAxisLimits(varfont, {"wght": (401, 700)})
+ assert normalized == instancer.NormalizedAxisLimits(expected)
def test_normalizeAxisLimits_no_avar(varfont):
del varfont["avar"]
- normalized = instancer.normalizeAxisLimits(varfont, {"wght": (400, 500)})
+ limits = instancer.AxisLimits(wght=(400, 400, 500))
+ normalized = limits.normalize(varfont)
- assert normalized["wght"] == pytest.approx((0, 0.2), 1e-4)
+ assert normalized["wght"] == pytest.approx((0, 0, 0.2, 300, 500), 1e-4)
def test_normalizeAxisLimits_missing_from_fvar(varfont):
with pytest.raises(ValueError, match="not present in fvar"):
- instancer.normalizeAxisLimits(varfont, {"ZZZZ": 1000})
+ instancer.AxisLimits({"ZZZZ": 1000}).normalize(varfont)
def test_sanityCheckVariableTables(varfont):
diff --git a/Tests/varLib/instancer/names_test.py b/Tests/varLib/instancer/names_test.py
index 9774458a..0d7ef1a8 100644
--- a/Tests/varLib/instancer/names_test.py
+++ b/Tests/varLib/instancer/names_test.py
@@ -115,7 +115,7 @@ def _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x409]):
),
# Condensed with unpinned weights
(
- {"wdth": 79, "wght": instancer.AxisRange(400, 900)},
+ {"wdth": 79, "wght": (400, 900)},
{
(1, 3, 1, 0x409): "Test Variable Font Condensed",
(2, 3, 1, 0x409): "Regular",
@@ -126,6 +126,19 @@ def _test_name_records(varfont, expected, isNonRIBBI, platforms=[0x409]):
},
True,
),
+ # Restrict weight and move default, new minimum (500) > old default (400)
+ (
+ {"wght": (500, 900)},
+ {
+ (1, 3, 1, 0x409): "Test Variable Font Medium",
+ (2, 3, 1, 0x409): "Regular",
+ (3, 3, 1, 0x409): "2.001;GOOG;TestVariableFont-Medium",
+ (6, 3, 1, 0x409): "TestVariableFont-Medium",
+ (16, 3, 1, 0x409): "Test Variable Font",
+ (17, 3, 1, 0x409): "Medium",
+ },
+ True,
+ ),
],
)
def test_updateNameTable_with_registered_axes_ribbi(
@@ -215,7 +228,7 @@ def test_updateNameTable_with_multilingual_names(varfont, limits, expected, isNo
def test_updateNameTable_missing_axisValues(varfont):
- with pytest.raises(ValueError, match="Cannot find Axis Values \['wght=200'\]"):
+ with pytest.raises(ValueError, match="Cannot find Axis Values {'wght': 200}"):
instancer.names.updateNameTable(varfont, {"wght": 200})
@@ -257,7 +270,7 @@ def test_updateNameTable_missing_stat(varfont):
def test_updateNameTable_vf_with_italic_attribute(
varfont, limits, expected, isNonRIBBI
):
- font_link_axisValue = varfont["STAT"].table.AxisValueArray.AxisValue[4]
+ font_link_axisValue = varfont["STAT"].table.AxisValueArray.AxisValue[5]
# Unset ELIDABLE_AXIS_VALUE_NAME flag
font_link_axisValue.Flags &= ~instancer.names.ELIDABLE_AXIS_VALUE_NAME
font_link_axisValue.ValueNameID = 294 # Roman --> Italic
@@ -320,3 +333,21 @@ def test_updateNameTable_existing_subfamily_name_is_not_regular(varfont):
instancer.names.updateNameTable(varfont, {"wght": 100})
expected = {(2, 3, 1, 0x409): "Regular", (17, 3, 1, 0x409): "Thin"}
_test_name_records(varfont, expected, isNonRIBBI=True)
+
+
+def test_name_irrelevant_axes(varfont):
+ # Cannot update name table if not on a named axis value location
+ with pytest.raises(ValueError) as excinfo:
+ location = {"wght": 400, "wdth": 90}
+ instance = instancer.instantiateVariableFont(
+ varfont, location, updateFontNames=True
+ )
+ assert "Cannot find Axis Values" in str(excinfo.value)
+
+ # Now let's make the wdth axis "irrelevant" to naming (no axis values)
+ varfont["STAT"].table.AxisValueArray.AxisValue.pop(6)
+ varfont["STAT"].table.AxisValueArray.AxisValue.pop(4)
+ location = {"wght": 400, "wdth": 90}
+ instance = instancer.instantiateVariableFont(
+ varfont, location, updateFontNames=True
+ )
diff --git a/Tests/varLib/instancer/solver_test.py b/Tests/varLib/instancer/solver_test.py
new file mode 100644
index 00000000..b9acf82f
--- /dev/null
+++ b/Tests/varLib/instancer/solver_test.py
@@ -0,0 +1,300 @@
+from fontTools.varLib.instancer import solver
+from fontTools.varLib.instancer import NormalizedAxisTripleAndDistances
+import pytest
+
+
+class RebaseTentTest(object):
+ @pytest.mark.parametrize(
+ "tent, axisRange, expected",
+ [
+ # Case 1: # Pin at default
+ pytest.param((0, 1, 1), (0.0, 0.0, 0.0), []),
+ # Case 1:
+ pytest.param((0.3, 0.5, 0.8), (0.1, 0.2, 0.3), []),
+ # Pin axis
+ pytest.param(
+ (0, 1, 1),
+ (0.5, 0.5, 0.5),
+ [
+ (0.5, None),
+ ],
+ ),
+ # Case 2:
+ pytest.param(
+ (0, 1, 1),
+ (-1, 0, 0.5),
+ [
+ (0.5, (0, 1, 1)),
+ ],
+ ),
+ # Case 2:
+ pytest.param(
+ (0, 1, 1),
+ (-1, 0, 0.75),
+ [
+ (0.75, (0, 1, 1)),
+ ],
+ ),
+ #
+ # Without gain:
+ #
+ # Case 3
+ pytest.param(
+ (0, 0.2, 1),
+ (-1, 0, 0.8),
+ [
+ (1, (0, 0.25, 1.25)),
+ ],
+ ),
+ # Case 3 boundary
+ pytest.param(
+ (0, 0.4, 1),
+ (-1, 0, 0.5),
+ [
+ (1, (0, 0.8, 1.99994)),
+ ],
+ ),
+ # Case 4
+ pytest.param(
+ (0, 0.25, 1),
+ (-1, 0, 0.4),
+ [
+ (1, (0, 0.625, 1)),
+ (0.8, (0.625, 1, 1)),
+ ],
+ ),
+ pytest.param(
+ (0.25, 0.3, 1.05),
+ (0, 0.2, 0.4),
+ [
+ (1, (0.25, 0.5, 1)),
+ (2.6 / 3, (0.5, 1, 1)),
+ ],
+ ),
+ # Case 4 boundary
+ pytest.param(
+ (0.25, 0.5, 1),
+ (0, 0.25, 0.5),
+ [
+ (1, (0, 1, 1)),
+ ],
+ ),
+ #
+ # With gain:
+ #
+ # Case 3a/1neg
+ pytest.param(
+ (0.0, 0.5, 1),
+ (0, 0.5, 1),
+ [
+ (1, None),
+ (-1, (0, 1, 1)),
+ (-1, (-1, -1, 0)),
+ ],
+ ),
+ pytest.param(
+ (0.0, 0.5, 1),
+ (0, 0.5, 0.75),
+ [
+ (1, None),
+ (-0.5, (0, 1, 1)),
+ (-1, (-1, -1, 0)),
+ ],
+ ),
+ pytest.param(
+ (0.0, 0.5, 1),
+ (0, 0.25, 0.8),
+ [
+ (0.5, None),
+ (0.5, (0, 0.45454545, 0.9090909090)),
+ (-0.1, (0.9090909090, 1.0, 1.0)),
+ (-0.5, (-1, -1, 0)),
+ ],
+ ),
+ # Case 3a/1neg
+ pytest.param(
+ (0.0, 0.5, 2),
+ (0.2, 0.5, 0.8),
+ [
+ (1, None),
+ (-0.2, (0, 1, 1)),
+ (-0.6, (-1, -1, 0)),
+ ],
+ ),
+ # Case 3a/1neg
+ pytest.param(
+ (0.0, 0.5, 2),
+ (0.2, 0.5, 1),
+ [
+ (1, None),
+ (-1 / 3, (0, 1, 1)),
+ (-0.6, (-1, -1, 0)),
+ ],
+ ),
+ # Case 3
+ pytest.param(
+ (0, 0.5, 1),
+ (0.25, 0.25, 0.75),
+ [
+ (0.5, None),
+ (0.5, (0, 0.5, 1.0)),
+ ],
+ ),
+ # Case 1neg
+ pytest.param(
+ (0.0, 0.5, 1),
+ (0, 0.25, 0.5),
+ [
+ (0.5, None),
+ (0.5, (0, 1, 1)),
+ (-0.5, (-1, -1, 0)),
+ ],
+ ),
+ # Case 2neg
+ pytest.param(
+ (0.05, 0.55, 1),
+ (0, 0.25, 0.5),
+ [
+ (0.4, None),
+ (0.5, (0, 1, 1)),
+ (-0.4, (-1, -0.8, 0)),
+ (-0.4, (-1, -1, -0.8)),
+ ],
+ ),
+ # Case 2neg, other side
+ pytest.param(
+ (-1, -0.55, -0.05),
+ (-0.5, -0.25, 0),
+ [
+ (0.4, None),
+ (0.5, (-1, -1, 0)),
+ (-0.4, (0, 0.8, 1)),
+ (-0.4, (0.8, 1, 1)),
+ ],
+ ),
+ #
+ # Misc corner cases
+ #
+ pytest.param(
+ (0.5, 0.5, 0.5),
+ (0.5, 0.5, 0.5),
+ [
+ (1, None),
+ ],
+ ),
+ pytest.param(
+ (0.3, 0.5, 0.7),
+ (0.1, 0.5, 0.9),
+ [
+ (1, None),
+ (-1, (0, 0.5, 1)),
+ (-1, (0.5, 1, 1)),
+ (-1, (-1, -0.5, 0)),
+ (-1, (-1, -1, -0.5)),
+ ],
+ ),
+ pytest.param(
+ (0.5, 0.5, 0.5),
+ (0.25, 0.25, 0.5),
+ [
+ (1, (1, 1, 1)),
+ ],
+ ),
+ pytest.param(
+ (0.5, 0.5, 0.5),
+ (0.25, 0.35, 0.5),
+ [
+ (1, (1, 1, 1)),
+ ],
+ ),
+ pytest.param(
+ (0.5, 0.5, 0.55),
+ (0.25, 0.35, 0.5),
+ [
+ (1, (1, 1, 1)),
+ ],
+ ),
+ pytest.param(
+ (0.5, 0.5, 1),
+ (0.5, 0.5, 1),
+ [
+ (1, None),
+ (-1, (0, 1, 1)),
+ ],
+ ),
+ pytest.param(
+ (0.25, 0.5, 1),
+ (0.5, 0.5, 1),
+ [
+ (1, None),
+ (-1, (0, 1, 1)),
+ ],
+ ),
+ pytest.param(
+ (0, 0.2, 1),
+ (0, 0, 0.5),
+ [
+ (1, (0, 0.4, 1.99994)),
+ ],
+ ),
+ # https://github.com/fonttools/fonttools/issues/3139
+ pytest.param(
+ (0, 0.5, 1),
+ (-1, 0.25, 1),
+ [
+ (0.5, None),
+ (0.5, (0.0, 1 / 3, 2 / 3)),
+ (-0.5, (2 / 3, 1, 1)),
+ (-0.5, (-1, -0.2, 0)),
+ (-0.5, (-1, -1, -0.2)),
+ ],
+ ),
+ # Dirac delta at new default. Fancy!
+ pytest.param(
+ (0.5, 0.5, 0.5),
+ (0, 0.5, 1),
+ [
+ (1, None),
+ (-1, (0, 0.0001220703, 1)),
+ (-1, (0.0001220703, 1, 1)),
+ (-1, (-1, -0.0001220703, 0)),
+ (-1, (-1, -1, -0.0001220703)),
+ ],
+ ),
+ # https://github.com/fonttools/fonttools/issues/3177
+ pytest.param(
+ (0, 1, 1),
+ (-1, -0.5, +1, 1, 1),
+ [
+ (1.0, (1 / 3, 1.0, 1.0)),
+ ],
+ ),
+ pytest.param(
+ (0, 1, 1),
+ (-1, -0.5, +1, 2, 1),
+ [
+ (1.0, (0.5, 1.0, 1.0)),
+ ],
+ ),
+ # https://github.com/fonttools/fonttools/issues/3291
+ pytest.param(
+ (0.6, 0.7, 0.8),
+ (-1, 0.2, +1, 1, 1),
+ [
+ (1.0, (0.5, 0.625, 0.75)),
+ ],
+ ),
+ ],
+ )
+ def test_rebaseTent(self, tent, axisRange, expected):
+ axisRange = NormalizedAxisTripleAndDistances(*axisRange)
+
+ sol = solver.rebaseTent(tent, axisRange)
+
+ a = pytest.approx
+ expected = [
+ (a(scalar), (a(v[0]), a(v[1]), a(v[2])) if v is not None else None)
+ for scalar, v in expected
+ ]
+
+ assert sol == expected, (tent, axisRange)
diff --git a/Tests/varLib/interpolatable_test.py b/Tests/varLib/interpolatable_test.py
index a30be71e..10b9cc30 100644
--- a/Tests/varLib/interpolatable_test.py
+++ b/Tests/varLib/interpolatable_test.py
@@ -5,6 +5,7 @@ import shutil
import sys
import tempfile
import unittest
+import pytest
try:
import scipy
@@ -35,12 +36,12 @@ class InterpolatableTest(unittest.TestCase):
shutil.rmtree(self.tempdir)
@staticmethod
- def get_test_input(test_file_or_folder):
+ def get_test_input(*test_file_or_folder):
path, _ = os.path.split(__file__)
- return os.path.join(path, "data", test_file_or_folder)
+ return os.path.join(path, "data", *test_file_or_folder)
@staticmethod
- def get_file_list(folder, suffix, prefix=''):
+ def get_file_list(folder, suffix, prefix=""):
all_files = os.listdir(folder)
file_list = []
for p in all_files:
@@ -51,8 +52,7 @@ class InterpolatableTest(unittest.TestCase):
def temp_path(self, suffix):
self.temp_dir()
self.num_tempfiles += 1
- return os.path.join(self.tempdir,
- "tmp%d%s" % (self.num_tempfiles, suffix))
+ return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
def temp_dir(self):
if not self.tempdir:
@@ -60,41 +60,201 @@ class InterpolatableTest(unittest.TestCase):
def compile_font(self, path, suffix, temp_dir):
ttx_filename = os.path.basename(path)
- savepath = os.path.join(temp_dir, ttx_filename.replace('.ttx', suffix))
+ savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.importXML(path)
font.save(savepath, reorderTables=None)
return font, savepath
-# -----
-# Tests
-# -----
+ # -----
+ # Tests
+ # -----
def test_interpolatable_ttf(self):
- suffix = '.ttf'
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ suffix = ".ttf"
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
ttf_paths = self.get_file_list(self.tempdir, suffix)
self.assertIsNone(interpolatable_main(ttf_paths))
-
def test_interpolatable_otf(self):
- suffix = '.otf'
- ttx_dir = self.get_test_input('master_ttx_interpolatable_otf')
+ suffix = ".otf"
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_otf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
otf_paths = self.get_file_list(self.tempdir, suffix)
self.assertIsNone(interpolatable_main(otf_paths))
+ def test_interpolatable_ufo(self):
+ ttx_dir = self.get_test_input("master_ufo")
+ ufo_paths = self.get_file_list(ttx_dir, ".ufo", "TestFamily2-")
+ self.assertIsNone(interpolatable_main(ufo_paths))
+
+ def test_designspace(self):
+ designspace_path = self.get_test_input("InterpolateLayout.designspace")
+ self.assertIsNone(interpolatable_main([designspace_path]))
+
+ def test_glyphsapp(self):
+ pytest.importorskip("glyphsLib")
+ glyphsapp_path = self.get_test_input("InterpolateLayout.glyphs")
+ self.assertIsNone(interpolatable_main([glyphsapp_path]))
+
+ def test_VF(self):
+ suffix = ".ttf"
+ ttx_dir = self.get_test_input("master_ttx_varfont_ttf")
+
+ self.temp_dir()
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "SparseMasters-")
+ for path in ttx_paths:
+ self.compile_font(path, suffix, self.tempdir)
+
+ ttf_paths = self.get_file_list(self.tempdir, suffix)
+
+ problems = interpolatable_main(["--quiet"] + ttf_paths)
+ self.assertIsNone(problems)
+
+ def test_sparse_interpolatable_ttfs(self):
+ suffix = ".ttf"
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
+
+ self.temp_dir()
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "SparseMasters-")
+ for path in ttx_paths:
+ self.compile_font(path, suffix, self.tempdir)
+
+ ttf_paths = self.get_file_list(self.tempdir, suffix)
+
+ # without --ignore-missing
+ problems = interpolatable_main(["--quiet"] + ttf_paths)
+ self.assertEqual(
+ problems["a"], [{"type": "missing", "master": "SparseMasters-Medium"}]
+ )
+ self.assertEqual(
+ problems["s"], [{"type": "missing", "master": "SparseMasters-Medium"}]
+ )
+ self.assertEqual(
+ problems["edotabove"],
+ [{"type": "missing", "master": "SparseMasters-Medium"}],
+ )
+ self.assertEqual(
+ problems["dotabovecomb"],
+ [{"type": "missing", "master": "SparseMasters-Medium"}],
+ )
+
+ # normal order, with --ignore-missing
+ self.assertIsNone(interpolatable_main(["--ignore-missing"] + ttf_paths))
+ # purposely putting the sparse master (medium) first
+ self.assertIsNone(
+ interpolatable_main(
+ ["--ignore-missing"] + [ttf_paths[1]] + [ttf_paths[0]] + [ttf_paths[2]]
+ )
+ )
+ # purposely putting the sparse master (medium) last
+ self.assertIsNone(
+ interpolatable_main(
+ ["--ignore-missing"] + [ttf_paths[0]] + [ttf_paths[2]] + [ttf_paths[1]]
+ )
+ )
+
+ def test_sparse_interpolatable_ufos(self):
+ ttx_dir = self.get_test_input("master_ufo")
+ ufo_paths = self.get_file_list(ttx_dir, ".ufo", "SparseMasters-")
+
+ # without --ignore-missing
+ problems = interpolatable_main(["--quiet"] + ufo_paths)
+ self.assertEqual(
+ problems["a"], [{"type": "missing", "master": "SparseMasters-Medium"}]
+ )
+ self.assertEqual(
+ problems["s"], [{"type": "missing", "master": "SparseMasters-Medium"}]
+ )
+ self.assertEqual(
+ problems["edotabove"],
+ [{"type": "missing", "master": "SparseMasters-Medium"}],
+ )
+ self.assertEqual(
+ problems["dotabovecomb"],
+ [{"type": "missing", "master": "SparseMasters-Medium"}],
+ )
+
+ # normal order, with --ignore-missing
+ self.assertIsNone(interpolatable_main(["--ignore-missing"] + ufo_paths))
+ # purposely putting the sparse master (medium) first
+ self.assertIsNone(
+ interpolatable_main(
+ ["--ignore-missing"] + [ufo_paths[1]] + [ufo_paths[0]] + [ufo_paths[2]]
+ )
+ )
+ # purposely putting the sparse master (medium) last
+ self.assertIsNone(
+ interpolatable_main(
+ ["--ignore-missing"] + [ufo_paths[0]] + [ufo_paths[2]] + [ufo_paths[1]]
+ )
+ )
+
+ def test_sparse_designspace(self):
+ designspace_path = self.get_test_input("SparseMasters_ufo.designspace")
+
+ problems = interpolatable_main(["--quiet", designspace_path])
+ self.assertEqual(
+ problems["a"], [{"type": "missing", "master": "SparseMasters-Medium"}]
+ )
+ self.assertEqual(
+ problems["s"], [{"type": "missing", "master": "SparseMasters-Medium"}]
+ )
+ self.assertEqual(
+ problems["edotabove"],
+ [{"type": "missing", "master": "SparseMasters-Medium"}],
+ )
+ self.assertEqual(
+ problems["dotabovecomb"],
+ [{"type": "missing", "master": "SparseMasters-Medium"}],
+ )
+
+ # normal order, with --ignore-missing
+ self.assertIsNone(interpolatable_main(["--ignore-missing", designspace_path]))
+
+ def test_sparse_glyphsapp(self):
+ pytest.importorskip("glyphsLib")
+ glyphsapp_path = self.get_test_input("SparseMasters.glyphs")
+
+ problems = interpolatable_main(["--quiet", glyphsapp_path])
+ self.assertEqual(
+ problems["a"], [{"type": "missing", "master": "Sparse Masters-Medium"}]
+ )
+ self.assertEqual(
+ problems["s"], [{"type": "missing", "master": "Sparse Masters-Medium"}]
+ )
+ self.assertEqual(
+ problems["edotabove"],
+ [{"type": "missing", "master": "Sparse Masters-Medium"}],
+ )
+ self.assertEqual(
+ problems["dotabovecomb"],
+ [{"type": "missing", "master": "Sparse Masters-Medium"}],
+ )
+
+ # normal order, with --ignore-missing
+ self.assertIsNone(interpolatable_main(["--ignore-missing", glyphsapp_path]))
+
+ def test_interpolatable_varComposite(self):
+ input_path = self.get_test_input(
+ "..", "..", "ttLib", "data", "varc-ac00-ac01.ttf"
+ )
+ # This particular test font which was generated by machine-learning
+ # exhibits an "error" in one of the masters; it's a false-positive.
+ # Just make sure the code runs.
+ interpolatable_main((input_path,))
+
if __name__ == "__main__":
sys.exit(unittest.main())
diff --git a/Tests/varLib/interpolate_layout_test.py b/Tests/varLib/interpolate_layout_test.py
index 219f087f..1844e3b1 100644
--- a/Tests/varLib/interpolate_layout_test.py
+++ b/Tests/varLib/interpolate_layout_test.py
@@ -39,7 +39,7 @@ class InterpolateLayoutTest(unittest.TestCase):
return os.path.join(path, "data", "test_results", test_file_or_folder)
@staticmethod
- def get_file_list(folder, suffix, prefix=''):
+ def get_file_list(folder, suffix, prefix=""):
all_files = os.listdir(folder)
file_list = []
for p in all_files:
@@ -50,8 +50,7 @@ class InterpolateLayoutTest(unittest.TestCase):
def temp_path(self, suffix):
self.temp_dir()
self.num_tempfiles += 1
- return os.path.join(self.tempdir,
- "tmp%d%s" % (self.num_tempfiles, suffix))
+ return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
def temp_dir(self):
if not self.tempdir:
@@ -75,7 +74,8 @@ class InterpolateLayoutTest(unittest.TestCase):
expected = self.read_ttx(expected_ttx)
if actual != expected:
for line in difflib.unified_diff(
- expected, actual, fromfile=expected_ttx, tofile=path):
+ expected, actual, fromfile=expected_ttx, tofile=path
+ ):
sys.stdout.write(line)
self.fail("TTX output is different from expected")
@@ -85,19 +85,21 @@ class InterpolateLayoutTest(unittest.TestCase):
font.save(path)
self.expect_ttx(TTFont(path), expected_ttx, tables)
- def compile_font(self, path, suffix, temp_dir, features=None):
+ def compile_font(self, path, suffix, temp_dir, features=None, cfg=None):
ttx_filename = os.path.basename(path)
- savepath = os.path.join(temp_dir, ttx_filename.replace('.ttx', suffix))
+ savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
+ if cfg:
+ font.cfg.update(cfg)
font.importXML(path)
if features:
addOpenTypeFeaturesFromString(font, features)
font.save(savepath, reorderTables=None)
return font, savepath
-# -----
-# Tests
-# -----
+ # -----
+ # Tests
+ # -----
def test_varlib_interpolate_layout_GSUB_only_ttf(self):
"""Only GSUB, and only in the base master.
@@ -105,49 +107,47 @@ class InterpolateLayoutTest(unittest.TestCase):
The variable font will inherit the GSUB table from the
base master.
"""
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GSUB']
- expected_ttx_path = self.get_test_output('InterpolateLayout.ttx')
+ tables = ["GSUB"]
+ expected_ttx_path = self.get_test_output("InterpolateLayout.ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
-
def test_varlib_interpolate_layout_no_GSUB_ttf(self):
"""The base master has no GSUB table.
The variable font will end up without a GSUB table.
"""
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout2.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout2.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GSUB']
- expected_ttx_path = self.get_test_output('InterpolateLayout2.ttx')
+ tables = ["GSUB"]
+ expected_ttx_path = self.get_test_output("InterpolateLayout2.ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
-
def test_varlib_interpolate_layout_GSUB_only_no_axes_ttf(self):
"""Only GSUB, and only in the base master.
Designspace file has no <axes> element.
@@ -155,17 +155,16 @@ class InterpolateLayoutTest(unittest.TestCase):
The variable font will inherit the GSUB table from the
base master.
"""
- ds_path = self.get_test_input('InterpolateLayout3.designspace')
+ ds_path = self.get_test_input("InterpolateLayout3.designspace")
with self.assertRaisesRegex(DesignSpaceDocumentError, "No axes defined"):
- instfont = interpolate_layout(ds_path, {'weight': 500})
+ instfont = interpolate_layout(ds_path, {"weight": 500})
def test_varlib_interpolate_layout_GPOS_only_size_feat_same_val_ttf(self):
- """Only GPOS; 'size' feature; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; 'size' feature; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
feature size {
@@ -175,26 +174,26 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_size_feat_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output(
+ "InterpolateLayoutGPOS_size_feat_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_1_same_val_ttf(self):
- """Only GPOS; LookupType 1; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 1; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
feature xxxx {
@@ -204,26 +203,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_1_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_1_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_1_diff_val_ttf(self):
- """Only GPOS; LookupType 1; different values in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 1; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
feature xxxx {
@@ -238,26 +235,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_1_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_1_diff.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_1_diff2_val_ttf(self):
- """Only GPOS; LookupType 1; different values and items in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 1; different values and items in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
feature xxxx {
@@ -273,26 +268,26 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_1_diff2.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_1_diff2.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_2_spec_pairs_same_val_ttf(self):
- """Only GPOS; LookupType 2 specific pairs; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_2_spec_pairs_same_val_ttf(
+ self,
+ ):
+ """Only GPOS; LookupType 2 specific pairs; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
feature xxxx {
@@ -302,26 +297,28 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_2_spec_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output(
+ "InterpolateLayoutGPOS_2_spec_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_2_spec_pairs_diff_val_ttf(self):
- """Only GPOS; LookupType 2 specific pairs; different values in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_2_spec_pairs_diff_val_ttf(
+ self,
+ ):
+ """Only GPOS; LookupType 2 specific pairs; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
feature xxxx {
@@ -336,26 +333,28 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_2_spec_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output(
+ "InterpolateLayoutGPOS_2_spec_diff.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_2_spec_pairs_diff2_val_ttf(self):
- """Only GPOS; LookupType 2 specific pairs; different values and items in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_2_spec_pairs_diff2_val_ttf(
+ self,
+ ):
+ """Only GPOS; LookupType 2 specific pairs; different values and items in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
feature xxxx {
@@ -371,26 +370,28 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_2_spec_diff2.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output(
+ "InterpolateLayoutGPOS_2_spec_diff2.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_2_class_pairs_same_val_ttf(self):
- """Only GPOS; LookupType 2 class pairs; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_2_class_pairs_same_val_ttf(
+ self,
+ ):
+ """Only GPOS; LookupType 2 class pairs; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
feature xxxx {
@@ -400,26 +401,28 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_2_class_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output(
+ "InterpolateLayoutGPOS_2_class_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_2_class_pairs_diff_val_ttf(self):
- """Only GPOS; LookupType 2 class pairs; different values in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_2_class_pairs_diff_val_ttf(
+ self,
+ ):
+ """Only GPOS; LookupType 2 class pairs; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
feature xxxx {
@@ -434,26 +437,28 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_2_class_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output(
+ "InterpolateLayoutGPOS_2_class_diff.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_2_class_pairs_diff2_val_ttf(self):
- """Only GPOS; LookupType 2 class pairs; different values and items in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_2_class_pairs_diff2_val_ttf(
+ self,
+ ):
+ """Only GPOS; LookupType 2 class pairs; different values and items in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
feature xxxx {
@@ -469,26 +474,26 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_2_class_diff2.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output(
+ "InterpolateLayoutGPOS_2_class_diff2.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_3_same_val_ttf(self):
- """Only GPOS; LookupType 3; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 3; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
feature xxxx {
@@ -498,26 +503,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_3_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_3_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_3_diff_val_ttf(self):
- """Only GPOS; LookupType 3; different values in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 3; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
feature xxxx {
@@ -532,26 +535,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_3_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_3_diff.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_4_same_val_ttf(self):
- """Only GPOS; LookupType 4; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 4; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
@@ -562,26 +563,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_4_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_4_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_4_diff_val_ttf(self):
- """Only GPOS; LookupType 4; different values in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 4; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
@@ -598,26 +597,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_4_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_4_diff.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_5_same_val_ttf(self):
- """Only GPOS; LookupType 5; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 5; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
markClass uni0330 <anchor 0 -50> @MARKS_BELOW;
@@ -629,26 +626,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_5_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_5_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_5_diff_val_ttf(self):
- """Only GPOS; LookupType 5; different values in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 5; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
markClass uni0330 <anchor 0 -50> @MARKS_BELOW;
@@ -667,26 +662,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_5_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_5_diff.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_6_same_val_ttf(self):
- """Only GPOS; LookupType 6; same values in all masters.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 6; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
@@ -697,26 +690,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_6_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_6_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_6_diff_val_ttf(self):
- """Only GPOS; LookupType 6; different values in each master.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 6; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
@@ -733,26 +724,112 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_6_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_6_diff.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_7_same_val_ttf(self):
+ """Only GPOS; LookupType 7; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
- def test_varlib_interpolate_layout_GPOS_only_LookupType_8_same_val_ttf(self):
- """Only GPOS; LookupType 8; same values in all masters.
+ fea_str = """
+ markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
+ lookup CNTXT_PAIR_POS {
+ pos A a -23;
+ } CNTXT_PAIR_POS;
+
+ lookup CNTXT_MARK_TO_BASE {
+ pos base a <anchor 260 500> mark @MARKS_ABOVE;
+ } CNTXT_MARK_TO_BASE;
+
+ feature xxxx {
+ pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
+ } xxxx;
"""
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ features = [fea_str] * 2
+
+ self.temp_dir()
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
+ cfg = {"fontTools.otlLib.builder:WRITE_GPOS7": True}
+ for i, path in enumerate(ttx_paths):
+ self.compile_font(path, suffix, self.tempdir, features[i], cfg)
+
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
+
+ tables = ["GPOS"]
+ 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_7_diff_val_ttf(self):
+ """Only GPOS; LookupType 7; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
+
+ fea_str_0 = """
+ markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
+ lookup CNTXT_PAIR_POS {
+ pos A a -23;
+ } CNTXT_PAIR_POS;
+
+ lookup CNTXT_MARK_TO_BASE {
+ pos base a <anchor 260 500> mark @MARKS_ABOVE;
+ } CNTXT_MARK_TO_BASE;
+
+ feature xxxx {
+ pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
+ } xxxx;
+ """
+ fea_str_1 = """
+ markClass uni0303 <anchor 0 520> @MARKS_ABOVE;
+ lookup CNTXT_PAIR_POS {
+ pos A a 57;
+ } CNTXT_PAIR_POS;
+
+ lookup CNTXT_MARK_TO_BASE {
+ pos base a <anchor 285 520> mark @MARKS_ABOVE;
+ } CNTXT_MARK_TO_BASE;
+
+ feature xxxx {
+ pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
+ } xxxx;
+ """
+ features = [fea_str_0, fea_str_1]
+
+ self.temp_dir()
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
+ cfg = {"fontTools.otlLib.builder:WRITE_GPOS7": True}
+ for i, path in enumerate(ttx_paths):
+ self.compile_font(path, suffix, self.tempdir, features[i], cfg)
+
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
+
+ tables = ["GPOS"]
+ 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)
+
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_8_same_val_ttf(self):
+ """Only GPOS; LookupType 8; same values in all masters."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
@@ -771,26 +848,24 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str] * 2
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_8_same.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_8_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.
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('InterpolateLayout.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Only GPOS; LookupType 8; different values in each master."""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("InterpolateLayout.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
@@ -823,49 +898,47 @@ class InterpolateLayoutTest(unittest.TestCase):
features = [fea_str_0, fea_str_1]
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i])
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
- instfont = interpolate_layout(ds_path, {'weight': 500}, finder)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
+ instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
- tables = ['GPOS']
- expected_ttx_path = self.get_test_output('InterpolateLayoutGPOS_8_diff.ttx')
+ tables = ["GPOS"]
+ expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_8_diff.ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
-
def test_varlib_interpolate_layout_main_ttf(self):
- """Mostly for testing varLib.interpolate_layout.main()
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('Build.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Mostly for testing varLib.interpolate_layout.main()"""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("Build.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
self.temp_dir()
- ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable')
+ ttf_dir = os.path.join(self.tempdir, "master_ttf_interpolatable")
os.makedirs(ttf_dir)
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-")
for path in ttx_paths:
self.compile_font(path, suffix, ttf_dir)
- finder = lambda s: s.replace(ufo_dir, ttf_dir).replace('.ufo', suffix)
+ finder = lambda s: s.replace(ufo_dir, ttf_dir).replace(".ufo", suffix)
varfont, _, _ = build(ds_path, finder)
- varfont_name = 'InterpolateLayoutMain'
+ varfont_name = "InterpolateLayoutMain"
varfont_path = os.path.join(self.tempdir, varfont_name + suffix)
varfont.save(varfont_path)
- ds_copy = os.path.splitext(varfont_path)[0] + '.designspace'
+ ds_copy = os.path.splitext(varfont_path)[0] + ".designspace"
shutil.copy2(ds_path, ds_copy)
- args = [ds_copy, 'weight=500', 'contrast=50']
+ args = [ds_copy, "weight=500", "contrast=50"]
interpolate_layout_main(args)
- instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix
+ instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix
instfont = TTFont(instfont_path)
- tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head']
- expected_ttx_path = self.get_test_output(varfont_name + '.ttx')
+ tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"]
+ expected_ttx_path = self.get_test_output(varfont_name + ".ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
diff --git a/Tests/varLib/iup_test.py b/Tests/varLib/iup_test.py
index 76b2af51..36f63e0e 100644
--- a/Tests/varLib/iup_test.py
+++ b/Tests/varLib/iup_test.py
@@ -4,40 +4,109 @@ import pytest
class IupTest:
-
-# -----
-# Tests
-# -----
+ # -----
+ # Tests
+ # -----
@pytest.mark.parametrize(
"delta, coords, forced",
[
- (
- [(0, 0)],
- [(1, 2)],
- set()
- ),
- (
- [(0, 0), (0, 0), (0, 0)],
- [(1, 2), (3, 2), (2, 3)],
- set()
- ),
+ ([(0, 0)], [(1, 2)], set()),
+ ([(0, 0), (0, 0), (0, 0)], [(1, 2), (3, 2), (2, 3)], set()),
(
[(1, 1), (-1, 1), (-1, -1), (1, -1)],
[(0, 0), (2, 0), (2, 2), (0, 2)],
- set()
+ set(),
),
(
- [(-1, 0), (-1, 0), (-1, 0), (-1, 0), (-1, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (-1, 0)],
- [(-35, -152), (-86, -101), (-50, -65), (0, -116), (51, -65), (86, -99), (35, -151), (87, -202), (51, -238), (-1, -187), (-53, -239), (-88, -205)],
- {11}
+ [
+ (-1, 0),
+ (-1, 0),
+ (-1, 0),
+ (-1, 0),
+ (-1, 0),
+ (0, 0),
+ (0, 0),
+ (0, 0),
+ (0, 0),
+ (0, 0),
+ (0, 0),
+ (-1, 0),
+ ],
+ [
+ (-35, -152),
+ (-86, -101),
+ (-50, -65),
+ (0, -116),
+ (51, -65),
+ (86, -99),
+ (35, -151),
+ (87, -202),
+ (51, -238),
+ (-1, -187),
+ (-53, -239),
+ (-88, -205),
+ ],
+ {11},
),
(
- [(0, 0), (1, 0), (2, 0), (2, 0), (0, 0), (1, 0), (3, 0), (3, 0), (2, 0), (2, 0), (0, 0), (0, 0), (-1, 0), (-1, 0), (-1, 0), (-3, 0), (-1, 0), (0, 0), (0, 0), (-2, 0), (-2, 0), (-1, 0), (-1, 0), (-1, 0), (-4, 0)],
- [(330, 65), (401, 65), (499, 117), (549, 225), (549, 308), (549, 422), (549, 500), (497, 600), (397, 648), (324, 648), (271, 648), (200, 620), (165, 570), (165, 536), (165, 473), (252, 407), (355, 407), (396, 407), (396, 333), (354, 333), (249, 333), (141, 268), (141, 203), (141, 131), (247, 65)],
- {5, 15, 24}
+ [
+ (0, 0),
+ (1, 0),
+ (2, 0),
+ (2, 0),
+ (0, 0),
+ (1, 0),
+ (3, 0),
+ (3, 0),
+ (2, 0),
+ (2, 0),
+ (0, 0),
+ (0, 0),
+ (-1, 0),
+ (-1, 0),
+ (-1, 0),
+ (-3, 0),
+ (-1, 0),
+ (0, 0),
+ (0, 0),
+ (-2, 0),
+ (-2, 0),
+ (-1, 0),
+ (-1, 0),
+ (-1, 0),
+ (-4, 0),
+ ],
+ [
+ (330, 65),
+ (401, 65),
+ (499, 117),
+ (549, 225),
+ (549, 308),
+ (549, 422),
+ (549, 500),
+ (497, 600),
+ (397, 648),
+ (324, 648),
+ (271, 648),
+ (200, 620),
+ (165, 570),
+ (165, 536),
+ (165, 473),
+ (252, 407),
+ (355, 407),
+ (396, 407),
+ (396, 333),
+ (354, 333),
+ (249, 333),
+ (141, 268),
+ (141, 203),
+ (141, 131),
+ (247, 65),
+ ],
+ {5, 15, 24},
),
- ]
+ ],
)
def test_forced_set(self, delta, coords, forced):
f = iup._iup_contour_bound_forced_set(delta, coords)
@@ -49,5 +118,6 @@ class IupTest:
assert chain1 == chain2, f
assert costs1 == costs2, f
+
if __name__ == "__main__":
sys.exit(pytest.main(sys.argv))
diff --git a/Tests/varLib/merger_test.py b/Tests/varLib/merger_test.py
index aa7a6998..e44d466d 100644
--- a/Tests/varLib/merger_test.py
+++ b/Tests/varLib/merger_test.py
@@ -7,6 +7,7 @@ from fontTools.varLib.models import VariationModel
from fontTools.ttLib import TTFont
from fontTools.ttLib.tables import otTables as ot
from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
+from io import BytesIO
import pytest
@@ -1842,3 +1843,102 @@ class COLRVariationMergerTest:
if colr.table.LayerList:
assert len({id(p) for p in colr.table.LayerList.Paint}) == after_layer_count
+
+
+class SparsePositioningMergerTest:
+ def test_zero_kern_at_default(self):
+ # https://github.com/fonttools/fonttools/issues/3111
+
+ pytest.importorskip("ufo2ft")
+ pytest.importorskip("ufoLib2")
+
+ from fontTools.designspaceLib import DesignSpaceDocument
+ from ufo2ft import compileVariableTTF
+ from ufoLib2 import Font
+
+ ds = DesignSpaceDocument()
+ ds.addAxisDescriptor(
+ name="wght", tag="wght", minimum=100, maximum=900, default=400
+ )
+ ds.addSourceDescriptor(font=Font(), location=dict(wght=100))
+ ds.addSourceDescriptor(font=Font(), location=dict(wght=400))
+ ds.addSourceDescriptor(font=Font(), location=dict(wght=900))
+
+ ds.sources[0].font.newGlyph("a").unicode = ord("a")
+ ds.sources[0].font.newGlyph("b").unicode = ord("b")
+ ds.sources[0].font.features.text = "feature kern { pos a b b' 100; } kern;"
+
+ ds.sources[1].font.newGlyph("a").unicode = ord("a")
+ ds.sources[1].font.newGlyph("b").unicode = ord("b")
+ ds.sources[1].font.features.text = "feature kern { pos a b b' 0; } kern;"
+
+ ds.sources[2].font.newGlyph("a").unicode = ord("a")
+ ds.sources[2].font.newGlyph("b").unicode = ord("b")
+ ds.sources[2].font.features.text = "feature kern { pos a b b' -100; } kern;"
+
+ font = compileVariableTTF(ds, inplace=True)
+ b = BytesIO()
+ font.save(b)
+
+ assert font["GDEF"].table.VarStore.VarData[0].Item[0] == [100, -100]
+
+ def test_sparse_cursive(self):
+ # https://github.com/fonttools/fonttools/issues/3168
+
+ pytest.importorskip("ufo2ft")
+ pytest.importorskip("ufoLib2")
+
+ from fontTools.designspaceLib import DesignSpaceDocument
+ from ufo2ft import compileVariableTTF
+ from ufoLib2 import Font
+
+ ds = DesignSpaceDocument()
+ ds.addAxisDescriptor(
+ name="wght", tag="wght", minimum=100, maximum=900, default=400
+ )
+ ds.addSourceDescriptor(font=Font(), location=dict(wght=100))
+ ds.addSourceDescriptor(font=Font(), location=dict(wght=400))
+ ds.addSourceDescriptor(font=Font(), location=dict(wght=900))
+
+ ds.sources[0].font.newGlyph("a").unicode = ord("a")
+ ds.sources[0].font.newGlyph("b").unicode = ord("b")
+ ds.sources[0].font.newGlyph("c").unicode = ord("c")
+ ds.sources[
+ 0
+ ].font.features.text = """
+ feature curs {
+ position cursive a <anchor 400 20> <anchor 0 -20>;
+ position cursive c <anchor NULL> <anchor 0 -20>;
+ } curs;
+ """
+
+ ds.sources[1].font.newGlyph("a").unicode = ord("a")
+ ds.sources[1].font.newGlyph("b").unicode = ord("b")
+ ds.sources[1].font.newGlyph("c").unicode = ord("c")
+ ds.sources[
+ 1
+ ].font.features.text = """
+ feature curs {
+ position cursive a <anchor 500 20> <anchor 0 -20>;
+ position cursive b <anchor 50 22> <anchor 0 -10>;
+ position cursive c <anchor NULL> <anchor 0 -20>;
+ } curs;
+ """
+
+ ds.sources[2].font.newGlyph("a").unicode = ord("a")
+ ds.sources[2].font.newGlyph("b").unicode = ord("b")
+ ds.sources[2].font.newGlyph("c").unicode = ord("c")
+ ds.sources[
+ 2
+ ].font.features.text = """
+ feature curs {
+ position cursive b <anchor 100 40> <anchor 0 -30>;
+ position cursive c <anchor NULL> <anchor 0 -20>;
+ } curs;
+ """
+
+ font = compileVariableTTF(ds, inplace=True)
+ b = BytesIO()
+ font.save(b)
+
+ assert font["GDEF"].table.VarStore.VarData[0].Item[0] == [-100, 0]
diff --git a/Tests/varLib/models_test.py b/Tests/varLib/models_test.py
index e0080129..11ec1a1e 100644
--- a/Tests/varLib/models_test.py
+++ b/Tests/varLib/models_test.py
@@ -31,15 +31,130 @@ def test_normalizeLocation():
assert normalizeLocation({"wght": 1001}, axes) == {"wght": 0.0}
+@pytest.mark.parametrize(
+ "axes, location, expected",
+ [
+ # lower != default != upper
+ ({"wght": (100, 400, 900)}, {"wght": 1000}, {"wght": 1.2}),
+ ({"wght": (100, 400, 900)}, {"wght": 900}, {"wght": 1.0}),
+ ({"wght": (100, 400, 900)}, {"wght": 650}, {"wght": 0.5}),
+ ({"wght": (100, 400, 900)}, {"wght": 400}, {"wght": 0.0}),
+ ({"wght": (100, 400, 900)}, {"wght": 250}, {"wght": -0.5}),
+ ({"wght": (100, 400, 900)}, {"wght": 100}, {"wght": -1.0}),
+ ({"wght": (100, 400, 900)}, {"wght": 25}, {"wght": -1.25}),
+ # lower == default != upper
+ (
+ {"wght": (400, 400, 900), "wdth": (100, 100, 150)},
+ {"wght": 1000, "wdth": 200},
+ {"wght": 1.2, "wdth": 2.0},
+ ),
+ (
+ {"wght": (400, 400, 900), "wdth": (100, 100, 150)},
+ {"wght": 25, "wdth": 25},
+ {"wght": -0.75, "wdth": -1.5},
+ ),
+ # lower != default == upper
+ (
+ {"wght": (100, 400, 400), "wdth": (50, 100, 100)},
+ {"wght": 700, "wdth": 150},
+ {"wght": 1.0, "wdth": 1.0},
+ ),
+ (
+ {"wght": (100, 400, 400), "wdth": (50, 100, 100)},
+ {"wght": -50, "wdth": 25},
+ {"wght": -1.5, "wdth": -1.5},
+ ),
+ # degenerate case with lower == default == upper, normalized location always 0
+ ({"wght": (400, 400, 400)}, {"wght": 100}, {"wght": 0.0}),
+ ({"wght": (400, 400, 400)}, {"wght": 400}, {"wght": 0.0}),
+ ({"wght": (400, 400, 400)}, {"wght": 700}, {"wght": 0.0}),
+ ],
+)
+def test_normalizeLocation_extrapolate(axes, location, expected):
+ assert normalizeLocation(location, axes, extrapolate=True) == expected
+
+
def test_supportScalar():
assert supportScalar({}, {}) == 1.0
assert supportScalar({"wght": 0.2}, {}) == 1.0
assert supportScalar({"wght": 0.2}, {"wght": (0, 2, 3)}) == 0.1
assert supportScalar({"wght": 2.5}, {"wght": (0, 2, 4)}) == 0.75
- assert supportScalar({"wght": 4}, {"wght": (0, 2, 2)}) == 0.0
- assert supportScalar({"wght": 4}, {"wght": (0, 2, 2)}, extrapolate=True) == 2.0
- assert supportScalar({"wght": 4}, {"wght": (0, 2, 3)}, extrapolate=True) == 2.0
- assert supportScalar({"wght": 2}, {"wght": (0, 0.75, 1)}, extrapolate=True) == -4.0
+ assert supportScalar({"wght": 3}, {"wght": (0, 2, 2)}) == 0.0
+ assert (
+ supportScalar(
+ {"wght": 3},
+ {"wght": (0, 2, 2)},
+ extrapolate=True,
+ axisRanges={"wght": (0, 2)},
+ )
+ == 1.5
+ )
+ assert (
+ supportScalar(
+ {"wght": -1},
+ {"wght": (0, 2, 2)},
+ extrapolate=True,
+ axisRanges={"wght": (0, 2)},
+ )
+ == -0.5
+ )
+ assert (
+ supportScalar(
+ {"wght": 3},
+ {"wght": (0, 1, 2)},
+ extrapolate=True,
+ axisRanges={"wght": (0, 2)},
+ )
+ == -1.0
+ )
+ assert (
+ supportScalar(
+ {"wght": -1},
+ {"wght": (0, 1, 2)},
+ extrapolate=True,
+ axisRanges={"wght": (0, 2)},
+ )
+ == -1.0
+ )
+ assert (
+ supportScalar(
+ {"wght": 2},
+ {"wght": (0, 0.75, 1)},
+ extrapolate=True,
+ axisRanges={"wght": (0, 1)},
+ )
+ == -4.0
+ )
+ with pytest.raises(TypeError):
+ supportScalar(
+ {"wght": 2}, {"wght": (0, 0.75, 1)}, extrapolate=True, axisRanges=None
+ )
+
+
+def test_model_extrapolate():
+ locations = [{}, {"a": 1}, {"b": 1}, {"a": 1, "b": 1}]
+ model = VariationModel(locations, extrapolate=True)
+ masterValues = [100, 200, 300, 400]
+ testLocsAndValues = [
+ ({"a": -1, "b": -1}, -200),
+ ({"a": -1, "b": 0}, 0),
+ ({"a": -1, "b": 1}, 200),
+ ({"a": -1, "b": 2}, 400),
+ ({"a": 0, "b": -1}, -100),
+ ({"a": 0, "b": 0}, 100),
+ ({"a": 0, "b": 1}, 300),
+ ({"a": 0, "b": 2}, 500),
+ ({"a": 1, "b": -1}, 0),
+ ({"a": 1, "b": 0}, 200),
+ ({"a": 1, "b": 1}, 400),
+ ({"a": 1, "b": 2}, 600),
+ ({"a": 2, "b": -1}, 100),
+ ({"a": 2, "b": 0}, 300),
+ ({"a": 2, "b": 1}, 500),
+ ({"a": 2, "b": 2}, 700),
+ ]
+ for loc, expectedValue in testLocsAndValues:
+ assert expectedValue == model.interpolateFromMasters(loc, masterValues)
@pytest.mark.parametrize(
diff --git a/Tests/varLib/mutator_test.py b/Tests/varLib/mutator_test.py
index 03ad870f..a3149c95 100644
--- a/Tests/varLib/mutator_test.py
+++ b/Tests/varLib/mutator_test.py
@@ -37,7 +37,7 @@ class MutatorTest(unittest.TestCase):
return os.path.join(path, "data", "test_results", test_file_or_folder)
@staticmethod
- def get_file_list(folder, suffix, prefix=''):
+ def get_file_list(folder, suffix, prefix=""):
all_files = os.listdir(folder)
file_list = []
for p in all_files:
@@ -48,8 +48,7 @@ class MutatorTest(unittest.TestCase):
def temp_path(self, suffix):
self.temp_dir()
self.num_tempfiles += 1
- return os.path.join(self.tempdir,
- "tmp%d%s" % (self.num_tempfiles, suffix))
+ return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
def temp_dir(self):
if not self.tempdir:
@@ -73,110 +72,111 @@ class MutatorTest(unittest.TestCase):
expected = self.read_ttx(expected_ttx)
if actual != expected:
for line in difflib.unified_diff(
- expected, actual, fromfile=expected_ttx, tofile=path):
+ expected, actual, fromfile=expected_ttx, tofile=path
+ ):
sys.stdout.write(line)
self.fail("TTX output is different from expected")
def compile_font(self, path, suffix, temp_dir):
ttx_filename = os.path.basename(path)
- savepath = os.path.join(temp_dir, ttx_filename.replace('.ttx', suffix))
+ savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.importXML(path)
font.save(savepath, reorderTables=None)
return font, savepath
-# -----
-# Tests
-# -----
+ # -----
+ # Tests
+ # -----
def test_varlib_mutator_ttf(self):
- suffix = '.ttf'
- ds_path = self.get_test_input('Build.designspace')
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ suffix = ".ttf"
+ ds_path = self.get_test_input("Build.designspace")
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
- finder = lambda s: s.replace(ufo_dir, self.tempdir).replace('.ufo', suffix)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
varfont, _, _ = build(ds_path, finder)
- varfont_name = 'Mutator'
+ varfont_name = "Mutator"
varfont_path = os.path.join(self.tempdir, varfont_name + suffix)
varfont.save(varfont_path)
- args = [varfont_path, 'wght=500', 'cntr=50']
+ args = [varfont_path, "wght=500", "cntr=50"]
mutator(args)
- instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix
+ instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix
instfont = TTFont(instfont_path)
- tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head']
- expected_ttx_path = self.get_test_output(varfont_name + '.ttx')
+ tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"]
+ expected_ttx_path = self.get_test_output(varfont_name + ".ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
def test_varlib_mutator_getvar_ttf(self):
- suffix = '.ttf'
- ttx_dir = self.get_test_input('master_ttx_getvar_ttf')
+ suffix = ".ttf"
+ ttx_dir = self.get_test_input("master_ttx_getvar_ttf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'Mutator_Getvar')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "Mutator_Getvar")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
- varfont_name = 'Mutator_Getvar'
+ varfont_name = "Mutator_Getvar"
varfont_path = os.path.join(self.tempdir, varfont_name + suffix)
- args = [varfont_path, 'wdth=80', 'ASCN=628']
+ args = [varfont_path, "wdth=80", "ASCN=628"]
mutator(args)
- instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix
+ instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix
instfont = TTFont(instfont_path)
- tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head']
- expected_ttx_path = self.get_test_output(varfont_name + '-instance.ttx')
+ tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"]
+ expected_ttx_path = self.get_test_output(varfont_name + "-instance.ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
def test_varlib_mutator_iup_ttf(self):
- suffix = '.ttf'
- ufo_dir = self.get_test_input('master_ufo')
- ttx_dir = self.get_test_input('master_ttx_varfont_ttf')
+ suffix = ".ttf"
+ ufo_dir = self.get_test_input("master_ufo")
+ ttx_dir = self.get_test_input("master_ttx_varfont_ttf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'Mutator_IUP')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "Mutator_IUP")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
- varfont_name = 'Mutator_IUP'
+ varfont_name = "Mutator_IUP"
varfont_path = os.path.join(self.tempdir, varfont_name + suffix)
-
- args = [varfont_path, 'wdth=80', 'ASCN=628']
+
+ args = [varfont_path, "wdth=80", "ASCN=628"]
mutator(args)
- instfont_path = os.path.splitext(varfont_path)[0] + '-instance' + suffix
+ instfont_path = os.path.splitext(varfont_path)[0] + "-instance" + suffix
instfont = TTFont(instfont_path)
- tables = [table_tag for table_tag in instfont.keys() if table_tag != 'head']
- expected_ttx_path = self.get_test_output(varfont_name + '-instance.ttx')
+ tables = [table_tag for table_tag in instfont.keys() if table_tag != "head"]
+ expected_ttx_path = self.get_test_output(varfont_name + "-instance.ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
def test_varlib_mutator_CFF2(self):
- suffix = '.otf'
- ttx_dir = self.get_test_input('master_ttx_varfont_otf')
+ suffix = ".otf"
+ ttx_dir = self.get_test_input("master_ttx_varfont_otf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestCFF2VF')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestCFF2VF")
for path in ttx_paths:
self.compile_font(path, suffix, self.tempdir)
- varfont_name = 'TestCFF2VF'
+ varfont_name = "TestCFF2VF"
varfont_path = os.path.join(self.tempdir, varfont_name + suffix)
- expected_ttx_name = 'InterpolateTestCFF2VF'
+ expected_ttx_name = "InterpolateTestCFF2VF"
tables = ["hmtx", "CFF2"]
- loc = {'wght':float(200)}
+ loc = {"wght": float(200)}
varfont = TTFont(varfont_path)
new_font = make_instance(varfont, loc)
- expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
+ expected_ttx_path = self.get_test_output(expected_ttx_name + ".ttx")
self.expect_ttx(new_font, expected_ttx_path, tables)
diff --git a/Tests/varLib/stat_test.py b/Tests/varLib/stat_test.py
index 6def990e..ce04423a 100644
--- a/Tests/varLib/stat_test.py
+++ b/Tests/varLib/stat_test.py
@@ -65,6 +65,18 @@ def test_getStatAxes(datadir):
"rangeMaxValue": 900.0,
"rangeMinValue": 850.0,
},
+ {
+ "flags": 2,
+ "name": {"en": "Regular"},
+ "value": 400.0,
+ "linkedValue": 700.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Bold"},
+ "value": 700.0,
+ "linkedValue": 400.0,
+ },
],
"name": {"en": "Wéíght", "fa-IR": "قطر"},
"ordering": 2,
@@ -120,6 +132,18 @@ def test_getStatAxes(datadir):
"rangeMaxValue": 850.0,
"rangeMinValue": 650.0,
},
+ {
+ "flags": 2,
+ "name": {"en": "Regular"},
+ "value": 400.0,
+ "linkedValue": 700.0,
+ },
+ {
+ "flags": 0,
+ "name": {"en": "Bold"},
+ "value": 700.0,
+ "linkedValue": 400.0,
+ },
],
"name": {"en": "Wéíght", "fa-IR": "قطر"},
"ordering": 2,
diff --git a/Tests/varLib/varLib_test.py b/Tests/varLib/varLib_test.py
index 29f909ae..87616ae2 100644
--- a/Tests/varLib/varLib_test.py
+++ b/Tests/varLib/varLib_test.py
@@ -1,15 +1,21 @@
+from fontTools.colorLib.builder import buildCOLR
from fontTools.ttLib import TTFont, newTable
-from fontTools.varLib import build, load_designspace
+from fontTools.ttLib.tables import otTables as ot
+from fontTools.varLib import build, build_many, load_designspace, _add_COLR
from fontTools.varLib.errors import VarLibValidationError
import fontTools.varLib.errors as varLibErrors
+from fontTools.varLib.models import VariationModel
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,
+ DesignSpaceDocumentError,
+ DesignSpaceDocument,
+ SourceDescriptor,
)
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
import difflib
+from copy import deepcopy
from io import BytesIO
import os
import shutil
@@ -62,7 +68,7 @@ class BuildTest(unittest.TestCase):
return os.path.join(path, "data", "test_results", test_file_or_folder)
@staticmethod
- def get_file_list(folder, suffix, prefix=''):
+ def get_file_list(folder, suffix, prefix=""):
all_files = os.listdir(folder)
file_list = []
for p in all_files:
@@ -73,8 +79,7 @@ class BuildTest(unittest.TestCase):
def temp_path(self, suffix):
self.temp_dir()
self.num_tempfiles += 1
- return os.path.join(self.tempdir,
- "tmp%d%s" % (self.num_tempfiles, suffix))
+ return os.path.join(self.tempdir, "tmp%d%s" % (self.num_tempfiles, suffix))
def temp_dir(self):
if not self.tempdir:
@@ -98,7 +103,8 @@ class BuildTest(unittest.TestCase):
expected = self.read_ttx(expected_ttx)
if actual != expected:
for line in difflib.unified_diff(
- expected, actual, fromfile=expected_ttx, tofile=path):
+ expected, actual, fromfile=expected_ttx, tofile=path
+ ):
sys.stdout.write(line)
self.fail("TTX output is different from expected")
@@ -110,28 +116,34 @@ class BuildTest(unittest.TestCase):
def compile_font(self, path, suffix, temp_dir):
ttx_filename = os.path.basename(path)
- savepath = os.path.join(temp_dir, ttx_filename.replace('.ttx', suffix))
+ savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.importXML(path)
font.save(savepath, reorderTables=None)
return font, savepath
- def _run_varlib_build_test(self, designspace_name, font_name, tables,
- 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')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ def _run_varlib_build_test(
+ self,
+ designspace_name,
+ font_name,
+ tables,
+ 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")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
self.temp_dir()
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', font_name + '-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", font_name + "-")
for path in ttx_paths:
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)
+ finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
varfont, model, _ = build(ds_path, finder)
if save_before_dump:
@@ -140,25 +152,26 @@ class BuildTest(unittest.TestCase):
# dumps we need to save to a temporary stream, and realod the font
varfont = reload_font(varfont)
- expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
+ expected_ttx_path = self.get_test_output(expected_ttx_name + ".ttx")
self.expect_ttx(varfont, expected_ttx_path, tables)
self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
-# -----
-# Tests
-# -----
+
+ # -----
+ # Tests
+ # -----
def test_varlib_build_ttf(self):
"""Designspace file contains <axes> element."""
self._run_varlib_build_test(
- designspace_name='Build',
- font_name='TestFamily',
- tables=['GDEF', 'HVAR', 'MVAR', 'fvar', 'gvar'],
- expected_ttx_name='Build'
+ designspace_name="Build",
+ font_name="TestFamily",
+ tables=["GDEF", "HVAR", "MVAR", "fvar", "gvar"],
+ expected_ttx_name="Build",
)
def test_varlib_build_no_axes_ttf(self):
"""Designspace file does not contain an <axes> element."""
- ds_path = self.get_test_input('InterpolateLayout3.designspace')
+ ds_path = self.get_test_input("InterpolateLayout3.designspace")
with self.assertRaisesRegex(DesignSpaceDocumentError, "No axes defined"):
build(ds_path)
@@ -166,12 +179,12 @@ class BuildTest(unittest.TestCase):
"""Designspace file contains a 'weight' axis with <map> elements
modifying the normalization mapping. An 'avar' table is generated.
"""
- test_name = 'BuildAvarSingleAxis'
+ test_name = "BuildAvarSingleAxis"
self._run_varlib_build_test(
designspace_name=test_name,
- font_name='TestFamily3',
- tables=['avar'],
- expected_ttx_name=test_name
+ font_name="TestFamily3",
+ tables=["avar"],
+ expected_ttx_name=test_name,
)
def test_varlib_avar_with_identity_maps(self):
@@ -186,12 +199,12 @@ class BuildTest(unittest.TestCase):
https://github.com/googlei18n/fontmake/issues/295
https://github.com/fonttools/fonttools/issues/1011
"""
- test_name = 'BuildAvarIdentityMaps'
+ test_name = "BuildAvarIdentityMaps"
self._run_varlib_build_test(
designspace_name=test_name,
- font_name='TestFamily3',
- tables=['avar'],
- expected_ttx_name=test_name
+ font_name="TestFamily3",
+ tables=["avar"],
+ expected_ttx_name=test_name,
)
def test_varlib_avar_empty_axis(self):
@@ -206,12 +219,25 @@ class BuildTest(unittest.TestCase):
https://github.com/googlei18n/fontmake/issues/295
https://github.com/fonttools/fonttools/issues/1011
"""
- test_name = 'BuildAvarEmptyAxis'
+ test_name = "BuildAvarEmptyAxis"
self._run_varlib_build_test(
designspace_name=test_name,
- font_name='TestFamily3',
- tables=['avar'],
- expected_ttx_name=test_name
+ font_name="TestFamily3",
+ tables=["avar"],
+ expected_ttx_name=test_name,
+ )
+
+ def test_varlib_avar2(self):
+ """Designspace file contains a 'weight' axis with <map> elements
+ modifying the normalization mapping as well as <mappings> element
+ modifying it post-normalization. An 'avar' table is generated.
+ """
+ test_name = "BuildAvar2"
+ self._run_varlib_build_test(
+ designspace_name=test_name,
+ font_name="TestFamily3",
+ tables=["avar"],
+ expected_ttx_name=test_name,
)
def test_varlib_build_feature_variations(self):
@@ -274,6 +300,7 @@ class BuildTest(unittest.TestCase):
The multiple languages are done to verify whether multiple existing
'rclt' features are updated correctly.
"""
+
def add_rclt(font, savepath):
features = """
languagesystem DFLT dflt;
@@ -294,6 +321,7 @@ class BuildTest(unittest.TestCase):
"""
addOpenTypeFeaturesFromString(font, features)
font.save(savepath)
+
self._run_varlib_build_test(
designspace_name="FeatureVars",
font_name="TestFamily",
@@ -310,22 +338,22 @@ class BuildTest(unittest.TestCase):
https://github.com/fonttools/fonttools/issues/1381
"""
- test_name = 'BuildGvarCompositeExplicitDelta'
+ test_name = "BuildGvarCompositeExplicitDelta"
self._run_varlib_build_test(
designspace_name=test_name,
- font_name='TestFamily4',
- tables=['gvar'],
- expected_ttx_name=test_name
+ font_name="TestFamily4",
+ tables=["gvar"],
+ expected_ttx_name=test_name,
)
def test_varlib_nonmarking_CFF2(self):
self.temp_dir()
- ds_path = self.get_test_input('TestNonMarkingCFF2.designspace', copy=True)
+ 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")
- for path in self.get_file_list(ttx_dir, '.ttx', 'TestNonMarkingCFF2_'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "TestNonMarkingCFF2_"):
self.compile_font(path, ".otf", self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -344,11 +372,11 @@ class BuildTest(unittest.TestCase):
def test_varlib_build_CFF2(self):
self.temp_dir()
- ds_path = self.get_test_input('TestCFF2.designspace', copy=True)
+ 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_'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "TestCFF2_"):
self.compile_font(path, ".otf", self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -367,11 +395,11 @@ class BuildTest(unittest.TestCase):
def test_varlib_build_CFF2_from_CFF2(self):
self.temp_dir()
- ds_path = self.get_test_input('TestCFF2Input.designspace', copy=True)
+ 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_'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "TestCFF2_"):
self.compile_font(path, ".otf", self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -390,11 +418,11 @@ class BuildTest(unittest.TestCase):
def test_varlib_build_sparse_CFF2(self):
self.temp_dir()
- ds_path = self.get_test_input('TestSparseCFF2VF.designspace', copy=True)
+ 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")
- for path in self.get_file_list(ttx_dir, '.ttx', 'MasterSet_Kanji-'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "MasterSet_Kanji-"):
self.compile_font(path, ".otf", self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -413,11 +441,11 @@ class BuildTest(unittest.TestCase):
def test_varlib_build_vpal(self):
self.temp_dir()
- ds_path = self.get_test_input('test_vpal.designspace', copy=True)
+ 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")
- for path in self.get_file_list(ttx_dir, '.ttx', 'master_vpal_test_'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "master_vpal_test_"):
self.compile_font(path, ".otf", self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -434,20 +462,19 @@ class BuildTest(unittest.TestCase):
self.expect_ttx(varfont, expected_ttx_path, tables)
def test_varlib_main_ttf(self):
- """Mostly for testing varLib.main()
- """
- suffix = '.ttf'
- ds_path = self.get_test_input('Build.designspace')
- ttx_dir = self.get_test_input('master_ttx_interpolatable_ttf')
+ """Mostly for testing varLib.main()"""
+ suffix = ".ttf"
+ ds_path = self.get_test_input("Build.designspace")
+ ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
self.temp_dir()
- ttf_dir = os.path.join(self.tempdir, 'master_ttf_interpolatable')
+ ttf_dir = os.path.join(self.tempdir, "master_ttf_interpolatable")
os.makedirs(ttf_dir)
- ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily-')
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-")
for path in ttx_paths:
self.compile_font(path, suffix, ttf_dir)
- ds_copy = os.path.join(self.tempdir, 'BuildMain.designspace')
+ ds_copy = os.path.join(self.tempdir, "BuildMain.designspace")
shutil.copy2(ds_path, ds_copy)
# by default, varLib.main finds master TTFs inside a
@@ -459,7 +486,7 @@ class BuildTest(unittest.TestCase):
finally:
os.chdir(cwd)
- varfont_path = os.path.splitext(ds_copy)[0] + '-VF' + suffix
+ varfont_path = os.path.splitext(ds_copy)[0] + "-VF" + suffix
self.assertTrue(os.path.exists(varfont_path))
# try again passing an explicit --master-finder
@@ -475,17 +502,110 @@ class BuildTest(unittest.TestCase):
self.assertTrue(os.path.exists(varfont_path))
varfont = TTFont(varfont_path)
- tables = [table_tag for table_tag in varfont.keys() if table_tag != 'head']
- expected_ttx_path = self.get_test_output('BuildMain.ttx')
+ tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
+ expected_ttx_path = self.get_test_output("BuildMain.ttx")
+ self.expect_ttx(varfont, expected_ttx_path, tables)
+
+ def test_varLib_main_output_dir(self):
+ self.temp_dir()
+ outdir = os.path.join(self.tempdir, "output_dir_test")
+ self.assertFalse(os.path.exists(outdir))
+
+ ds_path = os.path.join(self.tempdir, "BuildMain.designspace")
+ shutil.copy2(self.get_test_input("Build.designspace"), ds_path)
+
+ shutil.copytree(
+ self.get_test_input("master_ttx_interpolatable_ttf"),
+ os.path.join(outdir, "master_ttx"),
+ )
+
+ finder = "%s/output_dir_test/master_ttx/{stem}.ttx" % self.tempdir
+
+ varLib_main([ds_path, "--output-dir", outdir, "--master-finder", finder])
+
+ self.assertTrue(os.path.isdir(outdir))
+ self.assertTrue(os.path.exists(os.path.join(outdir, "BuildMain-VF.ttf")))
+
+ def test_varLib_main_filter_variable_fonts(self):
+ self.temp_dir()
+ outdir = os.path.join(self.tempdir, "filter_variable_fonts_test")
+ self.assertFalse(os.path.exists(outdir))
+
+ ds_path = os.path.join(self.tempdir, "BuildMain.designspace")
+ shutil.copy2(self.get_test_input("Build.designspace"), ds_path)
+
+ shutil.copytree(
+ self.get_test_input("master_ttx_interpolatable_ttf"),
+ os.path.join(outdir, "master_ttx"),
+ )
+
+ finder = "%s/filter_variable_fonts_test/master_ttx/{stem}.ttx" % self.tempdir
+
+ cmd = [ds_path, "--output-dir", outdir, "--master-finder", finder]
+
+ with pytest.raises(SystemExit):
+ varLib_main(cmd + ["--variable-fonts", "FooBar"]) # no font matches
+
+ varLib_main(cmd + ["--variable-fonts", "Build.*"]) # this does match
+
+ self.assertTrue(os.path.isdir(outdir))
+ self.assertTrue(os.path.exists(os.path.join(outdir, "BuildMain-VF.ttf")))
+
+ def test_varLib_main_drop_implied_oncurves(self):
+ self.temp_dir()
+ outdir = os.path.join(self.tempdir, "drop_implied_oncurves_test")
+ self.assertFalse(os.path.exists(outdir))
+
+ ttf_dir = os.path.join(outdir, "master_ttf_interpolatable")
+ os.makedirs(ttf_dir)
+ ttx_dir = self.get_test_input("master_ttx_drop_oncurves")
+ ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily-")
+ for path in ttx_paths:
+ self.compile_font(path, ".ttf", ttf_dir)
+
+ ds_copy = os.path.join(outdir, "DropOnCurves.designspace")
+ ds_path = self.get_test_input("DropOnCurves.designspace")
+ shutil.copy2(ds_path, ds_copy)
+
+ finder = "%s/master_ttf_interpolatable/{stem}.ttf" % outdir
+ varLib_main([ds_copy, "--master-finder", finder, "--drop-implied-oncurves"])
+
+ vf_path = os.path.join(outdir, "DropOnCurves-VF.ttf")
+ varfont = TTFont(vf_path)
+ tables = [table_tag for table_tag in varfont.keys() if table_tag != "head"]
+ expected_ttx_path = self.get_test_output("DropOnCurves.ttx")
self.expect_ttx(varfont, expected_ttx_path, tables)
+ def test_varLib_build_many_no_overwrite_STAT(self):
+ # Ensure that varLib.build_many doesn't overwrite a pre-existing STAT table,
+ # e.g. one built by feaLib from features.fea; the VF simply should inherit the
+ # STAT from the base master: https://github.com/googlefonts/fontmake/issues/985
+ base_master = TTFont()
+ base_master.importXML(
+ self.get_test_input("master_no_overwrite_stat/Test-CondensedThin.ttx")
+ )
+ assert "STAT" in base_master
+
+ vf = next(
+ iter(
+ build_many(
+ DesignSpaceDocument.fromfile(
+ self.get_test_input("TestNoOverwriteSTAT.designspace")
+ )
+ ).values()
+ )
+ )
+ assert "STAT" in vf
+
+ assert vf["STAT"].table == base_master["STAT"].table
+
def test_varlib_build_from_ds_object_in_memory_ttfonts(self):
ds_path = self.get_test_input("Build.designspace")
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-'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "TestFamily-"):
self.compile_font(path, ".ttf", self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -510,7 +630,7 @@ class BuildTest(unittest.TestCase):
ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
expected_ttx_path = self.get_test_output("BuildMain.ttx")
- for path in self.get_file_list(ttx_dir, '.ttx', 'TestFamily-'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "TestFamily-"):
self.compile_font(path, ".ttf", self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -656,12 +776,12 @@ class BuildTest(unittest.TestCase):
def test_varlib_build_VVAR_CFF2(self):
self.temp_dir()
- ds_path = self.get_test_input('TestVVAR.designspace', copy=True)
+ 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'
+ expected_ttx_name = "TestVVAR"
+ suffix = ".otf"
- for path in self.get_file_list(ttx_dir, '.ttx', 'TestVVAR'):
+ for path in self.get_file_list(ttx_dir, ".ttx", "TestVVAR"):
font, savepath = self.compile_font(path, suffix, self.tempdir)
ds = DesignSpaceDocument.fromfile(ds_path)
@@ -674,7 +794,7 @@ class BuildTest(unittest.TestCase):
varfont, _, _ = build(ds)
varfont = reload_font(varfont)
- expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
+ expected_ttx_path = self.get_test_output(expected_ttx_name + ".ttx")
tables = ["VVAR"]
self.expect_ttx(varfont, expected_ttx_path, tables)
self.check_ttx_dump(varfont, expected_ttx_path, tables, suffix)
@@ -682,12 +802,12 @@ class BuildTest(unittest.TestCase):
def test_varlib_build_BASE(self):
self.temp_dir()
- ds_path = self.get_test_input('TestBASE.designspace', copy=True)
+ 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'
+ expected_ttx_name = "TestBASE"
+ suffix = ".otf"
- for path in self.get_file_list(ttx_dir, '.ttx', 'TestBASE'):
+ 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)
@@ -700,17 +820,17 @@ class BuildTest(unittest.TestCase):
varfont, _, _ = build(ds)
varfont = reload_font(varfont)
- expected_ttx_path = self.get_test_output(expected_ttx_name + '.ttx')
+ 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',
+ designspace_name="SingleMaster",
+ font_name="TestFamily",
+ tables=["GDEF", "HVAR", "MVAR", "STAT", "fvar", "cvar", "gvar", "name"],
+ expected_ttx_name="SingleMaster",
save_before_dump=True,
)
@@ -718,7 +838,7 @@ class BuildTest(unittest.TestCase):
"""Test the correct merging of class-based pair kerning.
Problem description at https://github.com/fonttools/fonttools/pull/1638.
- Test font and Designspace generated by
+ Test font and Designspace generated by
https://gist.github.com/madig/183d0440c9f7d05f04bd1280b9664bd1.
"""
ds_path = self.get_test_input("KerningMerging.designspace")
@@ -756,7 +876,7 @@ class BuildTest(unittest.TestCase):
assert getattr(class2_zero.Value1, "XAdvDevice", None) is None
# Assert the variable font's kerning table (without deltas) is equal to the
- # default font's kerning table. The bug fixed in
+ # default font's kerning table. The bug fixed in
# https://github.com/fonttools/fonttools/pull/1638 caused rogue kerning
# values to be written to the variable font.
assert _extract_flat_kerning(varfont, class_kerning_table) == {
@@ -816,7 +936,7 @@ class BuildTest(unittest.TestCase):
def test_varlib_build_incompatible_features(self):
with pytest.raises(
varLibErrors.ShouldBeConstant,
- match = """
+ match="""
Couldn't merge the fonts, because some values were different, but should have
been the same. This happened while performing the following operation:
@@ -828,8 +948,8 @@ Expected to see .FeatureCount==2, instead saw 1
Incompatible features between masters.
Expected: kern, mark.
Got: kern.
-"""):
-
+""",
+ ):
self._run_varlib_build_test(
designspace_name="IncompatibleFeatures",
font_name="IncompatibleFeatures",
@@ -840,8 +960,7 @@ Got: kern.
def test_varlib_build_incompatible_lookup_types(self):
with pytest.raises(
- varLibErrors.MismatchedTypes,
- match = r"'MarkBasePos', instead saw 'PairPos'"
+ varLibErrors.MismatchedTypes, match=r"'MarkBasePos', instead saw 'PairPos'"
):
self._run_varlib_build_test(
designspace_name="IncompatibleLookupTypes",
@@ -854,14 +973,14 @@ Got: kern.
def test_varlib_build_incompatible_arrays(self):
with pytest.raises(
varLibErrors.ShouldBeConstant,
- match = """
+ 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"""
+Expected to see .ScriptCount==1, instead saw 0""",
):
self._run_varlib_build_test(
designspace_name="IncompatibleArrays",
@@ -873,13 +992,24 @@ Expected to see .ScriptCount==1, instead saw 0"""
def test_varlib_build_variable_colr(self):
self._run_varlib_build_test(
- designspace_name='TestVariableCOLR',
- font_name='TestVariableCOLR',
+ designspace_name="TestVariableCOLR",
+ font_name="TestVariableCOLR",
tables=["GlyphOrder", "fvar", "glyf", "COLR", "CPAL"],
- expected_ttx_name='TestVariableCOLR-VF',
+ expected_ttx_name="TestVariableCOLR-VF",
save_before_dump=True,
)
+ def test_varlib_build_variable_cff2_with_empty_sparse_glyph(self):
+ # https://github.com/fonttools/fonttools/issues/3233
+ self._run_varlib_build_test(
+ designspace_name="SparseCFF2",
+ font_name="SparseCFF2",
+ tables=["GlyphOrder", "CFF2", "fvar", "hmtx", "HVAR"],
+ expected_ttx_name="SparseCFF2-VF",
+ save_before_dump=True,
+ )
+
+
def test_load_masters_layerName_without_required_font():
ds = DesignSpaceDocument()
s = SourceDescriptor()
@@ -985,5 +1115,38 @@ class SetDefaultWeightWidthSlantTest(object):
assert ttFont["post"].italicAngle == -12.0
+def test_variable_COLR_without_VarIndexMap():
+ # test we don't add a no-op VarIndexMap to variable COLR when not needed
+ # https://github.com/fonttools/fonttools/issues/2800
+
+ font1 = TTFont()
+ font1.setGlyphOrder([".notdef", "A"])
+ font1["COLR"] = buildCOLR({"A": (ot.PaintFormat.PaintSolid, 0, 1.0)})
+ # font2 == font1 except for PaintSolid.Alpha
+ font2 = deepcopy(font1)
+ font2["COLR"].table.BaseGlyphList.BaseGlyphPaintRecord[0].Paint.Alpha = 0.0
+ master_fonts = [font1, font2]
+
+ varfont = deepcopy(font1)
+ axis_order = ["XXXX"]
+ model = VariationModel([{}, {"XXXX": 1.0}], axis_order)
+
+ _add_COLR(varfont, model, master_fonts, axis_order)
+
+ colr = varfont["COLR"].table
+
+ assert len(colr.BaseGlyphList.BaseGlyphPaintRecord) == 1
+ baserec = colr.BaseGlyphList.BaseGlyphPaintRecord[0]
+ assert baserec.Paint.Format == ot.PaintFormat.PaintVarSolid
+ assert baserec.Paint.VarIndexBase == 0
+
+ assert colr.VarStore is not None
+ assert len(colr.VarStore.VarData) == 1
+ assert len(colr.VarStore.VarData[0].Item) == 1
+ assert colr.VarStore.VarData[0].Item[0] == [-16384]
+
+ assert colr.VarIndexMap is None
+
+
if __name__ == "__main__":
sys.exit(unittest.main())
diff --git a/Tests/varLib/varStore_test.py b/Tests/varLib/varStore_test.py
index cad8ac73..7eb9d740 100644
--- a/Tests/varLib/varStore_test.py
+++ b/Tests/varLib/varStore_test.py
@@ -1,4 +1,7 @@
import pytest
+from io import StringIO
+from fontTools.misc.xmlWriter import XMLWriter
+from fontTools.misc.roundTools import noRound
from fontTools.varLib.models import VariationModel
from fontTools.varLib.varStore import OnlineVarStoreBuilder, VarStoreInstancer
from fontTools.ttLib import TTFont, newTable
@@ -13,7 +16,7 @@ from fontTools.ttLib.tables.otTables import VarStore
(
[{}, {"a": 1}],
[
- [10, 10], # Test NO_VARIATION_INDEX
+ [10, 10], # Test NO_VARIATION_INDEX
[100, 2000],
[100, 22000],
],
@@ -80,3 +83,204 @@ def buildAxis(axisTag):
axis = Axis()
axis.axisTag = axisTag
return axis
+
+
+@pytest.mark.parametrize(
+ "numRegions, varData, expectedNumVarData, expectedBytes",
+ [
+ (
+ 5,
+ [
+ [10, 10, 0, 0, 20],
+ {3: 300},
+ ],
+ 1,
+ 126,
+ ),
+ (
+ 5,
+ [
+ [10, 10, 0, 0, 20],
+ [10, 11, 0, 0, 20],
+ [10, 12, 0, 0, 20],
+ [10, 13, 0, 0, 20],
+ {3: 300},
+ ],
+ 1,
+ 175,
+ ),
+ (
+ 5,
+ [
+ [10, 11, 0, 0, 20],
+ [10, 300, 0, 0, 20],
+ [10, 301, 0, 0, 20],
+ [10, 302, 0, 0, 20],
+ [10, 303, 0, 0, 20],
+ [10, 304, 0, 0, 20],
+ ],
+ 1,
+ 180,
+ ),
+ (
+ 5,
+ [
+ [0, 11, 12, 0, 20],
+ [0, 13, 12, 0, 20],
+ [0, 14, 12, 0, 20],
+ [0, 15, 12, 0, 20],
+ [0, 16, 12, 0, 20],
+ [10, 300, 0, 0, 20],
+ [10, 301, 0, 0, 20],
+ [10, 302, 0, 0, 20],
+ [10, 303, 0, 0, 20],
+ [10, 304, 0, 0, 20],
+ ],
+ 1,
+ 200,
+ ),
+ (
+ 5,
+ [
+ [0, 11, 12, 0, 20],
+ [0, 13, 12, 0, 20],
+ [0, 14, 12, 0, 20],
+ [0, 15, 12, 0, 20],
+ [0, 16, 12, 0, 20],
+ [0, 17, 12, 0, 20],
+ [0, 18, 12, 0, 20],
+ [0, 19, 12, 0, 20],
+ [0, 20, 12, 0, 20],
+ [10, 300, 0, 0, 20],
+ [10, 301, 0, 0, 20],
+ [10, 302, 0, 0, 20],
+ [10, 303, 0, 0, 20],
+ [10, 304, 0, 0, 20],
+ ],
+ 2,
+ 218,
+ ),
+ (
+ 3,
+ [
+ [10, 10, 10],
+ ],
+ 0,
+ 12,
+ ),
+ ],
+)
+def test_optimize(numRegions, varData, expectedNumVarData, expectedBytes):
+ locations = [{i: i / 16384.0} for i in range(numRegions)]
+ axisTags = sorted({k for loc in locations for k in loc})
+
+ model = VariationModel(locations)
+ builder = OnlineVarStoreBuilder(axisTags)
+ builder.setModel(model)
+
+ for data in varData:
+ if type(data) is dict:
+ newData = [0] * numRegions
+ for k, v in data.items():
+ newData[k] = v
+ data = newData
+
+ builder.storeMasters(data)
+
+ varStore = builder.finish()
+ varStore.optimize()
+
+ dummyFont = TTFont()
+
+ writer = XMLWriter(StringIO())
+ varStore.toXML(writer, dummyFont)
+ xml = writer.file.getvalue()
+
+ assert len(varStore.VarData) == expectedNumVarData, xml
+
+ writer = OTTableWriter()
+ varStore.compile(writer, dummyFont)
+ data = writer.getAllData()
+
+ assert len(data) == expectedBytes, xml
+
+
+@pytest.mark.parametrize(
+ "quantization, expectedBytes",
+ [
+ (1, 200),
+ (2, 180),
+ (3, 170),
+ (4, 175),
+ (8, 170),
+ (32, 92),
+ (64, 56),
+ ],
+)
+def test_quantize(quantization, expectedBytes):
+ varData = [
+ [0, 11, 12, 0, 20],
+ [0, 13, 12, 0, 20],
+ [0, 14, 12, 0, 20],
+ [0, 15, 12, 0, 20],
+ [0, 16, 12, 0, 20],
+ [10, 300, 0, 0, 20],
+ [10, 301, 0, 0, 20],
+ [10, 302, 0, 0, 20],
+ [10, 303, 0, 0, 20],
+ [10, 304, 0, 0, 20],
+ ]
+
+ numRegions = 5
+ locations = [{i: i / 16384.0} for i in range(numRegions)]
+ axisTags = sorted({k for loc in locations for k in loc})
+
+ model = VariationModel(locations)
+
+ builder = OnlineVarStoreBuilder(axisTags)
+ builder.setModel(model)
+
+ for data in varData:
+ builder.storeMasters(data)
+
+ varStore = builder.finish()
+ varStore.optimize(quantization=quantization)
+
+ dummyFont = TTFont()
+
+ writer = XMLWriter(StringIO())
+ varStore.toXML(writer, dummyFont)
+ xml = writer.file.getvalue()
+
+ writer = OTTableWriter()
+ varStore.compile(writer, dummyFont)
+ data = writer.getAllData()
+
+ assert len(data) == expectedBytes, xml
+
+
+def test_optimize_overflow():
+ numRegions = 1
+ locations = [{"wght": 0}, {"wght": 0.5}]
+ axisTags = ["wght"]
+
+ model = VariationModel(locations)
+ builder = OnlineVarStoreBuilder(axisTags)
+ builder.setModel(model)
+
+ for data in range(0, 0xFFFF * 2):
+ data = [0, data]
+ builder.storeMasters(data, round=noRound)
+
+ varStore = builder.finish()
+ varStore.optimize()
+
+ for s in varStore.VarData:
+ print(len(s.Item))
+
+ # 5 data-sets:
+ # - 0..127: 1-byte dataset
+ # - 128..32767: 2-byte dataset
+ # - 32768..32768+65535-1: 4-byte dataset
+ # - 32768+65535..65535+65535-1: 4-byte dataset
+ assert len(varStore.VarData) == 4
diff --git a/Tests/voltLib/data/Empty.ttf b/Tests/voltLib/data/Empty.ttf
new file mode 100644
index 00000000..4eb6d88a
--- /dev/null
+++ b/Tests/voltLib/data/Empty.ttf
Binary files differ
diff --git a/Tests/voltLib/data/NamdhinggoSIL1006.fea b/Tests/voltLib/data/NamdhinggoSIL1006.fea
new file mode 100644
index 00000000..aa8ab1a5
--- /dev/null
+++ b/Tests/voltLib/data/NamdhinggoSIL1006.fea
@@ -0,0 +1,506 @@
+# Glyph classes
+@Cons = [uni1901 uni1902 uni1903 uni1904 uni1905 uni1906 uni1907 uni1908 uni1909 uni190A uni190B uni190C uni190D uni190E uni190F uni1910 uni1911 uni1912 uni1913 uni1914 uni1915 uni1916 uni1917 uni1918 uni1919 uni191A uni191B uni191C uni1940];
+@ConsRaU = [uni1901192A1922 uni1902192A1922 uni1903192A1922 uni1904192A1922 uni1905192A1922 uni1906192A1922 uni1907192A1922 uni1908192A1922 uni1909192A1922 uni190A192A1922 uni190B192A1922 uni190C192A1922 uni190D192A1922 uni190192AE1922 uni190F192A1922 uni1910192A1922 uni1911192A1922 uni1912192A1922 uni1913192A1922 uni1914192A1922 uni1915192A1922 uni1916192A1922 uni1917192A1922 uni1918192A1922 uni1919192A1922 uni1919192A1922 uni191A192A1922 uni191B192A1922 uni191C192A1922 uni1940192A1922];
+@ConsU = [uni19011922 uni19021922 uni19031922 uni19041922 uni19051922 uni19061922 uni19071922 uni19081922 uni19091922 uni190A1922 uni190B1922 uni190C1922 uni190D1922 uni190E1922 uni190F1922 uni19101922 uni19111922 uni19121922 uni19131922 uni19141922 uni19151922 uni19161922 uni19171922 uni19181922 uni19191922 uni191A1922 uni191B1922 uni191C1922 uni19401922];
+@Ikar = [uni1921 uni1921193A];
+@Vowels = [uni1920 uni1927 uni1928];
+@YaWa = [uni1929 uni192B];
+@AllCons = [@Cons @ConsU @ConsRaU];
+@VowelsKem = [@Vowels uni193A];
+
+# Mark classes
+markClass uni1920 <anchor -500 1050> @Aabove;
+markClass uni1922 <anchor -150 -15> @U;
+markClass uni1927 <anchor -300 1050> @eo;
+markClass uni1928 <anchor -190 1050> @eo;
+markClass uni193A <anchor -260 1250> @K;
+markClass uni193A <anchor -260 1250> @VK;
+
+# Lookups
+lookup EEAIDecomp {
+ sub uni1925 by uni1920 uni1923;
+ sub uni1926 by uni1920 uni1924;
+} EEAIDecomp;
+
+lookup OoAuKComp {
+ sub uni1923 uni193A by uni1923193A;
+ sub uni1924 uni193A by uni1924193A;
+} OoAuKComp;
+
+lookup OoAuKDecomp {
+ # The OoAuDecomp substitution rule replaces the OO and AU vowels with their visually constitutent components A plus EE or AI respectively. This is so that the 'A' portion can be positioned independently over the consonant when a Glide occurs between the consonant and the vowel.
+ sub uni1923193A by uni193A uni1923;
+ sub uni1924193A by uni193A uni1924;
+} OoAuKDecomp;
+
+lookup GlideVowelComp {
+ sub uni1929 uni1920 uni193A by uni19291920193A;
+ sub uni1929 uni1922 uni193A by uni19291922193A;
+ sub uni1929 uni1927 uni193A by uni19291927193A;
+ sub uni1929 uni1928 uni193A by uni19291928193A;
+ sub uni1929 uni193A by uni1929193A;
+ sub uni1929 uni1920 by uni19291920;
+ sub uni1929 uni1922 by uni19291922;
+ sub uni1929 uni1927 by uni19291927;
+ sub uni1929 uni1928 by uni19291928;
+ sub uni192B uni1920 uni193A by uni192B1920193A;
+ sub uni192B uni1922 uni193A by uni192B1922193A;
+ sub uni192B uni1927 uni193A by uni192B1927193A;
+ sub uni192B uni1928 uni193A by uni192B1928193A;
+ sub uni192B uni193A by uni192B193A;
+ sub uni192B uni1920 by uni192B1920;
+ sub uni192B uni1922 by uni192B1922;
+ sub uni192B uni1927 by uni192B1927;
+ sub uni192B uni1928 by uni192B1928;
+} GlideVowelComp;
+
+lookup GlideVowelDecomp {
+ sub uni19291920193A by uni1920 uni193A uni1929;
+ sub uni19291922193A by uni1922 uni193A uni1929;
+ sub uni19291927193A by uni1927 uni193A uni1929;
+ sub uni19291928193A by uni1928 uni193A uni1929;
+ sub uni1929193A by uni193A uni1929;
+ sub uni19291920 by uni1920 uni1929;
+ sub uni19291922 by uni1922 uni1929;
+ sub uni19291927 by uni1927 uni1929;
+ sub uni19291928 by uni1928 uni1929;
+ sub uni192B1920193A by uni1920 uni193A uni192B;
+ sub uni192B1922193A by uni1922 uni193A uni192B;
+ sub uni192B1927193A by uni1927 uni193A uni192B;
+ sub uni192B1928193A by uni1928 uni193A uni192B;
+ sub uni192B193A by uni193A uni192B;
+ sub uni192B1920 by uni1920 uni192B;
+ sub uni192B1922 by uni1922 uni192B;
+ sub uni192B1927 by uni1927 uni192B;
+ sub uni192B1928 by uni1928 uni192B;
+} GlideVowelDecomp;
+
+lookup RaUkar {
+ # The RaUkar substitution rule replaces Consonant, Ra, Ukar with a ligature.
+ sub @Cons uni192A uni1922 by @ConsRaU;
+} RaUkar;
+
+lookup Ukar {
+ # The Ukar substitution rule replaces Consonant + Ukar with a ligature. It also applies to the Vowel-Carrier, which has its own ligature with ukar.
+ sub @Cons uni1922 by @ConsU;
+ sub uni1900 uni1922 by uni19001922;
+} Ukar;
+
+lookup IkarK {
+ # The IkarK substitution rule replaces Ikar + Kemphreng with a ligature. The ligature is then positioned properly on the base consonant via the positioning rule IEO.
+ sub uni1921 uni193A by uni1921193A;
+} IkarK;
+
+lookup GlideIkar_target {
+ pos @YaWa -475;
+} GlideIkar_target;
+
+lookup GlideIkar {
+ pos [@YaWa]' lookup GlideIkar_target @Ikar;
+} GlideIkar;
+
+lookup IkarKWid_target {
+ pos uni1921193A 110;
+} IkarKWid_target;
+
+lookup IkarKWid {
+ # The IkarKWid lookup, applied to the Kern feature, adds 110 units of width to the IkarKemphreng ligature when followed by a consonant with akar on it. This prevents the akar from overprinting the rightmost dot of the kemphreng. (The dot overhangs to the right slightly, which is OK unless the following character has akar on it).
+ pos [uni1921193A]' lookup IkarKWid_target @Cons uni1920;
+} IkarKWid;
+
+lookup Akar {
+ # The Akar positioning rule positions the Akar on all consonants.
+ pos base uni1901
+ <anchor 487 1050> mark @Aabove;
+ pos base uni1902
+ <anchor 622 1050> mark @Aabove;
+ pos base uni1903
+ <anchor 475 1050> mark @Aabove;
+ pos base uni1904
+ <anchor 460 1050> mark @Aabove;
+ pos base uni1905
+ <anchor 590 1050> mark @Aabove;
+ pos base uni1906
+ <anchor 519 1050> mark @Aabove;
+ pos base uni1907
+ <anchor 570 1050> mark @Aabove;
+ pos base uni1908
+ <anchor 564 1050> mark @Aabove;
+ pos base uni1909
+ <anchor 430 1050> mark @Aabove;
+ pos base uni190A
+ <anchor 575 1050> mark @Aabove;
+ pos base uni190B
+ <anchor 450 1050> mark @Aabove;
+ pos base uni190C
+ <anchor 556 1050> mark @Aabove;
+ pos base uni190D
+ <anchor 515 1050> mark @Aabove;
+ pos base uni190E
+ <anchor 510 1050> mark @Aabove;
+ pos base uni190F
+ <anchor 497 1050> mark @Aabove;
+ pos base uni1910
+ <anchor 657 1050> mark @Aabove;
+ pos base uni1911
+ <anchor 690 1050> mark @Aabove;
+ pos base uni1912
+ <anchor 538 1050> mark @Aabove;
+ pos base uni1913
+ <anchor 571 1050> mark @Aabove;
+ pos base uni1914
+ <anchor 538 1050> mark @Aabove;
+ pos base uni1915
+ <anchor 470 1050> mark @Aabove;
+ pos base uni1916
+ <anchor 503 1050> mark @Aabove;
+ pos base uni1917
+ <anchor 548 1050> mark @Aabove;
+ pos base uni1918
+ <anchor 511 1050> mark @Aabove;
+ pos base uni1919
+ <anchor 560 1050> mark @Aabove;
+ pos base uni191A
+ <anchor 420 1050> mark @Aabove;
+ pos base uni191B
+ <anchor 580 1050> mark @Aabove;
+ pos base uni191C
+ <anchor 540 1050> mark @Aabove;
+ pos base uni1940
+ <anchor 480 1050> mark @Aabove;
+} Akar;
+
+lookup Kemphreng {
+ # The Kemphreng positioning rule positions the Kemphreng on all consonants, including the vowel carrier.
+ pos base uni1901
+ <anchor 500 1050> mark @K;
+ pos base uni1902
+ <anchor 680 1050> mark @K;
+ pos base uni1903
+ <anchor 540 1050> mark @K;
+ pos base uni1904
+ <anchor 500 1050> mark @K;
+ pos base uni1905
+ <anchor 590 1050> mark @K;
+ pos base uni1906
+ <anchor 540 1050> mark @K;
+ pos base uni1907
+ <anchor 620 1050> mark @K;
+ pos base uni1908
+ <anchor 580 1050> mark @K;
+ pos base uni1909
+ <anchor 450 1050> mark @K;
+ pos base uni190A
+ <anchor 580 1050> mark @K;
+ pos base uni190B
+ <anchor 450 1050> mark @K;
+ pos base uni190C
+ <anchor 656 1050> mark @K;
+ pos base uni190D
+ <anchor 570 1050> mark @K;
+ pos base uni190E
+ <anchor 530 1050> mark @K;
+ pos base uni190F
+ <anchor 515 1050> mark @K;
+ pos base uni1910
+ <anchor 680 1050> mark @K;
+ pos base uni1911
+ <anchor 720 1050> mark @K;
+ pos base uni1912
+ <anchor 580 1050> mark @K;
+ pos base uni1913
+ <anchor 600 1050> mark @K;
+ pos base uni1914
+ <anchor 560 1050> mark @K;
+ pos base uni1915
+ <anchor 480 1050> mark @K;
+ pos base uni1916
+ <anchor 520 1050> mark @K;
+ pos base uni1917
+ <anchor 585 1050> mark @K;
+ pos base uni1918
+ <anchor 610 1050> mark @K;
+ pos base uni1919
+ <anchor 520 1050> mark @K;
+ pos base uni191A
+ <anchor 440 1050> mark @K;
+ pos base uni191B
+ <anchor 600 1050> mark @K;
+ pos base uni191C
+ <anchor 600 1050> mark @K;
+ pos base uni1940
+ <anchor 490 1050> mark @K;
+ pos base uni19011922
+ <anchor 500 1050> mark @K;
+ pos base uni19021922
+ <anchor 680 1050> mark @K;
+ pos base uni19031922
+ <anchor 540 1050> mark @K;
+ pos base uni19041922
+ <anchor 500 1050> mark @K;
+ pos base uni19051922
+ <anchor 590 1050> mark @K;
+ pos base uni19061922
+ <anchor 540 1050> mark @K;
+ pos base uni19071922
+ <anchor 620 1050> mark @K;
+ pos base uni19081922
+ <anchor 580 1050> mark @K;
+ pos base uni19091922
+ <anchor 450 1050> mark @K;
+ pos base uni190A1922
+ <anchor 580 1050> mark @K;
+ pos base uni190B1922
+ <anchor 450 1050> mark @K;
+ pos base uni190C1922
+ <anchor 656 1050> mark @K;
+ pos base uni190D1922
+ <anchor 570 1050> mark @K;
+ pos base uni190E1922
+ <anchor 530 1050> mark @K;
+ pos base uni190F1922
+ <anchor 515 1050> mark @K;
+ pos base uni19101922
+ <anchor 680 1050> mark @K;
+ pos base uni19111922
+ <anchor 720 1050> mark @K;
+ pos base uni19121922
+ <anchor 580 1050> mark @K;
+ pos base uni19131922
+ <anchor 600 1050> mark @K;
+ pos base uni19141922
+ <anchor 560 1050> mark @K;
+ pos base uni19151922
+ <anchor 480 1050> mark @K;
+ pos base uni19161922
+ <anchor 520 1050> mark @K;
+ pos base uni19171922
+ <anchor 585 1050> mark @K;
+ pos base uni19181922
+ <anchor 610 1050> mark @K;
+ pos base uni19191922
+ <anchor 520 1050> mark @K;
+ pos base uni191A1922
+ <anchor 440 1050> mark @K;
+ pos base uni191B1922
+ <anchor 600 1050> mark @K;
+ pos base uni191C1922
+ <anchor 600 1050> mark @K;
+ pos base uni19401922
+ <anchor 490 1050> mark @K;
+ pos base uni1901192A1922
+ <anchor 500 1050> mark @K;
+ pos base uni1902192A1922
+ <anchor 680 1050> mark @K;
+ pos base uni1903192A1922
+ <anchor 540 1050> mark @K;
+ pos base uni1904192A1922
+ <anchor 500 1050> mark @K;
+ pos base uni1905192A1922
+ <anchor 590 1050> mark @K;
+ pos base uni1906192A1922
+ <anchor 540 1050> mark @K;
+ pos base uni1907192A1922
+ <anchor 620 1050> mark @K;
+ pos base uni1908192A1922
+ <anchor 580 1050> mark @K;
+ pos base uni1909192A1922
+ <anchor 450 1050> mark @K;
+ pos base uni190A192A1922
+ <anchor 580 1050> mark @K;
+ pos base uni190B192A1922
+ <anchor 450 1050> mark @K;
+ pos base uni190C192A1922
+ <anchor 656 1050> mark @K;
+ pos base uni190D192A1922
+ <anchor 570 1050> mark @K;
+ pos base uni190192AE1922
+ <anchor 530 1050> mark @K;
+ pos base uni190F192A1922
+ <anchor 515 1050> mark @K;
+ pos base uni1910192A1922
+ <anchor 680 1050> mark @K;
+ pos base uni1911192A1922
+ <anchor 720 1050> mark @K;
+ pos base uni1912192A1922
+ <anchor 580 1050> mark @K;
+ pos base uni1913192A1922
+ <anchor 600 1050> mark @K;
+ pos base uni1914192A1922
+ <anchor 560 1050> mark @K;
+ pos base uni1915192A1922
+ <anchor 480 1050> mark @K;
+ pos base uni1916192A1922
+ <anchor 520 1050> mark @K;
+ pos base uni1917192A1922
+ <anchor 585 1050> mark @K;
+ pos base uni1918192A1922
+ <anchor 610 1050> mark @K;
+ pos base uni1919192A1922
+ <anchor 520 1050> mark @K;
+ pos base uni191A192A1922
+ <anchor 440 1050> mark @K;
+ pos base uni191B192A1922
+ <anchor 600 1050> mark @K;
+ pos base uni191C192A1922
+ <anchor 600 1050> mark @K;
+ pos base uni1940192A1922
+ <anchor 490 1050> mark @K;
+ pos base uni1900
+ <anchor 525 1050> mark @K;
+} Kemphreng;
+
+lookup EO {
+ # The IEO positioning rule positions ikar (including the ligature with kemphreng), e and o on all consonants plus the vowel carrier.
+ pos base uni1901
+ <anchor 755 1050> mark @eo;
+ pos base uni1902
+ <anchor 943 1050> mark @eo;
+ pos base uni1903
+ <anchor 790 1050> mark @eo;
+ pos base uni1904
+ <anchor 780 1050> mark @eo;
+ pos base uni1905
+ <anchor 790 1050> mark @eo;
+ pos base uni1906
+ <anchor 878 1050> mark @eo;
+ pos base uni1907
+ <anchor 825 1050> mark @eo;
+ pos base uni1908
+ <anchor 968 1050> mark @eo;
+ pos base uni1909
+ <anchor 660 1050> mark @eo;
+ pos base uni190A
+ <anchor 569 1050> mark @eo;
+ pos base uni190B
+ <anchor 690 1050> mark @eo;
+ pos base uni190C
+ <anchor 649 1050> mark @eo;
+ pos base uni190D
+ <anchor 682 1050> mark @eo;
+ pos base uni190E
+ <anchor 680 1050> mark @eo;
+ pos base uni190F
+ <anchor 778 1050> mark @eo;
+ pos base uni1910
+ <anchor 920 1050> mark @eo;
+ pos base uni1911
+ <anchor 894 1050> mark @eo;
+ pos base uni1912
+ <anchor 782 1050> mark @eo;
+ pos base uni1913
+ <anchor 982 1050> mark @eo;
+ pos base uni1914
+ <anchor 917 1050> mark @eo;
+ pos base uni1915
+ <anchor 730 1050> mark @eo;
+ pos base uni1916
+ <anchor 767 1050> mark @eo;
+ pos base uni1917
+ <anchor 937 1050> mark @eo;
+ pos base uni1918
+ <anchor 862 1050> mark @eo;
+ pos base uni1919
+ <anchor 670 1050> mark @eo;
+ pos base uni191A
+ <anchor 682 1050> mark @eo;
+ pos base uni191B
+ <anchor 921 1050> mark @eo;
+ pos base uni191C
+ <anchor 870 1050> mark @eo;
+ pos base uni1940
+ <anchor 650 1050> mark @eo;
+ pos base uni1900
+ <anchor 810 1050> mark @eo;
+} EO;
+
+lookup VKem {
+ lookupflag MarkAttachmentType @VowelsKem;
+ # The VKem positioning rule positions the kemphreng on all upper vowels (except ikar, which has its own ligature). The vowel itself is positioned on the consonant with the Akar or IEO positioning rule.
+ pos mark uni1920
+ <anchor -260 1250> mark @VK;
+ pos mark uni1927
+ <anchor -300 1250> mark @VK;
+ pos mark uni1928
+ <anchor -150 1455> mark @VK;
+} VKem;
+
+lookup GlideU {
+ # The GlideU positioning rule positions the ukar on the glides Ya and Wa. (There is already a ligature for each consonant with the Ra+Ukar combination).
+ pos base uni1929
+ <anchor -135 -40> mark @U;
+ pos base uni192B
+ <anchor -135 -40> mark @U;
+} GlideU;
+
+# Features
+feature ccmp {
+ script latn;
+ language dflt;
+ lookup EEAIDecomp;
+ lookup OoAuKComp;
+ lookup OoAuKDecomp;
+ lookup GlideVowelComp;
+ lookup GlideVowelDecomp;
+ script limb;
+ language dflt;
+ lookup EEAIDecomp;
+ lookup OoAuKComp;
+ lookup OoAuKDecomp;
+ lookup GlideVowelComp;
+ lookup GlideVowelDecomp;
+} ccmp;
+
+feature kern {
+ script latn;
+ language dflt;
+ lookup GlideIkar;
+ lookup IkarKWid;
+ script limb;
+ language dflt;
+ lookup GlideIkar;
+ lookup IkarKWid;
+} kern;
+
+feature mark {
+ script latn;
+ language dflt;
+ lookup Akar;
+ lookup Kemphreng;
+ lookup EO;
+ script limb;
+ language dflt;
+ lookup Akar;
+ lookup Kemphreng;
+ lookup EO;
+} mark;
+
+feature mkmk {
+ script latn;
+ language dflt;
+ lookup VKem;
+ lookup GlideU;
+ script limb;
+ language dflt;
+ lookup VKem;
+ lookup GlideU;
+} mkmk;
+
+feature liga {
+ script latn;
+ language dflt;
+ lookup RaUkar;
+ lookup Ukar;
+ lookup IkarK;
+ script limb;
+ language dflt;
+ lookup RaUkar;
+ lookup Ukar;
+ lookup IkarK;
+} liga;
+
+@GDEF_base = [glyph0 .null CR space exclam quotedbl numbersign dollar percent quotesingle parenleft parenright asterisk plus comma hyphen period slash zero one two three four five six seven eight nine colon semicolon less equal greater question at 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 bracketleft backslash bracketright asciicircum underscore grave 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 braceleft bar braceright asciitilde uni0965 uni1900 uni19001922 uni1901 uni19011922 uni1901192A1922 uni1902 uni19021922 uni1902192A1922 uni1903 uni19031922 uni1903192A1922 uni1904 uni19041922 uni1904192A1922 uni1905 uni19051922 uni1905192A1922 uni1906 uni19061922 uni1906192A1922 uni1907 uni19071922 uni1907192A1922 uni1908 uni19081922 uni1908192A1922 uni1909 uni19091922 uni1909192A1922 uni190A uni190A1922 uni190A192A1922 uni190B uni190B1922 uni190B192A1922 uni190C uni190C1922 uni190C192A1922 uni190D uni190D1922 uni190D192A1922 uni190E uni190E1922 uni190192AE1922 uni190F uni190F1922 uni190F192A1922 uni1910 uni19101922 uni1910192A1922 uni1911 uni19111922 uni1911192A1922 uni1912 uni19121922 uni1912192A1922 uni1913 uni19131922 uni1913192A1922 uni1914 uni19141922 uni1914192A1922 uni1915 uni19151922 uni1915192A1922 uni1916 uni19161922 uni1916192A1922 uni1917 uni19171922 uni1917192A1922 uni1918 uni19181922 uni1918192A1922 uni1919 uni19191922 uni1919192A1922 uni191A uni191A1922 uni191A192A1922 uni191B uni191B1922 uni191B192A1922 uni191C uni191C1922 uni191C192A1922 uni1921 uni1923 uni1924 uni1929 uni192B uni1930 uni1931 uni1932 uni1933 uni1934 uni1935 uni1936 uni1937 uni1938 uni1939 uni1940 uni19401922 uni1940192A1922 uni1944 uni1945 uni1946 uni1947 uni1948 uni1949 uni194A uni194B uni194C uni194D uni194E uni194F quoteleft quoteright quotedblleft quotedblright uni1921193A ampersand uni2009 endash emdash uni202F uni1923193A uni1924193A uni19291920 uni19291922 uni19291927 uni19291928 uni1929193A uni19291920193A uni19291922193A uni19291927193A uni19291928193A uni192B1920 uni192B1922 uni192B1927 uni192B1928 uni192B193A uni192B1920193A uni192B1922193A uni192B1927193A uni192B1928193A uni25CC uni191E uni191E1922 uni191E192A1922 uni191D uni191D1922 uni191D192A1922];
+@GDEF_mark = [uni1920 uni1920.widC uni1920.widD uni1922 uni1922.altA uni1922.altB uni1922.altC uni1925 uni1926 uni1927 uni1928 uni192A uni193A uni193A.widC uni193B uni193B.widA uni193B.widB uni193B.widC uni192A1922];
+table GDEF {
+ GlyphClassDef @GDEF_base, , @GDEF_mark, ;
+} GDEF;
diff --git a/Tests/voltLib/data/NamdhinggoSIL1006.vtp b/Tests/voltLib/data/NamdhinggoSIL1006.vtp
new file mode 100644
index 00000000..7f2072b0
--- /dev/null
+++ b/Tests/voltLib/data/NamdhinggoSIL1006.vtp
@@ -0,0 +1 @@
+ DEF_GLYPH "glyph0" ID 0 TYPE BASE END_GLYPH DEF_GLYPH ".null" ID 1 TYPE BASE END_GLYPH DEF_GLYPH "CR" ID 2 TYPE BASE END_GLYPH DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH DEF_GLYPH "exclam" ID 4 UNICODE 33 TYPE BASE END_GLYPH DEF_GLYPH "quotedbl" ID 5 UNICODE 34 TYPE BASE END_GLYPH DEF_GLYPH "numbersign" ID 6 UNICODE 35 TYPE BASE END_GLYPH DEF_GLYPH "dollar" ID 7 UNICODE 36 TYPE BASE END_GLYPH DEF_GLYPH "percent" ID 8 UNICODE 37 TYPE BASE END_GLYPH DEF_GLYPH "quotesingle" ID 9 UNICODE 39 TYPE BASE END_GLYPH DEF_GLYPH "parenleft" ID 10 UNICODE 40 TYPE BASE END_GLYPH DEF_GLYPH "parenright" ID 11 UNICODE 41 TYPE BASE END_GLYPH DEF_GLYPH "asterisk" ID 12 UNICODE 42 TYPE BASE END_GLYPH DEF_GLYPH "plus" ID 13 UNICODE 43 TYPE BASE END_GLYPH DEF_GLYPH "comma" ID 14 UNICODE 44 TYPE BASE END_GLYPH DEF_GLYPH "hyphen" ID 15 UNICODE 45 TYPE BASE END_GLYPH DEF_GLYPH "period" ID 16 UNICODE 46 TYPE BASE END_GLYPH DEF_GLYPH "slash" ID 17 UNICODE 47 TYPE BASE END_GLYPH DEF_GLYPH "zero" ID 18 UNICODE 48 TYPE BASE END_GLYPH DEF_GLYPH "one" ID 19 UNICODE 49 TYPE BASE END_GLYPH DEF_GLYPH "two" ID 20 UNICODE 50 TYPE BASE END_GLYPH DEF_GLYPH "three" ID 21 UNICODE 51 TYPE BASE END_GLYPH DEF_GLYPH "four" ID 22 UNICODE 52 TYPE BASE END_GLYPH DEF_GLYPH "five" ID 23 UNICODE 53 TYPE BASE END_GLYPH DEF_GLYPH "six" ID 24 UNICODE 54 TYPE BASE END_GLYPH DEF_GLYPH "seven" ID 25 UNICODE 55 TYPE BASE END_GLYPH DEF_GLYPH "eight" ID 26 UNICODE 56 TYPE BASE END_GLYPH DEF_GLYPH "nine" ID 27 UNICODE 57 TYPE BASE END_GLYPH DEF_GLYPH "colon" ID 28 UNICODE 58 TYPE BASE END_GLYPH DEF_GLYPH "semicolon" ID 29 UNICODE 59 TYPE BASE END_GLYPH DEF_GLYPH "less" ID 30 UNICODE 60 TYPE BASE END_GLYPH DEF_GLYPH "equal" ID 31 UNICODE 61 TYPE BASE END_GLYPH DEF_GLYPH "greater" ID 32 UNICODE 62 TYPE BASE END_GLYPH DEF_GLYPH "question" ID 33 UNICODE 63 TYPE BASE END_GLYPH DEF_GLYPH "at" ID 34 UNICODE 64 TYPE BASE END_GLYPH DEF_GLYPH "A" ID 35 UNICODE 65 TYPE BASE END_GLYPH DEF_GLYPH "B" ID 36 UNICODE 66 TYPE BASE END_GLYPH DEF_GLYPH "C" ID 37 UNICODE 67 TYPE BASE END_GLYPH DEF_GLYPH "D" ID 38 UNICODE 68 TYPE BASE END_GLYPH DEF_GLYPH "E" ID 39 UNICODE 69 TYPE BASE END_GLYPH DEF_GLYPH "F" ID 40 UNICODE 70 TYPE BASE END_GLYPH DEF_GLYPH "G" ID 41 UNICODE 71 TYPE BASE END_GLYPH DEF_GLYPH "H" ID 42 UNICODE 72 TYPE BASE END_GLYPH DEF_GLYPH "I" ID 43 UNICODE 73 TYPE BASE END_GLYPH DEF_GLYPH "J" ID 44 UNICODE 74 TYPE BASE END_GLYPH DEF_GLYPH "K" ID 45 UNICODE 75 TYPE BASE END_GLYPH DEF_GLYPH "L" ID 46 UNICODE 76 TYPE BASE END_GLYPH DEF_GLYPH "M" ID 47 UNICODE 77 TYPE BASE END_GLYPH DEF_GLYPH "N" ID 48 UNICODE 78 TYPE BASE END_GLYPH DEF_GLYPH "O" ID 49 UNICODE 79 TYPE BASE END_GLYPH DEF_GLYPH "P" ID 50 UNICODE 80 TYPE BASE END_GLYPH DEF_GLYPH "Q" ID 51 UNICODE 81 TYPE BASE END_GLYPH DEF_GLYPH "R" ID 52 UNICODE 82 TYPE BASE END_GLYPH DEF_GLYPH "S" ID 53 UNICODE 83 TYPE BASE END_GLYPH DEF_GLYPH "T" ID 54 UNICODE 84 TYPE BASE END_GLYPH DEF_GLYPH "U" ID 55 UNICODE 85 TYPE BASE END_GLYPH DEF_GLYPH "V" ID 56 UNICODE 86 TYPE BASE END_GLYPH DEF_GLYPH "W" ID 57 UNICODE 87 TYPE BASE END_GLYPH DEF_GLYPH "X" ID 58 UNICODE 88 TYPE BASE END_GLYPH DEF_GLYPH "Y" ID 59 UNICODE 89 TYPE BASE END_GLYPH DEF_GLYPH "Z" ID 60 UNICODE 90 TYPE BASE END_GLYPH DEF_GLYPH "bracketleft" ID 61 UNICODE 91 TYPE BASE END_GLYPH DEF_GLYPH "backslash" ID 62 UNICODE 92 TYPE BASE END_GLYPH DEF_GLYPH "bracketright" ID 63 UNICODE 93 TYPE BASE END_GLYPH DEF_GLYPH "asciicircum" ID 64 UNICODE 94 TYPE BASE END_GLYPH DEF_GLYPH "underscore" ID 65 UNICODE 95 TYPE BASE END_GLYPH DEF_GLYPH "grave" ID 66 UNICODE 96 TYPE BASE END_GLYPH DEF_GLYPH "a" ID 67 UNICODE 97 TYPE BASE END_GLYPH DEF_GLYPH "b" ID 68 UNICODE 98 TYPE BASE END_GLYPH DEF_GLYPH "c" ID 69 UNICODE 99 TYPE BASE END_GLYPH DEF_GLYPH "d" ID 70 UNICODE 100 TYPE BASE END_GLYPH DEF_GLYPH "e" ID 71 UNICODE 101 TYPE BASE END_GLYPH DEF_GLYPH "f" ID 72 UNICODE 102 TYPE BASE END_GLYPH DEF_GLYPH "g" ID 73 UNICODE 103 TYPE BASE END_GLYPH DEF_GLYPH "h" ID 74 UNICODE 104 TYPE BASE END_GLYPH DEF_GLYPH "i" ID 75 UNICODE 105 TYPE BASE END_GLYPH DEF_GLYPH "j" ID 76 UNICODE 106 TYPE BASE END_GLYPH DEF_GLYPH "k" ID 77 UNICODE 107 TYPE BASE END_GLYPH DEF_GLYPH "l" ID 78 UNICODE 108 TYPE BASE END_GLYPH DEF_GLYPH "m" ID 79 UNICODE 109 TYPE BASE END_GLYPH DEF_GLYPH "n" ID 80 UNICODE 110 TYPE BASE END_GLYPH DEF_GLYPH "o" ID 81 UNICODE 111 TYPE BASE END_GLYPH DEF_GLYPH "p" ID 82 UNICODE 112 TYPE BASE END_GLYPH DEF_GLYPH "q" ID 83 UNICODE 113 TYPE BASE END_GLYPH DEF_GLYPH "r" ID 84 UNICODE 114 TYPE BASE END_GLYPH DEF_GLYPH "s" ID 85 UNICODE 115 TYPE BASE END_GLYPH DEF_GLYPH "t" ID 86 UNICODE 116 TYPE BASE END_GLYPH DEF_GLYPH "u" ID 87 UNICODE 117 TYPE BASE END_GLYPH DEF_GLYPH "v" ID 88 UNICODE 118 TYPE BASE END_GLYPH DEF_GLYPH "w" ID 89 UNICODE 119 TYPE BASE END_GLYPH DEF_GLYPH "x" ID 90 UNICODE 120 TYPE BASE END_GLYPH DEF_GLYPH "y" ID 91 UNICODE 121 TYPE BASE END_GLYPH DEF_GLYPH "z" ID 92 UNICODE 122 TYPE BASE END_GLYPH DEF_GLYPH "braceleft" ID 93 UNICODE 123 TYPE BASE END_GLYPH DEF_GLYPH "bar" ID 94 UNICODE 124 TYPE BASE END_GLYPH DEF_GLYPH "braceright" ID 95 UNICODE 125 TYPE BASE END_GLYPH DEF_GLYPH "asciitilde" ID 96 UNICODE 126 TYPE BASE END_GLYPH DEF_GLYPH "uni0965" ID 97 UNICODE 2405 TYPE BASE END_GLYPH DEF_GLYPH "uni1900" ID 98 UNICODE 6400 TYPE BASE END_GLYPH DEF_GLYPH "uni19001922" ID 99 TYPE BASE END_GLYPH DEF_GLYPH "uni1901" ID 100 UNICODE 6401 TYPE BASE END_GLYPH DEF_GLYPH "uni19011922" ID 101 TYPE BASE END_GLYPH DEF_GLYPH "uni1901192A1922" ID 102 TYPE BASE END_GLYPH DEF_GLYPH "uni1902" ID 103 UNICODE 6402 TYPE BASE END_GLYPH DEF_GLYPH "uni19021922" ID 104 TYPE BASE END_GLYPH DEF_GLYPH "uni1902192A1922" ID 105 TYPE BASE END_GLYPH DEF_GLYPH "uni1903" ID 106 UNICODE 6403 TYPE BASE END_GLYPH DEF_GLYPH "uni19031922" ID 107 TYPE BASE END_GLYPH DEF_GLYPH "uni1903192A1922" ID 108 TYPE BASE END_GLYPH DEF_GLYPH "uni1904" ID 109 UNICODE 6404 TYPE BASE END_GLYPH DEF_GLYPH "uni19041922" ID 110 TYPE BASE END_GLYPH DEF_GLYPH "uni1904192A1922" ID 111 TYPE BASE END_GLYPH DEF_GLYPH "uni1905" ID 112 UNICODE 6405 TYPE BASE END_GLYPH DEF_GLYPH "uni19051922" ID 113 TYPE BASE END_GLYPH DEF_GLYPH "uni1905192A1922" ID 114 TYPE BASE END_GLYPH DEF_GLYPH "uni1906" ID 115 UNICODE 6406 TYPE BASE END_GLYPH DEF_GLYPH "uni19061922" ID 116 TYPE BASE END_GLYPH DEF_GLYPH "uni1906192A1922" ID 117 TYPE BASE END_GLYPH DEF_GLYPH "uni1907" ID 118 UNICODE 6407 TYPE BASE END_GLYPH DEF_GLYPH "uni19071922" ID 119 TYPE BASE END_GLYPH DEF_GLYPH "uni1907192A1922" ID 120 TYPE BASE END_GLYPH DEF_GLYPH "uni1908" ID 121 UNICODE 6408 TYPE BASE END_GLYPH DEF_GLYPH "uni19081922" ID 122 TYPE BASE END_GLYPH DEF_GLYPH "uni1908192A1922" ID 123 TYPE BASE END_GLYPH DEF_GLYPH "uni1909" ID 124 UNICODE 6409 TYPE BASE END_GLYPH DEF_GLYPH "uni19091922" ID 125 TYPE BASE END_GLYPH DEF_GLYPH "uni1909192A1922" ID 126 TYPE BASE END_GLYPH DEF_GLYPH "uni190A" ID 127 UNICODE 6410 TYPE BASE END_GLYPH DEF_GLYPH "uni190A1922" ID 128 TYPE BASE END_GLYPH DEF_GLYPH "uni190A192A1922" ID 129 TYPE BASE END_GLYPH DEF_GLYPH "uni190B" ID 130 UNICODE 6411 TYPE BASE END_GLYPH DEF_GLYPH "uni190B1922" ID 131 TYPE BASE END_GLYPH DEF_GLYPH "uni190B192A1922" ID 132 TYPE BASE END_GLYPH DEF_GLYPH "uni190C" ID 133 UNICODE 6412 TYPE BASE END_GLYPH DEF_GLYPH "uni190C1922" ID 134 TYPE BASE END_GLYPH DEF_GLYPH "uni190C192A1922" ID 135 TYPE BASE END_GLYPH DEF_GLYPH "uni190D" ID 136 UNICODE 6413 TYPE BASE END_GLYPH DEF_GLYPH "uni190D1922" ID 137 TYPE BASE END_GLYPH DEF_GLYPH "uni190D192A1922" ID 138 TYPE BASE END_GLYPH DEF_GLYPH "uni190E" ID 139 UNICODE 6414 TYPE BASE END_GLYPH DEF_GLYPH "uni190E1922" ID 140 TYPE BASE END_GLYPH DEF_GLYPH "uni190192AE1922" ID 141 TYPE BASE END_GLYPH DEF_GLYPH "uni190F" ID 142 UNICODE 6415 TYPE BASE END_GLYPH DEF_GLYPH "uni190F1922" ID 143 TYPE BASE END_GLYPH DEF_GLYPH "uni190F192A1922" ID 144 TYPE BASE END_GLYPH DEF_GLYPH "uni1910" ID 145 UNICODE 6416 TYPE BASE END_GLYPH DEF_GLYPH "uni19101922" ID 146 TYPE BASE END_GLYPH DEF_GLYPH "uni1910192A1922" ID 147 TYPE BASE END_GLYPH DEF_GLYPH "uni1911" ID 148 UNICODE 6417 TYPE BASE END_GLYPH DEF_GLYPH "uni19111922" ID 149 TYPE BASE END_GLYPH DEF_GLYPH "uni1911192A1922" ID 150 TYPE BASE END_GLYPH DEF_GLYPH "uni1912" ID 151 UNICODE 6418 TYPE BASE END_GLYPH DEF_GLYPH "uni19121922" ID 152 TYPE BASE END_GLYPH DEF_GLYPH "uni1912192A1922" ID 153 TYPE BASE END_GLYPH DEF_GLYPH "uni1913" ID 154 UNICODE 6419 TYPE BASE END_GLYPH DEF_GLYPH "uni19131922" ID 155 TYPE BASE END_GLYPH DEF_GLYPH "uni1913192A1922" ID 156 TYPE BASE END_GLYPH DEF_GLYPH "uni1914" ID 157 UNICODE 6420 TYPE BASE END_GLYPH DEF_GLYPH "uni19141922" ID 158 TYPE BASE END_GLYPH DEF_GLYPH "uni1914192A1922" ID 159 TYPE BASE END_GLYPH DEF_GLYPH "uni1915" ID 160 UNICODE 6421 TYPE BASE END_GLYPH DEF_GLYPH "uni19151922" ID 161 TYPE BASE END_GLYPH DEF_GLYPH "uni1915192A1922" ID 162 TYPE BASE END_GLYPH DEF_GLYPH "uni1916" ID 163 UNICODE 6422 TYPE BASE END_GLYPH DEF_GLYPH "uni19161922" ID 164 TYPE BASE END_GLYPH DEF_GLYPH "uni1916192A1922" ID 165 TYPE BASE END_GLYPH DEF_GLYPH "uni1917" ID 166 UNICODE 6423 TYPE BASE END_GLYPH DEF_GLYPH "uni19171922" ID 167 TYPE BASE END_GLYPH DEF_GLYPH "uni1917192A1922" ID 168 TYPE BASE END_GLYPH DEF_GLYPH "uni1918" ID 169 UNICODE 6424 TYPE BASE END_GLYPH DEF_GLYPH "uni19181922" ID 170 TYPE BASE END_GLYPH DEF_GLYPH "uni1918192A1922" ID 171 TYPE BASE END_GLYPH DEF_GLYPH "uni1919" ID 172 UNICODE 6425 TYPE BASE END_GLYPH DEF_GLYPH "uni19191922" ID 173 TYPE BASE END_GLYPH DEF_GLYPH "uni1919192A1922" ID 174 TYPE BASE END_GLYPH DEF_GLYPH "uni191A" ID 175 UNICODE 6426 TYPE BASE END_GLYPH DEF_GLYPH "uni191A1922" ID 176 TYPE BASE END_GLYPH DEF_GLYPH "uni191A192A1922" ID 177 TYPE BASE END_GLYPH DEF_GLYPH "uni191B" ID 178 UNICODE 6427 TYPE BASE END_GLYPH DEF_GLYPH "uni191B1922" ID 179 TYPE BASE END_GLYPH DEF_GLYPH "uni191B192A1922" ID 180 TYPE BASE END_GLYPH DEF_GLYPH "uni191C" ID 181 UNICODE 6428 TYPE BASE END_GLYPH DEF_GLYPH "uni191C1922" ID 182 TYPE BASE END_GLYPH DEF_GLYPH "uni191C192A1922" ID 183 TYPE BASE END_GLYPH DEF_GLYPH "uni1920" ID 184 UNICODE 6432 TYPE MARK END_GLYPH DEF_GLYPH "uni1920.widC" ID 185 TYPE MARK END_GLYPH DEF_GLYPH "uni1920.widD" ID 186 TYPE MARK END_GLYPH DEF_GLYPH "uni1921" ID 187 UNICODE 6433 TYPE BASE END_GLYPH DEF_GLYPH "uni1922" ID 188 UNICODE 6434 TYPE MARK END_GLYPH DEF_GLYPH "uni1922.altA" ID 189 TYPE MARK END_GLYPH DEF_GLYPH "uni1922.altB" ID 190 TYPE MARK END_GLYPH DEF_GLYPH "uni1922.altC" ID 191 TYPE MARK END_GLYPH DEF_GLYPH "uni1923" ID 192 UNICODE 6435 TYPE BASE END_GLYPH DEF_GLYPH "uni1924" ID 193 UNICODE 6436 TYPE BASE END_GLYPH DEF_GLYPH "uni1925" ID 194 UNICODE 6437 TYPE MARK END_GLYPH DEF_GLYPH "uni1926" ID 195 UNICODE 6438 TYPE MARK END_GLYPH DEF_GLYPH "uni1927" ID 196 UNICODE 6439 TYPE MARK END_GLYPH DEF_GLYPH "uni1928" ID 197 UNICODE 6440 TYPE MARK END_GLYPH DEF_GLYPH "uni1929" ID 198 UNICODE 6441 TYPE BASE END_GLYPH DEF_GLYPH "uni192A" ID 199 UNICODE 6442 TYPE MARK END_GLYPH DEF_GLYPH "uni192B" ID 200 UNICODE 6443 TYPE BASE END_GLYPH DEF_GLYPH "uni1930" ID 201 UNICODE 6448 TYPE BASE END_GLYPH DEF_GLYPH "uni1931" ID 202 UNICODE 6449 TYPE BASE END_GLYPH DEF_GLYPH "uni1932" ID 203 UNICODE 6450 TYPE BASE END_GLYPH DEF_GLYPH "uni1933" ID 204 UNICODE 6451 TYPE BASE END_GLYPH DEF_GLYPH "uni1934" ID 205 UNICODE 6452 TYPE BASE END_GLYPH DEF_GLYPH "uni1935" ID 206 UNICODE 6453 TYPE BASE END_GLYPH DEF_GLYPH "uni1936" ID 207 UNICODE 6454 TYPE BASE END_GLYPH DEF_GLYPH "uni1937" ID 208 UNICODE 6455 TYPE BASE END_GLYPH DEF_GLYPH "uni1938" ID 209 UNICODE 6456 TYPE BASE END_GLYPH DEF_GLYPH "uni1939" ID 210 UNICODE 6457 TYPE BASE END_GLYPH DEF_GLYPH "uni193A" ID 211 UNICODE 6458 TYPE MARK END_GLYPH DEF_GLYPH "uni193A.widC" ID 212 TYPE MARK END_GLYPH DEF_GLYPH "uni193B" ID 213 UNICODE 6459 TYPE MARK END_GLYPH DEF_GLYPH "uni193B.widA" ID 214 TYPE MARK END_GLYPH DEF_GLYPH "uni193B.widB" ID 215 TYPE MARK END_GLYPH DEF_GLYPH "uni193B.widC" ID 216 TYPE MARK END_GLYPH DEF_GLYPH "uni1940" ID 217 UNICODE 6464 TYPE BASE END_GLYPH DEF_GLYPH "uni19401922" ID 218 TYPE BASE END_GLYPH DEF_GLYPH "uni1940192A1922" ID 219 TYPE BASE END_GLYPH DEF_GLYPH "uni1944" ID 220 UNICODE 6468 TYPE BASE END_GLYPH DEF_GLYPH "uni1945" ID 221 UNICODE 6469 TYPE BASE END_GLYPH DEF_GLYPH "uni1946" ID 222 UNICODE 6470 TYPE BASE END_GLYPH DEF_GLYPH "uni1947" ID 223 UNICODE 6471 TYPE BASE END_GLYPH DEF_GLYPH "uni1948" ID 224 UNICODE 6472 TYPE BASE END_GLYPH DEF_GLYPH "uni1949" ID 225 UNICODE 6473 TYPE BASE END_GLYPH DEF_GLYPH "uni194A" ID 226 UNICODE 6474 TYPE BASE END_GLYPH DEF_GLYPH "uni194B" ID 227 UNICODE 6475 TYPE BASE END_GLYPH DEF_GLYPH "uni194C" ID 228 UNICODE 6476 TYPE BASE END_GLYPH DEF_GLYPH "uni194D" ID 229 UNICODE 6477 TYPE BASE END_GLYPH DEF_GLYPH "uni194E" ID 230 UNICODE 6478 TYPE BASE END_GLYPH DEF_GLYPH "uni194F" ID 231 UNICODE 6479 TYPE BASE END_GLYPH DEF_GLYPH "quoteleft" ID 232 UNICODE 8216 TYPE BASE END_GLYPH DEF_GLYPH "quoteright" ID 233 UNICODE 8217 TYPE BASE END_GLYPH DEF_GLYPH "quotedblleft" ID 234 UNICODE 8220 TYPE BASE END_GLYPH DEF_GLYPH "quotedblright" ID 235 UNICODE 8221 TYPE BASE END_GLYPH DEF_GLYPH "uni1921193A" ID 236 TYPE BASE END_GLYPH DEF_GLYPH "uni192A1922" ID 237 TYPE MARK END_GLYPH DEF_GLYPH "ampersand" ID 238 UNICODE 38 TYPE BASE END_GLYPH DEF_GLYPH "uni2009" ID 239 UNICODE 8201 TYPE BASE END_GLYPH DEF_GLYPH "endash" ID 240 UNICODE 8211 TYPE BASE END_GLYPH DEF_GLYPH "emdash" ID 241 UNICODE 8212 TYPE BASE END_GLYPH DEF_GLYPH "uni202F" ID 242 UNICODE 8239 TYPE BASE END_GLYPH DEF_GLYPH "uni1923193A" ID 243 TYPE BASE END_GLYPH DEF_GLYPH "uni1924193A" ID 244 TYPE BASE END_GLYPH DEF_GLYPH "uni19291920" ID 245 TYPE BASE END_GLYPH DEF_GLYPH "uni19291922" ID 246 TYPE BASE END_GLYPH DEF_GLYPH "uni19291927" ID 247 TYPE BASE END_GLYPH DEF_GLYPH "uni19291928" ID 248 TYPE BASE END_GLYPH DEF_GLYPH "uni1929193A" ID 249 TYPE BASE END_GLYPH DEF_GLYPH "uni19291920193A" ID 250 TYPE BASE END_GLYPH DEF_GLYPH "uni19291922193A" ID 251 TYPE BASE END_GLYPH DEF_GLYPH "uni19291927193A" ID 252 TYPE BASE END_GLYPH DEF_GLYPH "uni19291928193A" ID 253 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1920" ID 254 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1922" ID 255 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1927" ID 256 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1928" ID 257 TYPE BASE END_GLYPH DEF_GLYPH "uni192B193A" ID 258 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1920193A" ID 259 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1922193A" ID 260 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1927193A" ID 261 TYPE BASE END_GLYPH DEF_GLYPH "uni192B1928193A" ID 262 TYPE BASE END_GLYPH DEF_GLYPH "uni25CC" ID 263 UNICODE 9676 TYPE BASE END_GLYPH DEF_GLYPH "uni191E" ID 264 UNICODE 6430 TYPE BASE END_GLYPH DEF_GLYPH "uni191E1922" ID 265 TYPE BASE END_GLYPH DEF_GLYPH "uni191E192A1922" ID 266 TYPE BASE END_GLYPH DEF_GLYPH "uni191D" ID 267 UNICODE 6429 TYPE BASE END_GLYPH DEF_GLYPH "uni191D1922" ID 268 TYPE BASE END_GLYPH DEF_GLYPH "uni191D192A1922" ID 269 TYPE BASE END_GLYPH DEF_SCRIPT NAME "Latin" TAG "latn" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Glyph Composition/Decomposition" TAG "ccmp" LOOKUP "EEAIDecomp" LOOKUP "OoAuKComp" LOOKUP "OoAuKDecomp" LOOKUP "GlideVowelComp" LOOKUP "GlideVowelDecomp" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "GlideIkar" LOOKUP "IkarKWid" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "Akar" LOOKUP "Kemphreng" LOOKUP "EO" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "VKem" LOOKUP "GlideU" END_FEATURE DEF_FEATURE NAME "Standard Ligatures" TAG "liga" LOOKUP "RaUkar" LOOKUP "Ukar" LOOKUP "IkarK" END_FEATURE END_LANGSYS END_SCRIPT DEF_SCRIPT NAME "Limbu" TAG "limb" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Glyph Composition/Decomposition" TAG "ccmp" LOOKUP "EEAIDecomp" LOOKUP "OoAuKComp" LOOKUP "OoAuKDecomp" LOOKUP "GlideVowelComp" LOOKUP "GlideVowelDecomp" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "GlideIkar" LOOKUP "IkarKWid" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "Akar" LOOKUP "Kemphreng" LOOKUP "EO" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "VKem" LOOKUP "GlideU" END_FEATURE DEF_FEATURE NAME "Standard Ligatures" TAG "liga" LOOKUP "RaUkar" LOOKUP "Ukar" LOOKUP "IkarK" END_FEATURE END_LANGSYS END_SCRIPT DEF_GROUP "AllCons" ENUM GROUP "cons" GROUP "ConsU" GROUP "ConsRaU" END_ENUM END_GROUP DEF_GROUP "Cons" ENUM GLYPH "uni1901" GLYPH "uni1902" GLYPH "uni1903" GLYPH "uni1904" GLYPH "uni1905" GLYPH "uni1906" GLYPH "uni1907" GLYPH "uni1908" GLYPH "uni1909" GLYPH "uni190A" GLYPH "uni190B" GLYPH "uni190C" GLYPH "uni190D" GLYPH "uni190E" GLYPH "uni190F" GLYPH "uni1910" GLYPH "uni1911" GLYPH "uni1912" GLYPH "uni1913" GLYPH "uni1914" GLYPH "uni1915" GLYPH "uni1916" GLYPH "uni1917" GLYPH "uni1918" GLYPH "uni1919" GLYPH "uni191A" GLYPH "uni191B" GLYPH "uni191C" GLYPH "uni1940" END_ENUM END_GROUP DEF_GROUP "ConsRaU" ENUM GLYPH "uni1901192A1922" GLYPH "uni1902192A1922" GLYPH "uni1903192A1922" GLYPH "uni1904192A1922" GLYPH "uni1905192A1922" GLYPH "uni1906192A1922" GLYPH "uni1907192A1922" GLYPH "uni1908192A1922" GLYPH "uni1909192A1922" GLYPH "uni190A192A1922" GLYPH "uni190B192A1922" GLYPH "uni190C192A1922" GLYPH "uni190D192A1922" GLYPH "uni190192AE1922" GLYPH "uni190F192A1922" GLYPH "uni1910192A1922" GLYPH "uni1911192A1922" GLYPH "uni1912192A1922" GLYPH "uni1913192A1922" GLYPH "uni1914192A1922" GLYPH "uni1915192A1922" GLYPH "uni1916192A1922" GLYPH "uni1917192A1922" GLYPH "uni1918192A1922" GLYPH "uni1919192A1922" GLYPH "uni1919192A1922" GLYPH "uni191A192A1922" GLYPH "uni191B192A1922" GLYPH "uni191C192A1922" GLYPH "uni1940192A1922" END_ENUM END_GROUP DEF_GROUP "ConsU" ENUM GLYPH "uni19011922" GLYPH "uni19021922" GLYPH "uni19031922" GLYPH "uni19041922" GLYPH "uni19051922" GLYPH "uni19061922" GLYPH "uni19071922" GLYPH "uni19081922" GLYPH "uni19091922" GLYPH "uni190A1922" GLYPH "uni190B1922" GLYPH "uni190C1922" GLYPH "uni190D1922" GLYPH "uni190E1922" GLYPH "uni190F1922" GLYPH "uni19101922" GLYPH "uni19111922" GLYPH "uni19121922" GLYPH "uni19131922" GLYPH "uni19141922" GLYPH "uni19151922" GLYPH "uni19161922" GLYPH "uni19171922" GLYPH "uni19181922" GLYPH "uni19191922" GLYPH "uni191A1922" GLYPH "uni191B1922" GLYPH "uni191C1922" GLYPH "uni19401922" END_ENUM END_GROUP DEF_GROUP "Ikar" ENUM GLYPH "uni1921" GLYPH "uni1921193A" END_ENUM END_GROUP DEF_GROUP "Vowels" ENUM GLYPH "uni1920" GLYPH "uni1927" GLYPH "uni1928" END_ENUM END_GROUP DEF_GROUP "VowelsKem" ENUM GROUP "Vowels" GLYPH "uni193A" END_ENUM END_GROUP DEF_GROUP "YaWa" ENUM GLYPH "uni1929" GLYPH "uni192B" END_ENUM END_GROUP DEF_LOOKUP "EEAIDecomp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1925" WITH GLYPH "uni1920" GLYPH "uni1923" END_SUB SUB GLYPH "uni1926" WITH GLYPH "uni1920" GLYPH "uni1924" END_SUB END_SUBSTITUTION DEF_LOOKUP "OoAuKComp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1923" GLYPH "uni193A" WITH GLYPH "uni1923193A" END_SUB SUB GLYPH "uni1924" GLYPH "uni193A" WITH GLYPH "uni1924193A" END_SUB END_SUBSTITUTION DEF_LOOKUP "OoAuKDecomp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The OoAuDecomp substitution rule replaces the OO and AU vowels with their visually constitutent components A plus EE or AI respectively. This is so that the 'A' portion can be positioned independently over the consonant when a Glide occurs between the consonant and the vowel." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1923193A" WITH GLYPH "uni193A" GLYPH "uni1923" END_SUB SUB GLYPH "uni1924193A" WITH GLYPH "uni193A" GLYPH "uni1924" END_SUB END_SUBSTITUTION DEF_LOOKUP "GlideVowelComp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1929" GLYPH "uni1920" GLYPH "uni193A" WITH GLYPH "uni19291920193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1922" GLYPH "uni193A" WITH GLYPH "uni19291922193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1927" GLYPH "uni193A" WITH GLYPH "uni19291927193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1928" GLYPH "uni193A" WITH GLYPH "uni19291928193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni193A" WITH GLYPH "uni1929193A" END_SUB SUB GLYPH "uni1929" GLYPH "uni1920" WITH GLYPH "uni19291920" END_SUB SUB GLYPH "uni1929" GLYPH "uni1922" WITH GLYPH "uni19291922" END_SUB SUB GLYPH "uni1929" GLYPH "uni1927" WITH GLYPH "uni19291927" END_SUB SUB GLYPH "uni1929" GLYPH "uni1928" WITH GLYPH "uni19291928" END_SUB SUB GLYPH "uni192B" GLYPH "uni1920" GLYPH "uni193A" WITH GLYPH "uni192B1920193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1922" GLYPH "uni193A" WITH GLYPH "uni192B1922193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1927" GLYPH "uni193A" WITH GLYPH "uni192B1927193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1928" GLYPH "uni193A" WITH GLYPH "uni192B1928193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni193A" WITH GLYPH "uni192B193A" END_SUB SUB GLYPH "uni192B" GLYPH "uni1920" WITH GLYPH "uni192B1920" END_SUB SUB GLYPH "uni192B" GLYPH "uni1922" WITH GLYPH "uni192B1922" END_SUB SUB GLYPH "uni192B" GLYPH "uni1927" WITH GLYPH "uni192B1927" END_SUB SUB GLYPH "uni192B" GLYPH "uni1928" WITH GLYPH "uni192B1928" END_SUB END_SUBSTITUTION DEF_LOOKUP "GlideVowelDecomp" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni19291920193A" WITH GLYPH "uni1920" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291922193A" WITH GLYPH "uni1922" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291927193A" WITH GLYPH "uni1927" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291928193A" WITH GLYPH "uni1928" GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni1929193A" WITH GLYPH "uni193A" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291920" WITH GLYPH "uni1920" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291922" WITH GLYPH "uni1922" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291927" WITH GLYPH "uni1927" GLYPH "uni1929" END_SUB SUB GLYPH "uni19291928" WITH GLYPH "uni1928" GLYPH "uni1929" END_SUB SUB GLYPH "uni192B1920193A" WITH GLYPH "uni1920" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1922193A" WITH GLYPH "uni1922" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1927193A" WITH GLYPH "uni1927" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1928193A" WITH GLYPH "uni1928" GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B193A" WITH GLYPH "uni193A" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1920" WITH GLYPH "uni1920" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1922" WITH GLYPH "uni1922" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1927" WITH GLYPH "uni1927" GLYPH "uni192B" END_SUB SUB GLYPH "uni192B1928" WITH GLYPH "uni1928" GLYPH "uni192B" END_SUB END_SUBSTITUTION DEF_LOOKUP "RaUkar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The RaUkar substitution rule replaces Consonant, Ra, Ukar with a ligature." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "Cons" GLYPH "uni192A" GLYPH "uni1922" WITH GROUP "ConsRaU" END_SUB SUB WITH END_SUB END_SUBSTITUTION DEF_LOOKUP "Ukar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The Ukar substitution rule replaces Consonant + Ukar with a ligature. It also applies to the Vowel-Carrier, which has its own ligature with ukar." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "Cons" GLYPH "uni1922" WITH GROUP "ConsU" END_SUB SUB GLYPH "uni1900" GLYPH "uni1922" WITH GLYPH "uni19001922" END_SUB END_SUBSTITUTION DEF_LOOKUP "IkarK" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The IkarK substitution rule replaces Ikar + Kemphreng with a ligature. The ligature is then positioned properly on the base consonant via the positioning rule IEO." IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GLYPH "uni1921" GLYPH "uni193A" WITH GLYPH "uni1921193A" END_SUB END_SUBSTITUTION DEF_LOOKUP "GlideIkar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "Ikar" END_CONTEXT AS_POSITION ADJUST_SINGLE GROUP "YaWa" BY POS ADV -475 END_POS END_ADJUST END_POSITION DEF_LOOKUP "IkarKWid" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The IkarKWid lookup, applied to the Kern feature, adds 110 units of width to the IkarKemphreng ligature when followed by a consonant with akar on it. This prevents the akar from overprinting the rightmost dot of the kemphreng. (The dot overhangs to the right slightly, which is OK unless the following character has akar on it)." IN_CONTEXT RIGHT GROUP "cons" RIGHT GLYPH "uni1920" END_CONTEXT AS_POSITION ADJUST_SINGLE GLYPH "uni1921193A" BY POS ADV 110 END_POS END_ADJUST END_POSITION DEF_LOOKUP "Akar" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The Akar positioning rule positions the Akar on all consonants." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "cons" TO GLYPH "uni1920" AT ANCHOR "Aabove" END_ATTACH END_POSITION DEF_LOOKUP "Kemphreng" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The Kemphreng positioning rule positions the Kemphreng on all consonants, including the vowel carrier." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "AllCons" GLYPH "uni1900" TO GLYPH "uni193A" AT ANCHOR "K" GLYPH "uni193A" AT ANCHOR "K" END_ATTACH END_POSITION DEF_LOOKUP "EO" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The IEO positioning rule positions ikar (including the ligature with kemphreng), e and o on all consonants plus the vowel carrier." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "cons" GLYPH "uni1900" TO GLYPH "uni1927" AT ANCHOR "eo" GLYPH "uni1928" AT ANCHOR "eo" END_ATTACH END_POSITION DEF_LOOKUP "VKem" PROCESS_BASE PROCESS_MARKS "VowelsKem" DIRECTION LTR COMMENTS "The VKem positioning rule positions the kemphreng on all upper vowels (except ikar, which has its own ligature). The vowel itself is positioned on the consonant with the Akar or IEO positioning rule." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "Vowels" TO GLYPH "uni193A" AT ANCHOR "VK" END_ATTACH END_POSITION DEF_LOOKUP "GlideU" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR COMMENTS "The GlideU positioning rule positions the ukar on the glides Ya and Wa. (There is already a ligature for each consonant with the Ra+Ukar combination)." IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "YaWa" TO GLYPH "uni1922" AT ANCHOR "U" END_ATTACH END_POSITION DEF_ANCHOR "MARK_Aabove" ON 184 GLYPH uni1920 COMPONENT 1 LOCKED AT POS DX -500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 100 GLYPH uni1901 COMPONENT 1 AT POS DX 487 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 103 GLYPH uni1902 COMPONENT 1 AT POS DX 622 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 106 GLYPH uni1903 COMPONENT 1 AT POS DX 475 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 109 GLYPH uni1904 COMPONENT 1 AT POS DX 460 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 112 GLYPH uni1905 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 115 GLYPH uni1906 COMPONENT 1 AT POS DX 519 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 118 GLYPH uni1907 COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 121 GLYPH uni1908 COMPONENT 1 AT POS DX 564 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 124 GLYPH uni1909 COMPONENT 1 AT POS DX 430 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 127 GLYPH uni190A COMPONENT 1 AT POS DX 575 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 130 GLYPH uni190B COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 133 GLYPH uni190C COMPONENT 1 AT POS DX 556 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 136 GLYPH uni190D COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 139 GLYPH uni190E COMPONENT 1 AT POS DX 510 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 142 GLYPH uni190F COMPONENT 1 AT POS DX 497 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 145 GLYPH uni1910 COMPONENT 1 AT POS DX 657 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 148 GLYPH uni1911 COMPONENT 1 AT POS DX 690 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 151 GLYPH uni1912 COMPONENT 1 AT POS DX 538 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 154 GLYPH uni1913 COMPONENT 1 AT POS DX 571 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 157 GLYPH uni1914 COMPONENT 1 AT POS DX 538 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 160 GLYPH uni1915 COMPONENT 1 AT POS DX 470 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 163 GLYPH uni1916 COMPONENT 1 AT POS DX 503 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 166 GLYPH uni1917 COMPONENT 1 AT POS DX 548 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 169 GLYPH uni1918 COMPONENT 1 AT POS DX 511 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 172 GLYPH uni1919 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 175 GLYPH uni191A COMPONENT 1 AT POS DX 420 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 178 GLYPH uni191B COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 181 GLYPH uni191C COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "Aabove" ON 217 GLYPH uni1940 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_K" ON 211 GLYPH uni193A COMPONENT 1 LOCKED AT POS DX -260 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "K" ON 100 GLYPH uni1901 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 101 GLYPH uni19011922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 102 GLYPH uni1901192A1922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 103 GLYPH uni1902 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 104 GLYPH uni19021922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 105 GLYPH uni1902192A1922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 106 GLYPH uni1903 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 107 GLYPH uni19031922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 108 GLYPH uni1903192A1922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 109 GLYPH uni1904 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 110 GLYPH uni19041922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 111 GLYPH uni1904192A1922 COMPONENT 1 AT POS DX 500 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 112 GLYPH uni1905 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 113 GLYPH uni19051922 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 114 GLYPH uni1905192A1922 COMPONENT 1 AT POS DX 590 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 115 GLYPH uni1906 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 116 GLYPH uni19061922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 117 GLYPH uni1906192A1922 COMPONENT 1 AT POS DX 540 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 118 GLYPH uni1907 COMPONENT 1 AT POS DX 620 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 119 GLYPH uni19071922 COMPONENT 1 AT POS DX 620 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 120 GLYPH uni1907192A1922 COMPONENT 1 AT POS DX 620 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 121 GLYPH uni1908 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 122 GLYPH uni19081922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 123 GLYPH uni1908192A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 124 GLYPH uni1909 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 125 GLYPH uni19091922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 126 GLYPH uni1909192A1922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 127 GLYPH uni190A COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 128 GLYPH uni190A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 129 GLYPH uni190A192A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 130 GLYPH uni190B COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 131 GLYPH uni190B1922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 132 GLYPH uni190B192A1922 COMPONENT 1 AT POS DX 450 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 133 GLYPH uni190C COMPONENT 1 AT POS DX 656 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 134 GLYPH uni190C1922 COMPONENT 1 AT POS DX 656 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 135 GLYPH uni190C192A1922 COMPONENT 1 AT POS DX 656 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 136 GLYPH uni190D COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 137 GLYPH uni190D1922 COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 138 GLYPH uni190D192A1922 COMPONENT 1 AT POS DX 570 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 139 GLYPH uni190E COMPONENT 1 AT POS DX 530 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 140 GLYPH uni190E1922 COMPONENT 1 AT POS DX 530 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 141 GLYPH uni190192AE1922 COMPONENT 1 AT POS DX 530 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 142 GLYPH uni190F COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 143 GLYPH uni190F1922 COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 144 GLYPH uni190F192A1922 COMPONENT 1 AT POS DX 515 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 145 GLYPH uni1910 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 146 GLYPH uni19101922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 147 GLYPH uni1910192A1922 COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 148 GLYPH uni1911 COMPONENT 1 AT POS DX 720 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 149 GLYPH uni19111922 COMPONENT 1 AT POS DX 720 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 150 GLYPH uni1911192A1922 COMPONENT 1 AT POS DX 720 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 151 GLYPH uni1912 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 152 GLYPH uni19121922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 153 GLYPH uni1912192A1922 COMPONENT 1 AT POS DX 580 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 154 GLYPH uni1913 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 155 GLYPH uni19131922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 156 GLYPH uni1913192A1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 157 GLYPH uni1914 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 158 GLYPH uni19141922 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 159 GLYPH uni1914192A1922 COMPONENT 1 AT POS DX 560 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 160 GLYPH uni1915 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 161 GLYPH uni19151922 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 162 GLYPH uni1915192A1922 COMPONENT 1 AT POS DX 480 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 163 GLYPH uni1916 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 164 GLYPH uni19161922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 165 GLYPH uni1916192A1922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 166 GLYPH uni1917 COMPONENT 1 AT POS DX 585 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 167 GLYPH uni19171922 COMPONENT 1 AT POS DX 585 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 168 GLYPH uni1917192A1922 COMPONENT 1 AT POS DX 585 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 169 GLYPH uni1918 COMPONENT 1 AT POS DX 610 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 170 GLYPH uni19181922 COMPONENT 1 AT POS DX 610 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 171 GLYPH uni1918192A1922 COMPONENT 1 AT POS DX 610 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 172 GLYPH uni1919 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 173 GLYPH uni19191922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 174 GLYPH uni1919192A1922 COMPONENT 1 AT POS DX 520 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 175 GLYPH uni191A COMPONENT 1 AT POS DX 440 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 176 GLYPH uni191A1922 COMPONENT 1 AT POS DX 440 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 177 GLYPH uni191A192A1922 COMPONENT 1 AT POS DX 440 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 178 GLYPH uni191B COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 179 GLYPH uni191B1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 180 GLYPH uni191B192A1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 181 GLYPH uni191C COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 182 GLYPH uni191C1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 183 GLYPH uni191C192A1922 COMPONENT 1 AT POS DX 600 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 217 GLYPH uni1940 COMPONENT 1 AT POS DX 490 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 218 GLYPH uni19401922 COMPONENT 1 AT POS DX 490 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 219 GLYPH uni1940192A1922 COMPONENT 1 AT POS DX 490 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "K" ON 98 GLYPH uni1900 COMPONENT 1 AT POS DX 525 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_eo" ON 196 GLYPH uni1927 COMPONENT 1 AT POS DX -300 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 100 GLYPH uni1901 COMPONENT 1 AT POS DX 755 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 103 GLYPH uni1902 COMPONENT 1 AT POS DX 943 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 106 GLYPH uni1903 COMPONENT 1 AT POS DX 790 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 109 GLYPH uni1904 COMPONENT 1 AT POS DX 780 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 112 GLYPH uni1905 COMPONENT 1 AT POS DX 790 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 115 GLYPH uni1906 COMPONENT 1 AT POS DX 878 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 118 GLYPH uni1907 COMPONENT 1 AT POS DX 825 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 121 GLYPH uni1908 COMPONENT 1 AT POS DX 968 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 124 GLYPH uni1909 COMPONENT 1 AT POS DX 660 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 127 GLYPH uni190A COMPONENT 1 AT POS DX 569 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 130 GLYPH uni190B COMPONENT 1 AT POS DX 690 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 133 GLYPH uni190C COMPONENT 1 AT POS DX 649 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 136 GLYPH uni190D COMPONENT 1 AT POS DX 682 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 139 GLYPH uni190E COMPONENT 1 AT POS DX 680 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 142 GLYPH uni190F COMPONENT 1 AT POS DX 778 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 145 GLYPH uni1910 COMPONENT 1 AT POS DX 920 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 148 GLYPH uni1911 COMPONENT 1 AT POS DX 894 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 151 GLYPH uni1912 COMPONENT 1 AT POS DX 782 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 154 GLYPH uni1913 COMPONENT 1 AT POS DX 982 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 157 GLYPH uni1914 COMPONENT 1 AT POS DX 917 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 160 GLYPH uni1915 COMPONENT 1 AT POS DX 730 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 163 GLYPH uni1916 COMPONENT 1 AT POS DX 767 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 166 GLYPH uni1917 COMPONENT 1 AT POS DX 937 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 169 GLYPH uni1918 COMPONENT 1 AT POS DX 862 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 172 GLYPH uni1919 COMPONENT 1 AT POS DX 670 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 175 GLYPH uni191A COMPONENT 1 AT POS DX 682 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 178 GLYPH uni191B COMPONENT 1 AT POS DX 921 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 181 GLYPH uni191C COMPONENT 1 AT POS DX 870 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 217 GLYPH uni1940 COMPONENT 1 AT POS DX 650 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "eo" ON 98 GLYPH uni1900 COMPONENT 1 AT POS DX 810 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_eo" ON 197 GLYPH uni1928 COMPONENT 1 AT POS DX -190 DY 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_VK" ON 211 GLYPH uni193A COMPONENT 1 LOCKED AT POS DX -260 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "VK" ON 184 GLYPH uni1920 COMPONENT 1 AT POS DX -260 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "VK" ON 196 GLYPH uni1927 COMPONENT 1 AT POS DX -300 DY 1250 END_POS END_ANCHOR DEF_ANCHOR "VK" ON 197 GLYPH uni1928 COMPONENT 1 AT POS DX -150 DY 1455 END_POS END_ANCHOR DEF_ANCHOR "MARK_U" ON 188 GLYPH uni1922 COMPONENT 1 LOCKED AT POS DX -150 DY -15 END_POS END_ANCHOR DEF_ANCHOR "U" ON 198 GLYPH uni1929 COMPONENT 1 AT POS DX -135 DY -40 END_POS END_ANCHOR DEF_ANCHOR "U" ON 200 GLYPH uni192B COMPONENT 1 AT POS DX -135 DY -40 END_POS END_ANCHOR GRID_PPEM 20 PRESENTATION_PPEM 72 PPOSITIONING_PPEM 144 CMAP_FORMAT 0 3 4 CMAP_FORMAT 1 0 6 CMAP_FORMAT 3 1 4 END \ No newline at end of file
diff --git a/Tests/voltLib/data/Nutso.fea b/Tests/voltLib/data/Nutso.fea
new file mode 100644
index 00000000..7a2c44bb
--- /dev/null
+++ b/Tests/voltLib/data/Nutso.fea
@@ -0,0 +1,328 @@
+# Glyph classes
+@dnom = [zero.dnom one.dnom two.dnom three.dnom four.dnom five.dnom six.dnom seven.dnom eight.dnom nine.dnom];
+@numerals = [zero one two three four five six seven eight nine];
+@numr = [zero.numr one.numr two.numr three.numr four.numr five.numr six.numr seven.numr eight.numr nine.numr];
+@slash = [slash fraction];
+
+# Mark classes
+markClass eight.numr <anchor 0 0> @INIT.1.10;
+markClass eight.numr <anchor 0 0> @INIT.2.10;
+markClass eight.numr <anchor 0 0> @INIT.3.10;
+markClass eight.numr <anchor 0 0> @INIT.4.10;
+markClass eight.numr <anchor 0 0> @INIT.5.10;
+markClass eight.numr <anchor 0 0> @INIT.6.10;
+markClass eight.numr <anchor 0 0> @INIT.7.10;
+markClass eight.numr <anchor 0 0> @INIT.8.10;
+markClass eight.numr <anchor 0 0> @INIT.9.10;
+markClass eight.numr <anchor 0 0> @NUMRNUMR;
+markClass five.numr <anchor 0 0> @INIT.1.10;
+markClass five.numr <anchor 0 0> @INIT.2.10;
+markClass five.numr <anchor 0 0> @INIT.3.10;
+markClass five.numr <anchor 0 0> @INIT.4.10;
+markClass five.numr <anchor 0 0> @INIT.5.10;
+markClass five.numr <anchor 0 0> @INIT.6.10;
+markClass five.numr <anchor 0 0> @INIT.7.10;
+markClass five.numr <anchor 0 0> @INIT.8.10;
+markClass five.numr <anchor 0 0> @INIT.9.10;
+markClass five.numr <anchor 0 0> @NUMRNUMR;
+markClass four.numr <anchor 0 0> @INIT.1.10;
+markClass four.numr <anchor 0 0> @INIT.2.10;
+markClass four.numr <anchor 0 0> @INIT.3.10;
+markClass four.numr <anchor 0 0> @INIT.4.10;
+markClass four.numr <anchor 0 0> @INIT.5.10;
+markClass four.numr <anchor 0 0> @INIT.6.10;
+markClass four.numr <anchor 0 0> @INIT.7.10;
+markClass four.numr <anchor 0 0> @INIT.8.10;
+markClass four.numr <anchor 0 0> @INIT.9.10;
+markClass four.numr <anchor 0 0> @NUMRNUMR;
+markClass nine.numr <anchor 0 0> @INIT.1.10;
+markClass nine.numr <anchor 0 0> @INIT.2.10;
+markClass nine.numr <anchor 0 0> @INIT.3.10;
+markClass nine.numr <anchor 0 0> @INIT.4.10;
+markClass nine.numr <anchor 0 0> @INIT.5.10;
+markClass nine.numr <anchor 0 0> @INIT.6.10;
+markClass nine.numr <anchor 0 0> @INIT.7.10;
+markClass nine.numr <anchor 0 0> @INIT.8.10;
+markClass nine.numr <anchor 0 0> @INIT.9.10;
+markClass nine.numr <anchor 0 0> @NUMRNUMR;
+markClass one.numr <anchor 0 0> @INIT.1.10;
+markClass one.numr <anchor 0 0> @INIT.2.10;
+markClass one.numr <anchor 0 0> @INIT.3.10;
+markClass one.numr <anchor 0 0> @INIT.4.10;
+markClass one.numr <anchor 0 0> @INIT.5.10;
+markClass one.numr <anchor 0 0> @INIT.6.10;
+markClass one.numr <anchor 0 0> @INIT.7.10;
+markClass one.numr <anchor 0 0> @INIT.8.10;
+markClass one.numr <anchor 0 0> @INIT.9.10;
+markClass one.numr <anchor 0 0> @NUMRNUMR;
+markClass seven.numr <anchor 0 0> @INIT.1.10;
+markClass seven.numr <anchor 0 0> @INIT.2.10;
+markClass seven.numr <anchor 0 0> @INIT.3.10;
+markClass seven.numr <anchor 0 0> @INIT.4.10;
+markClass seven.numr <anchor 0 0> @INIT.5.10;
+markClass seven.numr <anchor 0 0> @INIT.6.10;
+markClass seven.numr <anchor 0 0> @INIT.7.10;
+markClass seven.numr <anchor 0 0> @INIT.8.10;
+markClass seven.numr <anchor 0 0> @INIT.9.10;
+markClass seven.numr <anchor 0 0> @NUMRNUMR;
+markClass six.numr <anchor 0 0> @INIT.1.10;
+markClass six.numr <anchor 0 0> @INIT.2.10;
+markClass six.numr <anchor 0 0> @INIT.3.10;
+markClass six.numr <anchor 0 0> @INIT.4.10;
+markClass six.numr <anchor 0 0> @INIT.5.10;
+markClass six.numr <anchor 0 0> @INIT.6.10;
+markClass six.numr <anchor 0 0> @INIT.7.10;
+markClass six.numr <anchor 0 0> @INIT.8.10;
+markClass six.numr <anchor 0 0> @INIT.9.10;
+markClass six.numr <anchor 0 0> @NUMRNUMR;
+markClass three.numr <anchor 0 0> @INIT.1.10;
+markClass three.numr <anchor 0 0> @INIT.2.10;
+markClass three.numr <anchor 0 0> @INIT.3.10;
+markClass three.numr <anchor 0 0> @INIT.4.10;
+markClass three.numr <anchor 0 0> @INIT.5.10;
+markClass three.numr <anchor 0 0> @INIT.6.10;
+markClass three.numr <anchor 0 0> @INIT.7.10;
+markClass three.numr <anchor 0 0> @INIT.8.10;
+markClass three.numr <anchor 0 0> @INIT.9.10;
+markClass three.numr <anchor 0 0> @NUMRNUMR;
+markClass two.numr <anchor 0 0> @INIT.1.10;
+markClass two.numr <anchor 0 0> @INIT.2.10;
+markClass two.numr <anchor 0 0> @INIT.3.10;
+markClass two.numr <anchor 0 0> @INIT.4.10;
+markClass two.numr <anchor 0 0> @INIT.5.10;
+markClass two.numr <anchor 0 0> @INIT.6.10;
+markClass two.numr <anchor 0 0> @INIT.7.10;
+markClass two.numr <anchor 0 0> @INIT.8.10;
+markClass two.numr <anchor 0 0> @INIT.9.10;
+markClass two.numr <anchor 0 0> @NUMRNUMR;
+markClass zero.numr <anchor 0 0> @INIT.1.10;
+markClass zero.numr <anchor 0 0> @INIT.2.10;
+markClass zero.numr <anchor 0 0> @INIT.3.10;
+markClass zero.numr <anchor 0 0> @INIT.4.10;
+markClass zero.numr <anchor 0 0> @INIT.5.10;
+markClass zero.numr <anchor 0 0> @INIT.6.10;
+markClass zero.numr <anchor 0 0> @INIT.7.10;
+markClass zero.numr <anchor 0 0> @INIT.8.10;
+markClass zero.numr <anchor 0 0> @INIT.9.10;
+markClass zero.numr <anchor 0 0> @NUMRNUMR;
+
+# Lookups
+lookup frac.numr {
+ sub @numerals by @numr;
+} frac.numr;
+
+lookup frac.dnom {
+ sub [@slash @dnom] @numr' by @dnom;
+} frac.dnom;
+
+lookup frac.noslash {
+ sub @numr slash by @numr;
+ sub @numr fraction by @numr;
+} frac.noslash;
+
+lookup frac.fracinit {
+ ignore sub @numr @numr';
+ sub @numr' by fracinit @numr;
+} frac.fracinit;
+
+lookup kern.numeral_to_fraction {
+ enum pos @numerals fracinit 140;
+ pos @dnom @numerals 140;
+} kern.numeral_to_fraction;
+
+lookup fracmark.init_1.10_target {
+ pos base fracinit
+ <anchor 3150 0> mark @INIT.1.10;
+} fracmark.init_1.10_target;
+
+lookup fracmark.init_2.10_target {
+ pos base fracinit
+ <anchor 2800 0> mark @INIT.2.10;
+} fracmark.init_2.10_target;
+
+lookup fracmark.init_3.10_target {
+ pos base fracinit
+ <anchor 2450 0> mark @INIT.3.10;
+} fracmark.init_3.10_target;
+
+lookup fracmark.init_4.10_target {
+ pos base fracinit
+ <anchor 2100 0> mark @INIT.4.10;
+} fracmark.init_4.10_target;
+
+lookup fracmark.init_5.10_target {
+ pos base fracinit
+ <anchor 1750 0> mark @INIT.5.10;
+} fracmark.init_5.10_target;
+
+lookup fracmark.init_6.10_target {
+ pos base fracinit
+ <anchor 1400 0> mark @INIT.6.10;
+} fracmark.init_6.10_target;
+
+lookup fracmark.init_7.10_target {
+ pos base fracinit
+ <anchor 1050 0> mark @INIT.7.10;
+} fracmark.init_7.10_target;
+
+lookup fracmark.init_8.10_target {
+ pos base fracinit
+ <anchor 700 0> mark @INIT.8.10;
+} fracmark.init_8.10_target;
+
+lookup fracmark.init_9.10_target {
+ pos base fracinit
+ <anchor 350 0> mark @INIT.9.10;
+} fracmark.init_9.10_target;
+
+lookup fracmark.init {
+ # fracmark.init\1.10
+ pos [@numr]' lookup fracmark.init_1.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\2.10
+ pos [@numr]' lookup fracmark.init_2.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_2.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\3.10
+ pos [@numr]' lookup fracmark.init_3.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_3.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_3.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\4.10
+ pos [@numr]' lookup fracmark.init_4.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_4.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_4.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_4.10_target @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\5.10
+ pos [@numr]' lookup fracmark.init_5.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_5.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_5.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_5.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_5.10_target @dnom @dnom @dnom @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\6.10
+ pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_6.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_6.10_target @numr @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_6.10_target @dnom @dnom @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\7.10
+ pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_7.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_7.10_target @numr @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_7.10_target @dnom @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\8.10
+ pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_8.10_target @numr @numr @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_8.10_target @numr @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_8.10_target @dnom @dnom @dnom;
+ subtable;
+ # fracmark.init\9.10
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @numr @dnom @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @numr @dnom @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @numr @dnom @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @numr @dnom @dnom @dnom;
+ pos [@numr]' lookup fracmark.init_9.10_target @dnom @dnom;
+} fracmark.init;
+
+lookup fracmkmk.numrspacing {
+ pos mark zero.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark one.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark two.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark three.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark four.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark five.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark six.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark seven.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark eight.numr
+ <anchor 700 0> mark @NUMRNUMR;
+ pos mark nine.numr
+ <anchor 700 0> mark @NUMRNUMR;
+} fracmkmk.numrspacing;
+
+# Features
+feature afrc {
+ script DFLT;
+ language dflt;
+ lookup frac.numr;
+ lookup frac.dnom;
+ lookup frac.noslash;
+ lookup frac.fracinit;
+ script latn;
+ language dflt;
+ lookup frac.numr;
+ lookup frac.dnom;
+ lookup frac.noslash;
+ lookup frac.fracinit;
+} afrc;
+
+feature frac {
+ script DFLT;
+ language dflt;
+ lookup frac.numr;
+ lookup frac.dnom;
+ lookup frac.noslash;
+ lookup frac.fracinit;
+ script latn;
+ language dflt;
+ lookup frac.numr;
+ lookup frac.dnom;
+ lookup frac.noslash;
+ lookup frac.fracinit;
+} frac;
+
+feature kern {
+ script DFLT;
+ language dflt;
+ lookup kern.numeral_to_fraction;
+ script latn;
+ language dflt;
+ lookup kern.numeral_to_fraction;
+} kern;
+
+feature mark {
+ script DFLT;
+ language dflt;
+ lookup fracmark.init;
+ script latn;
+ language dflt;
+ lookup fracmark.init;
+} mark;
+
+feature mkmk {
+ script DFLT;
+ language dflt;
+ lookup fracmkmk.numrspacing;
+ script latn;
+ language dflt;
+ lookup fracmkmk.numrspacing;
+} mkmk;
+
+@GDEF_base = [glyph0 \NULL CR space zero one two three four five six seven eight nine slash fraction fracinit zero.dnom one.dnom two.dnom three.dnom four.dnom five.dnom six.dnom seven.dnom eight.dnom nine.dnom];
+@GDEF_mark = [zero.numr one.numr two.numr three.numr four.numr five.numr six.numr seven.numr eight.numr nine.numr];
+table GDEF {
+ GlyphClassDef @GDEF_base, , @GDEF_mark, ;
+} GDEF;
diff --git a/Tests/voltLib/data/Nutso.ttf b/Tests/voltLib/data/Nutso.ttf
new file mode 100644
index 00000000..5efec568
--- /dev/null
+++ b/Tests/voltLib/data/Nutso.ttf
Binary files differ
diff --git a/Tests/voltLib/data/Nutso.vtp b/Tests/voltLib/data/Nutso.vtp
new file mode 100644
index 00000000..9572a002
--- /dev/null
+++ b/Tests/voltLib/data/Nutso.vtp
@@ -0,0 +1 @@
+ DEF_GLYPH "glyph0" ID 0 TYPE BASE END_GLYPH DEF_GLYPH "NULL" ID 1 UNICODE 0 TYPE BASE END_GLYPH DEF_GLYPH "CR" ID 2 UNICODE 13 TYPE BASE END_GLYPH DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH DEF_GLYPH "zero" ID 4 UNICODE 48 TYPE BASE END_GLYPH DEF_GLYPH "one" ID 5 UNICODE 49 TYPE BASE END_GLYPH DEF_GLYPH "two" ID 6 UNICODE 50 TYPE BASE END_GLYPH DEF_GLYPH "three" ID 7 UNICODE 51 TYPE BASE END_GLYPH DEF_GLYPH "four" ID 8 UNICODE 52 TYPE BASE END_GLYPH DEF_GLYPH "five" ID 9 UNICODE 53 TYPE BASE END_GLYPH DEF_GLYPH "six" ID 10 UNICODE 54 TYPE BASE END_GLYPH DEF_GLYPH "seven" ID 11 UNICODE 55 TYPE BASE END_GLYPH DEF_GLYPH "eight" ID 12 UNICODE 56 TYPE BASE END_GLYPH DEF_GLYPH "nine" ID 13 UNICODE 57 TYPE BASE END_GLYPH DEF_GLYPH "slash" ID 14 UNICODE 47 TYPE BASE END_GLYPH DEF_GLYPH "fraction" ID 15 UNICODE 8260 TYPE BASE END_GLYPH DEF_GLYPH "fracinit" ID 16 TYPE BASE END_GLYPH DEF_GLYPH "zero.numr" ID 17 TYPE MARK END_GLYPH DEF_GLYPH "one.numr" ID 18 TYPE MARK END_GLYPH DEF_GLYPH "two.numr" ID 19 TYPE MARK END_GLYPH DEF_GLYPH "three.numr" ID 20 TYPE MARK END_GLYPH DEF_GLYPH "four.numr" ID 21 TYPE MARK END_GLYPH DEF_GLYPH "five.numr" ID 22 TYPE MARK END_GLYPH DEF_GLYPH "six.numr" ID 23 TYPE MARK END_GLYPH DEF_GLYPH "seven.numr" ID 24 TYPE MARK END_GLYPH DEF_GLYPH "eight.numr" ID 25 TYPE MARK END_GLYPH DEF_GLYPH "nine.numr" ID 26 TYPE MARK END_GLYPH DEF_GLYPH "zero.dnom" ID 27 TYPE BASE END_GLYPH DEF_GLYPH "one.dnom" ID 28 TYPE BASE END_GLYPH DEF_GLYPH "two.dnom" ID 29 TYPE BASE END_GLYPH DEF_GLYPH "three.dnom" ID 30 TYPE BASE END_GLYPH DEF_GLYPH "four.dnom" ID 31 TYPE BASE END_GLYPH DEF_GLYPH "five.dnom" ID 32 TYPE BASE END_GLYPH DEF_GLYPH "six.dnom" ID 33 TYPE BASE END_GLYPH DEF_GLYPH "seven.dnom" ID 34 TYPE BASE END_GLYPH DEF_GLYPH "eight.dnom" ID 35 TYPE BASE END_GLYPH DEF_GLYPH "nine.dnom" ID 36 TYPE BASE END_GLYPH DEF_SCRIPT NAME "Default" TAG "DFLT" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Alternative Fractions" TAG "afrc" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Fractions" TAG "frac" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "kern.numeral-to-fraction" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "fracmark.init\1.10" LOOKUP "fracmark.init\2.10" LOOKUP "fracmark.init\3.10" LOOKUP "fracmark.init\4.10" LOOKUP "fracmark.init\5.10" LOOKUP "fracmark.init\6.10" LOOKUP "fracmark.init\7.10" LOOKUP "fracmark.init\8.10" LOOKUP "fracmark.init\9.10" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "fracmkmk.numrspacing" END_FEATURE END_LANGSYS END_SCRIPT DEF_SCRIPT NAME "Latin" TAG "latn" DEF_LANGSYS NAME "Default" TAG "dflt" DEF_FEATURE NAME "Alternative Fractions" TAG "afrc" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Fractions" TAG "frac" LOOKUP "frac.numr" LOOKUP "frac.dnom" LOOKUP "frac.noslash" LOOKUP "frac.fracinit" END_FEATURE DEF_FEATURE NAME "Kerning" TAG "kern" LOOKUP "kern.numeral-to-fraction" END_FEATURE DEF_FEATURE NAME "Mark Positioning" TAG "mark" LOOKUP "fracmark.init\1.10" LOOKUP "fracmark.init\2.10" LOOKUP "fracmark.init\3.10" LOOKUP "fracmark.init\4.10" LOOKUP "fracmark.init\5.10" LOOKUP "fracmark.init\6.10" LOOKUP "fracmark.init\7.10" LOOKUP "fracmark.init\8.10" LOOKUP "fracmark.init\9.10" END_FEATURE DEF_FEATURE NAME "Mark to Mark Positioning" TAG "mkmk" LOOKUP "fracmkmk.numrspacing" END_FEATURE END_LANGSYS END_SCRIPT DEF_GROUP "dnom" ENUM GLYPH "zero.dnom" GLYPH "one.dnom" GLYPH "two.dnom" GLYPH "three.dnom" GLYPH "four.dnom" GLYPH "five.dnom" GLYPH "six.dnom" GLYPH "seven.dnom" GLYPH "eight.dnom" GLYPH "nine.dnom" END_ENUM END_GROUP DEF_GROUP "numerals" ENUM GLYPH "zero" GLYPH "one" GLYPH "two" GLYPH "three" GLYPH "four" GLYPH "five" GLYPH "six" GLYPH "seven" GLYPH "eight" GLYPH "nine" END_ENUM END_GROUP DEF_GROUP "numr" ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" GLYPH "three.numr" GLYPH "four.numr" GLYPH "five.numr" GLYPH "six.numr" GLYPH "seven.numr" GLYPH "eight.numr" GLYPH "nine.numr" END_ENUM END_GROUP DEF_GROUP "slash" ENUM GLYPH "slash" GLYPH "fraction" END_ENUM END_GROUP DEF_LOOKUP "frac.numr" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "numerals" WITH GROUP "numr" END_SUB END_SUBSTITUTION DEF_LOOKUP "frac.dnom" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT LEFT ENUM GROUP "slash" GROUP "dnom" END_ENUM END_CONTEXT AS_SUBSTITUTION SUB GROUP "numr" WITH GROUP "dnom" END_SUB END_SUBSTITUTION DEF_LOOKUP "frac.noslash" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_SUBSTITUTION SUB GROUP "numr" GLYPH "slash" WITH GROUP "numr" END_SUB SUB GROUP "numr" GLYPH "fraction" WITH GROUP "numr" END_SUB END_SUBSTITUTION DEF_LOOKUP "frac.fracinit" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR EXCEPT_CONTEXT LEFT GROUP "numr" END_CONTEXT AS_SUBSTITUTION SUB GROUP "numr" WITH GLYPH "fracinit" GROUP "numr" END_SUB END_SUBSTITUTION DEF_LOOKUP "kern.numeral-to-fraction" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_POSITION ADJUST_PAIR FIRST GROUP "numerals" FIRST GROUP "dnom" SECOND GROUP "numerals" SECOND GLYPH "fracinit" 1 2 BY POS ADV 140 END_POS POS END_POS 2 1 BY POS ADV 140 END_POS POS END_POS END_ADJUST END_POSITION DEF_LOOKUP "fracmark.init\1.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.1.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\2.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.2.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\3.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.3.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\4.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.4.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\5.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.5.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\6.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.6.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\7.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.7.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\8.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.8.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmark.init\9.10" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "numr" RIGHT GROUP "dnom" RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT IN_CONTEXT RIGHT GROUP "dnom" RIGHT GROUP "dnom" END_CONTEXT AS_POSITION ATTACH GLYPH "fracinit" TO GROUP "numr" AT ANCHOR "INIT.9.10" END_ATTACH END_POSITION DEF_LOOKUP "fracmkmk.numrspacing" PROCESS_BASE PROCESS_MARKS ALL DIRECTION LTR IN_CONTEXT END_CONTEXT AS_POSITION ATTACH GROUP "numr" TO GROUP "numr" AT ANCHOR "NUMRNUMR" END_ATTACH END_POSITION DEF_ANCHOR "NUMRNUMR" ON 17 GLYPH zero.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_NUMRNUMR" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 18 GLYPH one.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 19 GLYPH two.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 20 GLYPH three.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 21 GLYPH four.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 22 GLYPH five.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 23 GLYPH six.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 24 GLYPH seven.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 25 GLYPH eight.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "NUMRNUMR" ON 26 GLYPH nine.numr COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "INIT.1.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 3150 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.1.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.2.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 2800 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.2.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.3.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 2450 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.3.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.4.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 2100 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.4.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.5.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 1750 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.5.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.6.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 1400 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.6.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.7.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 1050 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.7.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.8.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 700 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.8.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "INIT.9.10" ON 16 GLYPH fracinit COMPONENT 1 AT POS DX 350 END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 17 GLYPH zero.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 18 GLYPH one.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 19 GLYPH two.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 20 GLYPH three.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 21 GLYPH four.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 22 GLYPH five.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 23 GLYPH six.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 24 GLYPH seven.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 25 GLYPH eight.numr COMPONENT 1 AT POS END_POS END_ANCHOR DEF_ANCHOR "MARK_INIT.9.10" ON 26 GLYPH nine.numr COMPONENT 1 AT POS END_POS END_ANCHOR GRID_PPEM 20 PRESENTATION_PPEM 144 PPOSITIONING_PPEM 205 CMAP_FORMAT 0 3 4 CMAP_FORMAT 1 0 6 CMAP_FORMAT 3 1 4 END \ No newline at end of file
diff --git a/Tests/voltLib/lexer_test.py b/Tests/voltLib/lexer_test.py
index 2145d079..a8e849b1 100644
--- a/Tests/voltLib/lexer_test.py
+++ b/Tests/voltLib/lexer_test.py
@@ -16,18 +16,21 @@ class LexerTest(unittest.TestCase):
self.assertEqual(lex("\t"), [])
def test_string(self):
- self.assertEqual(lex('"foo" "bar"'),
- [(Lexer.STRING, "foo"), (Lexer.STRING, "bar")])
+ self.assertEqual(
+ lex('"foo" "bar"'), [(Lexer.STRING, "foo"), (Lexer.STRING, "bar")]
+ )
self.assertRaises(VoltLibError, lambda: lex('"foo\n bar"'))
def test_name(self):
- self.assertEqual(lex('DEF_FOO bar.alt1'),
- [(Lexer.NAME, "DEF_FOO"), (Lexer.NAME, "bar.alt1")])
+ self.assertEqual(
+ lex("DEF_FOO bar.alt1"), [(Lexer.NAME, "DEF_FOO"), (Lexer.NAME, "bar.alt1")]
+ )
def test_number(self):
- self.assertEqual(lex("123 -456"),
- [(Lexer.NUMBER, 123), (Lexer.NUMBER, -456)])
+ self.assertEqual(lex("123 -456"), [(Lexer.NUMBER, 123), (Lexer.NUMBER, -456)])
+
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/voltLib/parser_test.py b/Tests/voltLib/parser_test.py
index 0e0191fc..abc02d3b 100644
--- a/Tests/voltLib/parser_test.py
+++ b/Tests/voltLib/parser_test.py
@@ -24,98 +24,175 @@ class ParserTest(unittest.TestCase):
[def_glyph] = self.parse(
'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH'
).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(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ (".notdef", 0, None, "BASE", None),
+ )
def test_def_glyph_base_with_unicode(self):
[def_glyph] = self.parse(
'DEF_GLYPH "space" ID 3 UNICODE 32 TYPE BASE END_GLYPH'
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("space", 3, [0x0020], "BASE", None))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("space", 3, [0x0020], "BASE", None),
+ )
def test_def_glyph_base_with_unicodevalues(self):
[def_glyph] = self.parse_(
- 'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009" '
- 'TYPE BASE END_GLYPH'
+ 'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009" ' "TYPE BASE END_GLYPH"
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("CR", 2, [0x0009], "BASE", None))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("CR", 2, [0x0009], "BASE", None),
+ )
def test_def_glyph_base_with_mult_unicodevalues(self):
[def_glyph] = self.parse(
- 'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009,U+000D" '
- 'TYPE BASE END_GLYPH'
+ 'DEF_GLYPH "CR" ID 2 UNICODEVALUES "U+0009,U+000D" ' "TYPE BASE END_GLYPH"
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("CR", 2, [0x0009, 0x000D], "BASE", None))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("CR", 2, [0x0009, 0x000D], "BASE", None),
+ )
def test_def_glyph_base_with_empty_unicodevalues(self):
[def_glyph] = self.parse_(
- 'DEF_GLYPH "i.locl" ID 269 UNICODEVALUES "" '
- 'TYPE BASE END_GLYPH'
+ 'DEF_GLYPH "i.locl" ID 269 UNICODEVALUES "" ' "TYPE BASE END_GLYPH"
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("i.locl", 269, None, "BASE", None))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("i.locl", 269, None, "BASE", None),
+ )
def test_def_glyph_base_2_components(self):
[def_glyph] = self.parse(
'DEF_GLYPH "glyphBase" ID 320 TYPE BASE COMPONENTS 2 END_GLYPH'
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("glyphBase", 320, None, "BASE", 2))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("glyphBase", 320, None, "BASE", 2),
+ )
def test_def_glyph_ligature_2_components(self):
[def_glyph] = self.parse(
'DEF_GLYPH "f_f" ID 320 TYPE LIGATURE COMPONENTS 2 END_GLYPH'
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("f_f", 320, None, "LIGATURE", 2))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("f_f", 320, None, "LIGATURE", 2),
+ )
def test_def_glyph_mark(self):
[def_glyph] = self.parse(
'DEF_GLYPH "brevecomb" ID 320 TYPE MARK END_GLYPH'
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("brevecomb", 320, None, "MARK", None))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("brevecomb", 320, None, "MARK", None),
+ )
def test_def_glyph_component(self):
[def_glyph] = self.parse(
'DEF_GLYPH "f.f_f" ID 320 TYPE COMPONENT END_GLYPH'
).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("f.f_f", 320, None, "COMPONENT", None))
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("f.f_f", 320, None, "COMPONENT", None),
+ )
def test_def_glyph_no_type(self):
- [def_glyph] = self.parse(
- 'DEF_GLYPH "glyph20" ID 20 END_GLYPH'
- ).statements
- self.assertEqual((def_glyph.name, def_glyph.id, def_glyph.unicode,
- def_glyph.type, def_glyph.components),
- ("glyph20", 20, None, None, None))
+ [def_glyph] = self.parse('DEF_GLYPH "glyph20" ID 20 END_GLYPH').statements
+ self.assertEqual(
+ (
+ def_glyph.name,
+ def_glyph.id,
+ def_glyph.unicode,
+ def_glyph.type,
+ def_glyph.components,
+ ),
+ ("glyph20", 20, None, None, None),
+ )
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'
).statements
- self.assertEqual((def_glyphs[0].name, def_glyphs[0].id,
- def_glyphs[0].unicode, def_glyphs[0].type,
- def_glyphs[0].components),
- ("A", 3, [0x41], "BASE", None))
- self.assertEqual((def_glyphs[1].name, def_glyphs[1].id,
- def_glyphs[1].unicode, def_glyphs[1].type,
- def_glyphs[1].components),
- ("a", 4, [0x61], "BASE", None))
+ self.assertEqual(
+ (
+ def_glyphs[0].name,
+ def_glyphs[0].id,
+ def_glyphs[0].unicode,
+ def_glyphs[0].type,
+ def_glyphs[0].components,
+ ),
+ ("A", 3, [0x41], "BASE", None),
+ )
+ self.assertEqual(
+ (
+ def_glyphs[1].name,
+ def_glyphs[1].id,
+ def_glyphs[1].unicode,
+ def_glyphs[1].type,
+ def_glyphs[1].components,
+ ),
+ ("a", 4, [0x61], "BASE", None),
+ )
def test_def_group_glyphs(self):
[def_group] = self.parse(
@@ -123,61 +200,70 @@ class ParserTest(unittest.TestCase):
' 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'
+ "END_GROUP"
).statements
- self.assertEqual((def_group.name, def_group.enum.glyphSet()),
- ("aaccented",
- ("aacute", "abreve", "acircumflex", "adieresis",
- "ae", "agrave", "amacron", "aogonek", "aring",
- "atilde")))
+ self.assertEqual(
+ (def_group.name, def_group.enum.glyphSet()),
+ (
+ "aaccented",
+ (
+ "aacute",
+ "abreve",
+ "acircumflex",
+ "adieresis",
+ "ae",
+ "agrave",
+ "amacron",
+ "aogonek",
+ "aring",
+ "atilde",
+ ),
+ ),
+ )
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'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_GROUP "Group2"\n'
' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_GROUP "TestGroup"\n'
' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
- 'END_GROUP'
+ "END_GROUP"
).statements
groups = [g.group for g in test_group.enum.enum]
- self.assertEqual((test_group.name, groups),
- ("TestGroup", ["Group1", "Group2"]))
+ self.assertEqual((test_group.name, groups), ("TestGroup", ["Group1", "Group2"]))
def test_def_group_groups_not_yet_defined(self):
- [group1, test_group1, test_group2, test_group3, group2] = \
- self.parse(
+ [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'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_GROUP "TestGroup1"\n'
' ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_GROUP "TestGroup2"\n'
' ENUM GROUP "Group2" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_GROUP "TestGroup3"\n'
' ENUM GROUP "Group2" GROUP "Group1" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_GROUP "Group2"\n'
' ENUM GLYPH "e" GLYPH "f" GLYPH "g" GLYPH "h" END_ENUM\n'
- 'END_GROUP'
+ "END_GROUP"
).statements
groups = [g.group for g in test_group1.enum.enum]
self.assertEqual(
- (test_group1.name, groups),
- ("TestGroup1", ["Group1", "Group2"]))
+ (test_group1.name, groups), ("TestGroup1", ["Group1", "Group2"])
+ )
groups = [g.group for g in test_group2.enum.enum]
- self.assertEqual(
- (test_group2.name, groups),
- ("TestGroup2", ["Group2"]))
+ self.assertEqual((test_group2.name, groups), ("TestGroup2", ["Group2"]))
groups = [g.group for g in test_group3.enum.enum]
self.assertEqual(
- (test_group3.name, groups),
- ("TestGroup3", ["Group2", "Group1"]))
+ (test_group3.name, groups), ("TestGroup3", ["Group2", "Group1"])
+ )
# def test_def_group_groups_undefined(self):
# with self.assertRaisesRegex(
@@ -198,14 +284,16 @@ class ParserTest(unittest.TestCase):
' 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\n"
'DEF_GROUP "KERN_lc_a_2ND"\n'
' ENUM GLYPH "a" GROUP "aaccented" END_ENUM\n'
- 'END_GROUP'
+ "END_GROUP"
).statements
items = def_group2.enum.enum
- self.assertEqual((def_group2.name, items[0].glyphSet(), items[1].group),
- ("KERN_lc_a_2ND", ("a",), "aaccented"))
+ self.assertEqual(
+ (def_group2.name, items[0].glyphSet(), items[1].group),
+ ("KERN_lc_a_2ND", ("a",), "aaccented"),
+ )
def test_def_group_range(self):
def_group = self.parse(
@@ -220,169 +308,166 @@ class ParserTest(unittest.TestCase):
'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" '
- 'END_ENUM\n'
- 'END_GROUP'
+ "END_ENUM\n"
+ "END_GROUP"
).statements[-1]
- self.assertEqual((def_group.name, def_group.enum.glyphSet()),
- ("KERN_lc_a_2ND",
- ("a", "agrave", "aacute", "acircumflex", "atilde",
- "b", "c", "ccaron", "ccedilla", "cdotaccent")))
+ self.assertEqual(
+ (def_group.name, def_group.enum.glyphSet()),
+ (
+ "KERN_lc_a_2ND",
+ (
+ "a",
+ "agrave",
+ "aacute",
+ "acircumflex",
+ "atilde",
+ "b",
+ "c",
+ "ccaron",
+ "ccedilla",
+ "cdotaccent",
+ ),
+ ),
+ )
def test_group_duplicate(self):
self.assertRaisesRegex(
VoltLibError,
- 'Glyph group "dupe" already defined, '
- 'group names are case insensitive',
- self.parse, 'DEF_GROUP "dupe"\n'
- 'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
- 'END_GROUP\n'
- 'DEF_GROUP "dupe"\n'
- 'ENUM GLYPH "x" END_ENUM\n'
- 'END_GROUP'
+ 'Glyph group "dupe" already defined, ' "group names are case insensitive",
+ self.parse,
+ 'DEF_GROUP "dupe"\n'
+ 'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
+ "END_GROUP\n"
+ 'DEF_GROUP "dupe"\n'
+ 'ENUM GLYPH "x" END_ENUM\n'
+ "END_GROUP",
)
def test_group_duplicate_case_insensitive(self):
self.assertRaisesRegex(
VoltLibError,
- 'Glyph group "Dupe" already defined, '
- 'group names are case insensitive',
- self.parse, 'DEF_GROUP "dupe"\n'
- 'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
- 'END_GROUP\n'
- 'DEF_GROUP "Dupe"\n'
- 'ENUM GLYPH "x" END_ENUM\n'
- 'END_GROUP'
+ 'Glyph group "Dupe" already defined, ' "group names are case insensitive",
+ self.parse,
+ 'DEF_GROUP "dupe"\n'
+ 'ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
+ "END_GROUP\n"
+ 'DEF_GROUP "Dupe"\n'
+ 'ENUM GLYPH "x" END_ENUM\n'
+ "END_GROUP",
)
def test_script_without_langsys(self):
[script] = self.parse(
- 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
- 'END_SCRIPT'
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n' "END_SCRIPT"
).statements
- self.assertEqual((script.name, script.tag, script.langs),
- ("Latin", "latn", []))
+ self.assertEqual((script.name, script.tag, script.langs), ("Latin", "latn", []))
def test_langsys_normal(self):
[def_script] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
- 'END_LANGSYS\n'
+ "END_LANGSYS\n"
'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
- self.assertEqual((def_script.name, def_script.tag),
- ("Latin",
- "latn"))
+ self.assertEqual((def_script.name, def_script.tag), ("Latin", "latn"))
def_lang = def_script.langs[0]
- self.assertEqual((def_lang.name, def_lang.tag),
- ("Romanian",
- "ROM "))
+ self.assertEqual((def_lang.name, def_lang.tag), ("Romanian", "ROM "))
def_lang = def_script.langs[1]
- self.assertEqual((def_lang.name, def_lang.tag),
- ("Moldavian",
- "MOL "))
+ self.assertEqual((def_lang.name, def_lang.tag), ("Moldavian", "MOL "))
def test_langsys_no_script_name(self):
[langsys] = self.parse(
'DEF_SCRIPT TAG "latn"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
- self.assertEqual((langsys.name, langsys.tag),
- (None,
- "latn"))
+ self.assertEqual((langsys.name, langsys.tag), (None, "latn"))
lang = langsys.langs[0]
- self.assertEqual((lang.name, lang.tag),
- ("Default",
- "dflt"))
+ self.assertEqual((lang.name, lang.tag), ("Default", "dflt"))
def test_langsys_no_script_tag_fails(self):
- with self.assertRaisesRegex(
- VoltLibError,
- r'.*Expected "TAG"'):
+ with self.assertRaisesRegex(VoltLibError, r'.*Expected "TAG"'):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Latin"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
def test_langsys_duplicate_script(self):
with self.assertRaisesRegex(
- VoltLibError,
- 'Script "DFLT" already defined, '
- 'script tags are case insensitive'):
+ VoltLibError,
+ 'Script "DFLT" already defined, ' "script tags are case insensitive",
+ ):
[langsys1, langsys2] = self.parse(
'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT\n'
+ "END_LANGSYS\n"
+ "END_SCRIPT\n"
'DEF_SCRIPT TAG "DFLT"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
def test_langsys_duplicate_lang(self):
with self.assertRaisesRegex(
- VoltLibError,
- 'Language "dflt" already defined in script "DFLT", '
- 'language tags are case insensitive'):
+ VoltLibError,
+ 'Language "dflt" already defined in script "DFLT", '
+ "language tags are case insensitive",
+ ):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Default" TAG "DFLT"\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
- 'END_LANGSYS\n'
+ "END_LANGSYS\n"
'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
def test_langsys_lang_in_separate_scripts(self):
[langsys1, langsys2] = self.parse(
'DEF_SCRIPT NAME "Default" TAG "DFLT"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
- 'END_LANGSYS\n'
+ "END_LANGSYS\n"
'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT\n'
+ "END_LANGSYS\n"
+ "END_SCRIPT\n"
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Default" TAG "dflt"\n\n'
- 'END_LANGSYS\n'
+ "END_LANGSYS\n"
'DEF_LANGSYS NAME "Default" TAG "ROM "\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
- self.assertEqual((langsys1.langs[0].tag, langsys1.langs[1].tag),
- ("dflt", "ROM "))
- self.assertEqual((langsys2.langs[0].tag, langsys2.langs[1].tag),
- ("dflt", "ROM "))
+ self.assertEqual(
+ (langsys1.langs[0].tag, langsys1.langs[1].tag), ("dflt", "ROM ")
+ )
+ self.assertEqual(
+ (langsys2.langs[0].tag, langsys2.langs[1].tag), ("dflt", "ROM ")
+ )
def test_langsys_no_lang_name(self):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS TAG "dflt"\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
- self.assertEqual((langsys.name, langsys.tag),
- ("Latin",
- "latn"))
+ self.assertEqual((langsys.name, langsys.tag), ("Latin", "latn"))
lang = langsys.langs[0]
- self.assertEqual((lang.name, lang.tag),
- (None,
- "dflt"))
+ self.assertEqual((lang.name, lang.tag), (None, "dflt"))
def test_langsys_no_langsys_tag_fails(self):
- with self.assertRaisesRegex(
- VoltLibError,
- r'.*Expected "TAG"'):
+ with self.assertRaisesRegex(VoltLibError, r'.*Expected "TAG"'):
[langsys] = self.parse(
'DEF_SCRIPT NAME "Latin" TAG "latn"\n\n'
'DEF_LANGSYS NAME "Default"\n\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
def test_feature(self):
@@ -391,181 +476,168 @@ class ParserTest(unittest.TestCase):
'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n\n'
'DEF_FEATURE NAME "Fractions" TAG "frac"\n'
' LOOKUP "fraclookup"\n'
- 'END_FEATURE\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_FEATURE\n"
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
def_feature = def_script.langs[0].features[0]
- self.assertEqual((def_feature.name, def_feature.tag,
- def_feature.lookups),
- ("Fractions",
- "frac",
- ["fraclookup"]))
+ self.assertEqual(
+ (def_feature.name, def_feature.tag, def_feature.lookups),
+ ("Fractions", "frac", ["fraclookup"]),
+ )
[def_script] = self.parse(
'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'
- 'END_FEATURE\n'
- 'END_LANGSYS\n'
- 'END_SCRIPT'
+ "END_FEATURE\n"
+ "END_LANGSYS\n"
+ "END_SCRIPT"
).statements
def_feature = def_script.langs[0].features[0]
- self.assertEqual((def_feature.name, def_feature.tag,
- def_feature.lookups),
- ("Kerning",
- "kern",
- ["kern1", "kern2"]))
+ self.assertEqual(
+ (def_feature.name, def_feature.tag, def_feature.lookups),
+ ("Kerning", "kern", ["kern1", "kern2"]),
+ )
def test_lookup_duplicate(self):
with self.assertRaisesRegex(
VoltLibError,
- 'Lookup "dupe" already defined, '
- 'lookup names are case insensitive',
+ 'Lookup "dupe" already defined, ' "lookup names are case insensitive",
):
[lookup1, lookup2] = self.parse(
'DEF_LOOKUP "dupe"\n'
- 'AS_SUBSTITUTION\n'
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION\n"
'DEF_LOOKUP "dupe"\n'
- 'AS_SUBSTITUTION\n'
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "b"\n'
'WITH GLYPH "b.alt"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION\n"
).statements
def test_lookup_duplicate_insensitive_case(self):
with self.assertRaisesRegex(
VoltLibError,
- 'Lookup "Dupe" already defined, '
- 'lookup names are case insensitive',
+ 'Lookup "Dupe" already defined, ' "lookup names are case insensitive",
):
[lookup1, lookup2] = self.parse(
'DEF_LOOKUP "dupe"\n'
- 'AS_SUBSTITUTION\n'
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION\n"
'DEF_LOOKUP "Dupe"\n'
- 'AS_SUBSTITUTION\n'
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "b"\n'
'WITH GLYPH "b.alt"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION\n"
).statements
def test_lookup_name_starts_with_letter(self):
with self.assertRaisesRegex(
- VoltLibError,
- r'Lookup name "\\lookupname" must start with a letter'
+ VoltLibError, r'Lookup name "\\lookupname" must start with a letter'
):
[lookup] = self.parse(
'DEF_LOOKUP "\\lookupname"\n'
- 'AS_SUBSTITUTION\n'
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION\n'
+ "END_SUB\n"
+ "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'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "a"\n'
'WITH GLYPH "b"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "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,
- r'Expected SUB'):
+ with self.assertRaisesRegex(VoltLibError, r"Expected SUB"):
[lookup] = self.parse(
'DEF_LOOKUP "empty_substitution" PROCESS_BASE PROCESS_MARKS '
- 'ALL DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
- 'END_SUBSTITUTION'
+ "ALL DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ "END_SUBSTITUTION"
).statements
def test_substitution_invalid_many_to_many(self):
- with self.assertRaisesRegex(
- VoltLibError,
- r'Invalid substitution type'):
+ with self.assertRaisesRegex(VoltLibError, r"Invalid substitution type"):
[lookup] = self.parse(
'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
- 'ALL DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "ALL DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "f" GLYPH "i"\n'
'WITH GLYPH "f.alt" GLYPH "i.alt"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
def test_substitution_invalid_reverse_chaining_single(self):
- with self.assertRaisesRegex(
- VoltLibError,
- r'Invalid substitution type'):
+ with self.assertRaisesRegex(VoltLibError, r"Invalid substitution type"):
[lookup] = self.parse(
'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
- 'ALL DIRECTION LTR REVERSAL\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "ALL DIRECTION LTR REVERSAL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "f" GLYPH "i"\n'
'WITH GLYPH "f_i"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
def test_substitution_invalid_mixed(self):
- with self.assertRaisesRegex(
- VoltLibError,
- r'Invalid substitution type'):
+ with self.assertRaisesRegex(VoltLibError, r"Invalid substitution type"):
[lookup] = self.parse(
'DEF_LOOKUP "invalid_substitution" PROCESS_BASE PROCESS_MARKS '
- 'ALL DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "ALL DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "fi"\n'
'WITH GLYPH "f" GLYPH "i"\n'
- 'END_SUB\n'
+ "END_SUB\n"
'SUB GLYPH "f" GLYPH "l"\n'
'WITH GLYPH "f_l"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
def test_substitution_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "a"\n'
'WITH GLYPH "a.sc"\n'
- 'END_SUB\n'
+ "END_SUB\n"
'SUB GLYPH "b"\n'
'WITH GLYPH "b.sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
self.assertEqual(lookup.name, "smcp")
self.assertSubEqual(lookup.sub, [["a"], ["b"]], [["a.sc"], ["b.sc"]])
@@ -574,20 +646,20 @@ class ParserTest(unittest.TestCase):
[group, lookup] = self.parse(
'DEF_GROUP "Denominators"\n'
' ENUM GLYPH "one.dnom" GLYPH "two.dnom" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "fracdnom" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
' LEFT ENUM GROUP "Denominators" GLYPH "fraction" END_ENUM\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "one"\n'
'WITH GLYPH "one.dnom"\n'
- 'END_SUB\n'
+ "END_SUB\n"
'SUB GLYPH "two"\n'
'WITH GLYPH "two.dnom"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
context = lookup.context[0]
@@ -599,29 +671,30 @@ class ParserTest(unittest.TestCase):
self.assertEqual(context.left[0][0].enum[0].group, "Denominators")
self.assertEqual(context.left[0][0].enum[1].glyph, "fraction")
self.assertEqual(context.right, [])
- self.assertSubEqual(lookup.sub, [["one"], ["two"]],
- [["one.dnom"], ["two.dnom"]])
+ self.assertSubEqual(
+ lookup.sub, [["one"], ["two"]], [["one.dnom"], ["two.dnom"]]
+ )
def test_substitution_single_in_contexts(self):
[group, lookup] = self.parse(
'DEF_GROUP "Hebrew"\n'
' ENUM GLYPH "uni05D0" GLYPH "uni05D1" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
' RIGHT GROUP "Hebrew"\n'
' RIGHT GLYPH "one.Hebr"\n'
- 'END_CONTEXT\n'
- 'IN_CONTEXT\n'
+ "END_CONTEXT\n"
+ "IN_CONTEXT\n"
' LEFT GROUP "Hebrew"\n'
' LEFT GLYPH "one.Hebr"\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "dollar"\n'
'WITH GLYPH "dollar.Hebr"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
context1 = lookup.context[0]
context2 = lookup.context[1]
@@ -648,273 +721,251 @@ class ParserTest(unittest.TestCase):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "SomeSub" SKIP_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_base),
- ("SomeSub", False))
+ self.assertEqual((lookup.name, lookup.process_base), ("SomeSub", False))
def test_substitution_process_base(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_base),
- ("SomeSub", True))
+ self.assertEqual((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'
+ "END_GROUP\n"
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "SomeMarks"\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_marks),
- ("SomeSub", 'SomeMarks'))
+ 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'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_marks),
- ("SomeSub", True))
+ 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'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_marks),
- ("SomeSub", False))
+ 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'):
+ 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'
+ "END_ENUM END_GROUP\n"
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS SomeMarks '
- 'AS_SUBSTITUTION\n'
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A" WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
)
def test_substitution_skip_marks(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "marka" GLYPH "markb" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "SomeSub" PROCESS_BASE SKIP_MARKS DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_marks),
- ("SomeSub", False))
+ self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", False))
def test_substitution_mark_attachment(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "SomeSub" PROCESS_BASE '
'PROCESS_MARKS "SomeMarks" DIRECTION RTL\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_marks),
- ("SomeSub", "SomeMarks"))
+ self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", "SomeMarks"))
def test_substitution_mark_glyph_set(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "SomeSub" PROCESS_BASE '
'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" DIRECTION RTL\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.mark_glyph_set),
- ("SomeSub", "SomeMarks"))
+ self.assertEqual((lookup.name, lookup.mark_glyph_set), ("SomeSub", "SomeMarks"))
def test_substitution_process_all_marks(self):
[group, lookup] = self.parse(
'DEF_GROUP "SomeMarks"\n'
' ENUM GLYPH "acutecmb" GLYPH "gravecmb" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION RTL\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "A"\n'
'WITH GLYPH "A.c2sc"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.process_marks),
- ("SomeSub", True))
+ self.assertEqual((lookup.name, lookup.process_marks), ("SomeSub", True))
def test_substitution_no_reversal(self):
# TODO: check right context with no reversal
[lookup] = self.parse(
'DEF_LOOKUP "Lookup" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "a"\n'
'WITH GLYPH "a.alt"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
- self.assertEqual(
- (lookup.name, lookup.reversal),
- ("Lookup", None)
- )
+ self.assertEqual((lookup.name, lookup.reversal), ("Lookup", None))
def test_substitution_reversal(self):
lookup = self.parse(
'DEF_GROUP "DFLT_Num_standardFigures"\n'
' ENUM GLYPH "zero" GLYPH "one" GLYPH "two" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_GROUP "DFLT_Num_numerators"\n'
' ENUM GLYPH "zero.numr" GLYPH "one.numr" GLYPH "two.numr" END_ENUM\n'
- 'END_GROUP\n'
+ "END_GROUP\n"
'DEF_LOOKUP "RevLookup" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR REVERSAL\n'
- 'IN_CONTEXT\n'
+ "DIRECTION LTR REVERSAL\n"
+ "IN_CONTEXT\n"
' RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GROUP "DFLT_Num_standardFigures"\n'
'WITH GROUP "DFLT_Num_numerators"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements[-1]
- self.assertEqual(
- (lookup.name, lookup.reversal),
- ("RevLookup", True)
- )
+ self.assertEqual((lookup.name, lookup.reversal), ("RevLookup", True))
def test_substitution_single_to_multiple(self):
[lookup] = self.parse(
'DEF_LOOKUP "ccmp" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "aacute"\n'
'WITH GLYPH "a" GLYPH "acutecomb"\n'
- 'END_SUB\n'
+ "END_SUB\n"
'SUB GLYPH "agrave"\n'
'WITH GLYPH "a" GLYPH "gravecomb"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
self.assertEqual(lookup.name, "ccmp")
- self.assertSubEqual(lookup.sub, [["aacute"], ["agrave"]],
- [["a", "acutecomb"], ["a", "gravecomb"]])
+ self.assertSubEqual(
+ lookup.sub,
+ [["aacute"], ["agrave"]],
+ [["a", "acutecomb"], ["a", "gravecomb"]],
+ )
def test_substitution_multiple_to_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "liga" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB GLYPH "f" GLYPH "i"\n'
'WITH GLYPH "f_i"\n'
- 'END_SUB\n'
+ "END_SUB\n"
'SUB GLYPH "f" GLYPH "t"\n'
'WITH GLYPH "f_t"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
self.assertEqual(lookup.name, "liga")
- self.assertSubEqual(lookup.sub, [["f", "i"], ["f", "t"]],
- [["f_i"], ["f_t"]])
+ self.assertSubEqual(lookup.sub, [["f", "i"], ["f", "t"]], [["f_i"], ["f_t"]])
def test_substitution_reverse_chaining_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR REVERSAL\n'
- 'IN_CONTEXT\n'
- ' RIGHT ENUM '
+ "DIRECTION LTR REVERSAL\n"
+ "IN_CONTEXT\n"
+ " RIGHT ENUM "
'GLYPH "fraction" '
'RANGE "zero.numr" TO "nine.numr" '
- 'END_ENUM\n'
- 'END_CONTEXT\n'
- 'AS_SUBSTITUTION\n'
+ "END_ENUM\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
'SUB RANGE "zero" TO "nine"\n'
'WITH RANGE "zero.numr" TO "nine.numr"\n'
- 'END_SUB\n'
- 'END_SUBSTITUTION'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
).statements
mapping = lookup.sub.mapping
@@ -922,16 +973,17 @@ class ParserTest(unittest.TestCase):
replacement = [[(r.start, r.end) for r in v] for v in mapping.values()]
self.assertEqual(lookup.name, "numr")
- self.assertEqual(glyphs, [[('zero', 'nine')]])
- self.assertEqual(replacement, [[('zero.numr', 'nine.numr')]])
+ self.assertEqual(glyphs, [[("zero", "nine")]])
+ self.assertEqual(replacement, [[("zero.numr", "nine.numr")]])
self.assertEqual(len(lookup.context[0].right), 1)
self.assertEqual(len(lookup.context[0].right[0]), 1)
enum = lookup.context[0].right[0][0]
self.assertEqual(len(enum.enum), 2)
self.assertEqual(enum.enum[0].glyph, "fraction")
- self.assertEqual((enum.enum[1].start, enum.enum[1].end),
- ('zero.numr', 'nine.numr'))
+ self.assertEqual(
+ (enum.enum[1].start, enum.enum[1].end), ("zero.numr", "nine.numr")
+ )
# GPOS
# ATTACH_CURSIVE
@@ -940,296 +992,365 @@ class ParserTest(unittest.TestCase):
# ADJUST_SINGLE
def test_position_empty(self):
with self.assertRaisesRegex(
- VoltLibError,
- 'Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE'):
+ VoltLibError, "Expected ATTACH, ATTACH_CURSIVE, ADJUST_PAIR, ADJUST_SINGLE"
+ ):
[lookup] = self.parse(
'DEF_LOOKUP "empty_position" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'EXCEPT_CONTEXT\n'
+ "DIRECTION LTR\n"
+ "EXCEPT_CONTEXT\n"
' LEFT GLYPH "glyph"\n'
- 'END_CONTEXT\n'
- 'AS_POSITION\n'
- 'END_POSITION'
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ "END_POSITION"
).statements
def test_position_attach(self):
[lookup, anchor1, anchor2, anchor3, anchor4] = self.parse(
'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION RTL\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_POSITION\n'
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
'ATTACH GLYPH "a" GLYPH "e"\n'
'TO GLYPH "acutecomb" AT ANCHOR "top" '
'GLYPH "gravecomb" AT ANCHOR "top"\n'
- 'END_ATTACH\n'
- 'END_POSITION\n'
+ "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'
+ "AT POS DX 215 DY 450 END_POS END_ANCHOR"
).statements
pos = lookup.pos
coverage = [g.glyph for g in pos.coverage]
coverage_to = [[[g.glyph for g in e], a] for (e, a) in pos.coverage_to]
self.assertEqual(
(lookup.name, coverage, coverage_to),
- ("anchor_top", ["a", "e"],
- [[["acutecomb"], "top"], [["gravecomb"], "top"]])
+ (
+ "anchor_top",
+ ["a", "e"],
+ [[["acutecomb"], "top"], [["gravecomb"], "top"]],
+ ),
)
self.assertEqual(
- (anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component,
- anchor1.locked, anchor1.pos),
- ("MARK_top", 120, "acutecomb", 1, False, (None, 0, 450, {}, {},
- {}))
+ (
+ anchor1.name,
+ anchor1.gid,
+ anchor1.glyph_name,
+ anchor1.component,
+ anchor1.locked,
+ anchor1.pos,
+ ),
+ ("MARK_top", 120, "acutecomb", 1, False, (None, 0, 450, {}, {}, {})),
)
self.assertEqual(
- (anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component,
- anchor2.locked, anchor2.pos),
- ("MARK_top", 121, "gravecomb", 1, False, (None, 0, 450, {}, {},
- {}))
+ (
+ anchor2.name,
+ anchor2.gid,
+ anchor2.glyph_name,
+ anchor2.component,
+ anchor2.locked,
+ anchor2.pos,
+ ),
+ ("MARK_top", 121, "gravecomb", 1, False, (None, 0, 450, {}, {}, {})),
)
self.assertEqual(
- (anchor3.name, anchor3.gid, anchor3.glyph_name, anchor3.component,
- anchor3.locked, anchor3.pos),
- ("top", 31, "a", 1, False, (None, 210, 450, {}, {}, {}))
+ (
+ anchor3.name,
+ anchor3.gid,
+ anchor3.glyph_name,
+ anchor3.component,
+ anchor3.locked,
+ anchor3.pos,
+ ),
+ ("top", 31, "a", 1, False, (None, 210, 450, {}, {}, {})),
)
self.assertEqual(
- (anchor4.name, anchor4.gid, anchor4.glyph_name, anchor4.component,
- anchor4.locked, anchor4.pos),
- ("top", 35, "e", 1, False, (None, 215, 450, {}, {}, {}))
+ (
+ anchor4.name,
+ anchor4.gid,
+ anchor4.glyph_name,
+ anchor4.component,
+ anchor4.locked,
+ anchor4.pos,
+ ),
+ ("top", 35, "e", 1, False, (None, 215, 450, {}, {}, {})),
)
def test_position_attach_cursive(self):
[lookup] = self.parse(
'DEF_LOOKUP "SomeLookup" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION RTL\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_POSITION\n'
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
'ATTACH_CURSIVE\nEXIT GLYPH "a" GLYPH "b"\nENTER GLYPH "c"\n'
- 'END_ATTACH\n'
- 'END_POSITION'
+ "END_ATTACH\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]
self.assertEqual(
- (lookup.name, exit, enter),
- ("SomeLookup", [["a", "b"]], [["c"]])
+ (lookup.name, exit, enter), ("SomeLookup", [["a", "b"]], [["c"]])
)
def test_position_adjust_pair(self):
[lookup] = self.parse(
'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION RTL\n'
- 'IN_CONTEXT\n'
- 'END_CONTEXT\n'
- 'AS_POSITION\n'
- 'ADJUST_PAIR\n'
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ "ADJUST_PAIR\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\n'
- 'END_ADJUST\n'
- 'END_POSITION'
+ " 1 2 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"
).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]
self.assertEqual(
- (lookup.name, coverages_1, coverages_2,
- lookup.pos.adjust_pair),
- ("kern1", [["A"]], [["V"]],
- {(1, 2): ((-30, None, None, {}, {}, {}),
- (None, None, None, {}, {}, {})),
- (2, 1): ((-30, None, None, {}, {}, {}),
- (None, None, None, {}, {}, {}))})
+ (lookup.name, coverages_1, coverages_2, lookup.pos.adjust_pair),
+ (
+ "kern1",
+ [["A"]],
+ [["V"]],
+ {
+ (1, 2): (
+ (-30, None, None, {}, {}, {}),
+ (None, None, None, {}, {}, {}),
+ ),
+ (2, 1): (
+ (-30, None, None, {}, {}, {}),
+ (None, None, None, {}, {}, {}),
+ ),
+ },
+ ),
)
def test_position_adjust_single(self):
[lookup] = self.parse(
'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
- 'DIRECTION LTR\n'
- 'IN_CONTEXT\n'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
# ' LEFT GLYPH "leftGlyph"\n'
# ' RIGHT GLYPH "rightGlyph"\n'
- 'END_CONTEXT\n'
- 'AS_POSITION\n'
- 'ADJUST_SINGLE'
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ "ADJUST_SINGLE"
' 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'
+ "END_ADJUST\n"
+ "END_POSITION"
).statements
pos = lookup.pos
adjust = [[[g.glyph for g in a], b] for (a, b) in pos.adjust_single]
self.assertEqual(
(lookup.name, adjust),
- ("TestLookup",
- [[["glyph1"], (0, 123, None, {}, {}, {})],
- [["glyph2"], (0, 456, None, {}, {}, {})]])
+ (
+ "TestLookup",
+ [
+ [["glyph1"], (0, 123, None, {}, {}, {})],
+ [["glyph2"], (0, 456, None, {}, {}, {})],
+ ],
+ ),
)
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'
+ "COMPONENT 1 AT POS DX 250 DY 0 END_POS END_ANCHOR"
).statements
self.assertEqual(
- (anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component,
- anchor1.locked, anchor1.pos),
- ("top", 120, "a", 1,
- False, (None, 250, 450, {}, {}, {}))
+ (
+ anchor1.name,
+ anchor1.gid,
+ anchor1.glyph_name,
+ anchor1.component,
+ anchor1.locked,
+ anchor1.pos,
+ ),
+ ("top", 120, "a", 1, False, (None, 250, 450, {}, {}, {})),
)
self.assertEqual(
- (anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component,
- anchor2.locked, anchor2.pos),
- ("MARK_top", 120, "acutecomb", 1,
- False, (None, 0, 450, {}, {}, {}))
+ (
+ anchor2.name,
+ anchor2.gid,
+ anchor2.glyph_name,
+ anchor2.component,
+ anchor2.locked,
+ anchor2.pos,
+ ),
+ ("MARK_top", 120, "acutecomb", 1, False, (None, 0, 450, {}, {}, {})),
)
self.assertEqual(
- (anchor3.name, anchor3.gid, anchor3.glyph_name, anchor3.component,
- anchor3.locked, anchor3.pos),
- ("bottom", 120, "a", 1,
- False, (None, 250, 0, {}, {}, {}))
+ (
+ anchor3.name,
+ anchor3.gid,
+ anchor3.glyph_name,
+ anchor3.component,
+ anchor3.locked,
+ anchor3.pos,
+ ),
+ ("bottom", 120, "a", 1, False, (None, 250, 0, {}, {}, {})),
)
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'
+ "COMPONENT 2 AT POS DX 250 DY 450 END_POS END_ANCHOR"
).statements
self.assertEqual(
(anchor1.name, anchor1.gid, anchor1.glyph_name, anchor1.component),
- ("top", 120, "a", 1)
+ ("top", 120, "a", 1),
)
self.assertEqual(
(anchor2.name, anchor2.gid, anchor2.glyph_name, anchor2.component),
- ("top", 120, "a", 2)
+ ("top", 120, "a", 2),
)
def test_def_anchor_duplicate(self):
self.assertRaisesRegex(
VoltLibError,
- 'Anchor "dupe" already defined, '
- 'anchor names are case insensitive',
+ 'Anchor "dupe" already defined, ' "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'
+ "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'
+ "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,
- anchor.locked, anchor.pos),
- ("top", 120, "a", 1,
- True, (None, 250, 450, {}, {}, {}))
+ (
+ anchor.name,
+ anchor.gid,
+ anchor.glyph_name,
+ anchor.component,
+ anchor.locked,
+ anchor.pos,
+ ),
+ ("top", 120, "a", 1, True, (None, 250, 450, {}, {}, {})),
)
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 '
- 'ADJUST_BY 56 AT 78 END_POS END_ANCHOR'
+ "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(
(anchor.name, anchor.pos),
- ("MARK_top", (None, 0, 456, {}, {}, {34: 12, 78: 56}))
+ ("MARK_top", (None, 0, 456, {}, {}, {34: 12, 78: 56})),
)
def test_ppem(self):
[grid_ppem, pres_ppem, ppos_ppem] = self.parse(
- 'GRID_PPEM 20\n'
- 'PRESENTATION_PPEM 72\n'
- 'PPOSITIONING_PPEM 144'
+ "GRID_PPEM 20\n" "PRESENTATION_PPEM 72\n" "PPOSITIONING_PPEM 144"
).statements
self.assertEqual(
- ((grid_ppem.name, grid_ppem.value),
- (pres_ppem.name, pres_ppem.value),
- (ppos_ppem.name, ppos_ppem.value)),
- (("GRID_PPEM", 20), ("PRESENTATION_PPEM", 72),
- ("PPOSITIONING_PPEM", 144))
+ (
+ (grid_ppem.name, grid_ppem.value),
+ (pres_ppem.name, pres_ppem.value),
+ (ppos_ppem.name, ppos_ppem.value),
+ ),
+ (("GRID_PPEM", 20), ("PRESENTATION_PPEM", 72), ("PPOSITIONING_PPEM", 144)),
)
def test_compiler_flags(self):
[setting1, setting2] = self.parse(
- 'COMPILER_USEEXTENSIONLOOKUPS\n'
- 'COMPILER_USEPAIRPOSFORMAT2'
+ "COMPILER_USEEXTENSIONLOOKUPS\n" "COMPILER_USEPAIRPOSFORMAT2"
).statements
self.assertEqual(
- ((setting1.name, setting1.value),
- (setting2.name, setting2.value)),
- (("COMPILER_USEEXTENSIONLOOKUPS", True),
- ("COMPILER_USEPAIRPOSFORMAT2", True))
+ ((setting1.name, setting1.value), (setting2.name, setting2.value)),
+ (
+ ("COMPILER_USEEXTENSIONLOOKUPS", True),
+ ("COMPILER_USEPAIRPOSFORMAT2", True),
+ ),
)
def test_cmap(self):
[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'
+ "CMAP_FORMAT 0 3 4\n" "CMAP_FORMAT 1 0 6\n" "CMAP_FORMAT 3 1 4"
).statements
self.assertEqual(
- ((cmap_format1.name, cmap_format1.value),
- (cmap_format2.name, cmap_format2.value),
- (cmap_format3.name, cmap_format3.value)),
- (("CMAP_FORMAT", (0, 3, 4)),
- ("CMAP_FORMAT", (1, 0, 6)),
- ("CMAP_FORMAT", (3, 1, 4)))
+ (
+ (cmap_format1.name, cmap_format1.value),
+ (cmap_format2.name, cmap_format2.value),
+ (cmap_format3.name, cmap_format3.value),
+ ),
+ (
+ ("CMAP_FORMAT", (0, 3, 4)),
+ ("CMAP_FORMAT", (1, 0, 6)),
+ ("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'
+ "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)))
+ (
+ (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):
- doc = self.parse_(
- 'DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\0\0\0\0'
- )
+ doc = self.parse_('DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH END\0\0\0\0')
[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')
+ 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):
doc = self.parse_(text)
- self.assertEqual('\n'.join(str(s) for s in doc.statements), text)
+ self.assertEqual("\n".join(str(s) for s in doc.statements), text)
return Parser(StringIO(text)).parse()
+
if __name__ == "__main__":
import sys
+
sys.exit(unittest.main())
diff --git a/Tests/voltLib/volttofea_test.py b/Tests/voltLib/volttofea_test.py
new file mode 100644
index 00000000..0d8d8d28
--- /dev/null
+++ b/Tests/voltLib/volttofea_test.py
@@ -0,0 +1,1253 @@
+import pathlib
+import shutil
+import tempfile
+import unittest
+from io import StringIO
+
+from fontTools.voltLib.voltToFea import VoltToFea
+
+DATADIR = pathlib.Path(__file__).parent / "data"
+
+
+class ToFeaTest(unittest.TestCase):
+ @classmethod
+ def setup_class(cls):
+ cls.tempdir = None
+ cls.num_tempfiles = 0
+
+ @classmethod
+ def teardown_class(cls):
+ if cls.tempdir:
+ shutil.rmtree(cls.tempdir, ignore_errors=True)
+
+ @classmethod
+ def temp_path(cls):
+ if not cls.tempdir:
+ cls.tempdir = pathlib.Path(tempfile.mkdtemp())
+ cls.num_tempfiles += 1
+ return cls.tempdir / f"tmp{cls.num_tempfiles}"
+
+ def test_def_glyph_base(self):
+ fea = self.parse('DEF_GLYPH ".notdef" ID 0 TYPE BASE END_GLYPH')
+ self.assertEqual(
+ fea,
+ "@GDEF_base = [.notdef];\n"
+ "table GDEF {\n"
+ " GlyphClassDef @GDEF_base, , , ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_def_glyph_base_2_components(self):
+ fea = self.parse(
+ 'DEF_GLYPH "glyphBase" ID 320 TYPE BASE COMPONENTS 2 END_GLYPH'
+ )
+ self.assertEqual(
+ fea,
+ "@GDEF_base = [glyphBase];\n"
+ "table GDEF {\n"
+ " GlyphClassDef @GDEF_base, , , ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_def_glyph_ligature_2_components(self):
+ fea = self.parse('DEF_GLYPH "f_f" ID 320 TYPE LIGATURE COMPONENTS 2 END_GLYPH')
+ self.assertEqual(
+ fea,
+ "@GDEF_ligature = [f_f];\n"
+ "table GDEF {\n"
+ " GlyphClassDef , @GDEF_ligature, , ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_def_glyph_mark(self):
+ fea = self.parse('DEF_GLYPH "brevecomb" ID 320 TYPE MARK END_GLYPH')
+ self.assertEqual(
+ fea,
+ "@GDEF_mark = [brevecomb];\n"
+ "table GDEF {\n"
+ " GlyphClassDef , , @GDEF_mark, ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_def_glyph_component(self):
+ fea = self.parse('DEF_GLYPH "f.f_f" ID 320 TYPE COMPONENT END_GLYPH')
+ self.assertEqual(
+ fea,
+ "@GDEF_component = [f.f_f];\n"
+ "table GDEF {\n"
+ " GlyphClassDef , , , @GDEF_component;\n"
+ "} GDEF;\n",
+ )
+
+ def test_def_glyph_no_type(self):
+ fea = self.parse('DEF_GLYPH "glyph20" ID 20 END_GLYPH')
+ self.assertEqual(fea, "")
+
+ def test_def_glyph_case_sensitive(self):
+ fea = 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'
+ )
+ self.assertEqual(
+ fea,
+ "@GDEF_base = [A a];\n"
+ "table GDEF {\n"
+ " GlyphClassDef @GDEF_base, , , ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_def_group_glyphs(self):
+ fea = self.parse(
+ 'DEF_GROUP "aaccented"\n'
+ '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"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@aaccented = [aacute abreve acircumflex adieresis ae"
+ " agrave amacron aogonek aring atilde];",
+ )
+
+ def test_def_group_groups(self):
+ fea = self.parse(
+ 'DEF_GROUP "Group1"\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'
+ "END_GROUP\n"
+ 'DEF_GROUP "TestGroup"\n'
+ 'ENUM GROUP "Group1" GROUP "Group2" END_ENUM\n'
+ "END_GROUP\n"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@Group1 = [a b c d];\n"
+ "@Group2 = [e f g h];\n"
+ "@TestGroup = [@Group1 @Group2];",
+ )
+
+ def test_def_group_groups_not_yet_defined(self):
+ fea = self.parse(
+ 'DEF_GROUP "Group1"\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'
+ "END_GROUP\n"
+ 'DEF_GROUP "TestGroup2"\n'
+ 'ENUM GROUP "Group2" END_ENUM\n'
+ "END_GROUP\n"
+ 'DEF_GROUP "TestGroup3"\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"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@Group1 = [a b c d];\n"
+ "@Group2 = [e f g h];\n"
+ "@TestGroup1 = [@Group1 @Group2];\n"
+ "@TestGroup2 = [@Group2];\n"
+ "@TestGroup3 = [@Group2 @Group1];",
+ )
+
+ def test_def_group_glyphs_and_group(self):
+ fea = self.parse(
+ 'DEF_GROUP "aaccented"\n'
+ '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'
+ "END_GROUP"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@aaccented = [aacute abreve acircumflex adieresis ae"
+ " agrave amacron aogonek aring atilde];\n"
+ "@KERN_lc_a_2ND = [a @aaccented];",
+ )
+
+ def test_def_group_range(self):
+ fea = self.parse(
+ 'DEF_GLYPH "a" ID 163 UNICODE 97 TYPE BASE END_GLYPH\n'
+ 'DEF_GLYPH "agrave" ID 194 UNICODE 224 TYPE BASE END_GLYPH\n'
+ 'DEF_GLYPH "aacute" ID 195 UNICODE 225 TYPE BASE END_GLYPH\n'
+ 'DEF_GLYPH "acircumflex" ID 196 UNICODE 226 TYPE BASE END_GLYPH\n'
+ 'DEF_GLYPH "atilde" ID 197 UNICODE 227 TYPE BASE END_GLYPH\n'
+ 'DEF_GLYPH "c" ID 165 UNICODE 99 TYPE BASE END_GLYPH\n'
+ 'DEF_GLYPH "ccaron" ID 209 UNICODE 269 TYPE BASE END_GLYPH\n'
+ '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" '
+ "END_ENUM\n"
+ "END_GROUP"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@KERN_lc_a_2ND = [a - atilde b c - cdotaccent];\n"
+ "@GDEF_base = [a agrave aacute acircumflex atilde c"
+ " ccaron ccedilla cdotaccent];\n"
+ "table GDEF {\n"
+ " GlyphClassDef @GDEF_base, , , ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_script_without_langsys(self):
+ fea = self.parse('DEF_SCRIPT NAME "Latin" TAG "latn"\n' "END_SCRIPT")
+ self.assertEqual(fea, "")
+
+ def test_langsys_normal(self):
+ fea = self.parse(
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
+ 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n'
+ "END_LANGSYS\n"
+ 'DEF_LANGSYS NAME "Moldavian" TAG "MOL "\n'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
+ )
+ self.assertEqual(fea, "")
+
+ def test_langsys_no_script_name(self):
+ fea = self.parse(
+ 'DEF_SCRIPT TAG "latn"\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
+ )
+ self.assertEqual(fea, "")
+
+ def test_langsys_lang_in_separate_scripts(self):
+ fea = self.parse(
+ 'DEF_SCRIPT NAME "Default" TAG "DFLT"\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ "END_LANGSYS\n"
+ 'DEF_LANGSYS NAME "Default" TAG "ROM "\n'
+ "END_LANGSYS\n"
+ "END_SCRIPT\n"
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
+ 'DEF_LANGSYS NAME "Default" TAG "dflt"\n'
+ "END_LANGSYS\n"
+ 'DEF_LANGSYS NAME "Default" TAG "ROM "\n'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
+ )
+ self.assertEqual(fea, "")
+
+ def test_langsys_no_lang_name(self):
+ fea = self.parse(
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
+ 'DEF_LANGSYS TAG "dflt"\n'
+ "END_LANGSYS\n"
+ "END_SCRIPT"
+ )
+ self.assertEqual(fea, "")
+
+ def test_feature(self):
+ fea = self.parse(
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
+ 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n'
+ 'DEF_FEATURE NAME "Fractions" TAG "frac"\n'
+ 'LOOKUP "fraclookup"\n'
+ "END_FEATURE\n"
+ "END_LANGSYS\n"
+ "END_SCRIPT\n"
+ 'DEF_LOOKUP "fraclookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "one" GLYPH "slash" GLYPH "two"\n'
+ 'WITH GLYPH "one_slash_two.frac"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup fraclookup {\n"
+ " sub one slash two by one_slash_two.frac;\n"
+ "} fraclookup;\n"
+ "\n"
+ "# Features\n"
+ "feature frac {\n"
+ " script latn;\n"
+ " language ROM exclude_dflt;\n"
+ " lookup fraclookup;\n"
+ "} frac;\n",
+ )
+
+ def test_feature_sub_lookups(self):
+ fea = self.parse(
+ 'DEF_SCRIPT NAME "Latin" TAG "latn"\n'
+ 'DEF_LANGSYS NAME "Romanian" TAG "ROM "\n'
+ 'DEF_FEATURE NAME "Fractions" TAG "frac"\n'
+ 'LOOKUP "fraclookup\\1"\n'
+ 'LOOKUP "fraclookup\\1"\n'
+ "END_FEATURE\n"
+ "END_LANGSYS\n"
+ "END_SCRIPT\n"
+ 'DEF_LOOKUP "fraclookup\\1" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "one" GLYPH "slash" GLYPH "two"\n'
+ 'WITH GLYPH "one_slash_two.frac"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION\n"
+ 'DEF_LOOKUP "fraclookup\\2" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "one" GLYPH "slash" GLYPH "three"\n'
+ 'WITH GLYPH "one_slash_three.frac"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup fraclookup {\n"
+ " lookupflag RightToLeft;\n"
+ " # fraclookup\\1\n"
+ " sub one slash two by one_slash_two.frac;\n"
+ " subtable;\n"
+ " # fraclookup\\2\n"
+ " sub one slash three by one_slash_three.frac;\n"
+ "} fraclookup;\n"
+ "\n"
+ "# Features\n"
+ "feature frac {\n"
+ " script latn;\n"
+ " language ROM exclude_dflt;\n"
+ " lookup fraclookup;\n"
+ "} frac;\n",
+ )
+
+ def test_lookup_comment(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ 'COMMENTS "Smallcaps lookup for testing"\n'
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "a"\n'
+ 'WITH GLYPH "a.sc"\n'
+ "END_SUB\n"
+ 'SUB GLYPH "b"\n'
+ 'WITH GLYPH "b.sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup smcp {\n"
+ " # Smallcaps lookup for testing\n"
+ " sub a by a.sc;\n"
+ " sub b by b.sc;\n"
+ "} smcp;\n",
+ )
+
+ def test_substitution_single(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "smcp" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "a"\n'
+ 'WITH GLYPH "a.sc"\n'
+ "END_SUB\n"
+ 'SUB GLYPH "b"\n'
+ 'WITH GLYPH "b.sc"\n'
+ "END_SUB\n"
+ "SUB WITH\n" # Empty substitution, will be ignored
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup smcp {\n"
+ " sub a by a.sc;\n"
+ " sub b by b.sc;\n"
+ "} smcp;\n",
+ )
+
+ def test_substitution_single_in_context(self):
+ fea = self.parse(
+ 'DEF_GROUP "Denominators" ENUM GLYPH "one.dnom" GLYPH "two.dnom" '
+ "END_ENUM 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"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "one"\n'
+ 'WITH GLYPH "one.dnom"\n'
+ "END_SUB\n"
+ 'SUB GLYPH "two"\n'
+ 'WITH GLYPH "two.dnom"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@Denominators = [one.dnom two.dnom];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup fracdnom {\n"
+ " sub [@Denominators fraction] one' by one.dnom;\n"
+ " sub [@Denominators fraction] two' by two.dnom;\n"
+ "} fracdnom;\n",
+ )
+
+ def test_substitution_single_in_contexts(self):
+ fea = self.parse(
+ 'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" '
+ "END_ENUM 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'
+ "END_CONTEXT\n"
+ "IN_CONTEXT\n"
+ 'LEFT GROUP "Hebrew"\n'
+ 'LEFT GLYPH "one.Hebr"\n'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "dollar"\n'
+ 'WITH GLYPH "dollar.Hebr"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@Hebrew = [uni05D0 uni05D1];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup HebrewCurrency {\n"
+ " sub dollar' @Hebrew one.Hebr by dollar.Hebr;\n"
+ " sub @Hebrew one.Hebr dollar' by dollar.Hebr;\n"
+ "} HebrewCurrency;\n",
+ )
+
+ def test_substitution_single_except_context(self):
+ fea = self.parse(
+ 'DEF_GROUP "Hebrew" ENUM GLYPH "uni05D0" GLYPH "uni05D1" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "HebrewCurrency" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "EXCEPT_CONTEXT\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'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "dollar"\n'
+ 'WITH GLYPH "dollar.Hebr"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@Hebrew = [uni05D0 uni05D1];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup HebrewCurrency {\n"
+ " ignore sub dollar' @Hebrew one.Hebr;\n"
+ " sub @Hebrew one.Hebr dollar' by dollar.Hebr;\n"
+ "} HebrewCurrency;\n",
+ )
+
+ def test_substitution_skip_base(self):
+ fea = self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "SomeSub" SKIP_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [marka markb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " lookupflag IgnoreBaseGlyphs;\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_process_base(self):
+ fea = self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [marka markb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_process_marks_all(self):
+ fea = self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "ALL"'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [marka markb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_process_marks_none(self):
+ fea = self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "marka" GLYPH "markb" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE PROCESS_MARKS "NONE"'
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [marka markb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " lookupflag IgnoreMarks;\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_skip_marks(self):
+ fea = 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"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [marka markb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " lookupflag IgnoreMarks;\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_mark_attachment(self):
+ fea = self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE '
+ 'PROCESS_MARKS "SomeMarks" \n'
+ "DIRECTION RTL\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [acutecmb gravecmb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " lookupflag RightToLeft MarkAttachmentType"
+ " @SomeMarks;\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_mark_glyph_set(self):
+ fea = self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE '
+ 'PROCESS_MARKS MARK_GLYPH_SET "SomeMarks" \n'
+ "DIRECTION RTL\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [acutecmb gravecmb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " lookupflag RightToLeft UseMarkFilteringSet"
+ " @SomeMarks;\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_process_all_marks(self):
+ fea = self.parse(
+ 'DEF_GROUP "SomeMarks" ENUM GLYPH "acutecmb" GLYPH "gravecmb" '
+ "END_ENUM END_GROUP\n"
+ 'DEF_LOOKUP "SomeSub" PROCESS_BASE '
+ "PROCESS_MARKS ALL \n"
+ "DIRECTION RTL\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "A"\n'
+ 'WITH GLYPH "A.c2sc"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@SomeMarks = [acutecmb gravecmb];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup SomeSub {\n"
+ " lookupflag RightToLeft;\n"
+ " sub A by A.c2sc;\n"
+ "} SomeSub;\n",
+ )
+
+ def test_substitution_no_reversal(self):
+ # TODO: check right context with no reversal
+ fea = self.parse(
+ 'DEF_LOOKUP "Lookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ 'RIGHT ENUM GLYPH "a" GLYPH "b" END_ENUM\n'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "a"\n'
+ 'WITH GLYPH "a.alt"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup Lookup {\n"
+ " sub a' [a b] by a.alt;\n"
+ "} Lookup;\n",
+ )
+
+ def test_substitution_reversal(self):
+ fea = self.parse(
+ 'DEF_GROUP "DFLT_Num_standardFigures"\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'
+ "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'
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GROUP "DFLT_Num_standardFigures"\n'
+ 'WITH GROUP "DFLT_Num_numerators"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@DFLT_Num_standardFigures = [zero one two];\n"
+ "@DFLT_Num_numerators = [zero.numr one.numr two.numr];\n"
+ "\n"
+ "# Lookups\n"
+ "lookup RevLookup {\n"
+ " rsub @DFLT_Num_standardFigures' [a b] by @DFLT_Num_numerators;\n"
+ "} RevLookup;\n",
+ )
+
+ def test_substitution_single_to_multiple(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "ccmp" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "aacute"\n'
+ 'WITH GLYPH "a" GLYPH "acutecomb"\n'
+ "END_SUB\n"
+ 'SUB GLYPH "agrave"\n'
+ 'WITH GLYPH "a" GLYPH "gravecomb"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup ccmp {\n"
+ " sub aacute by a acutecomb;\n"
+ " sub agrave by a gravecomb;\n"
+ "} ccmp;\n",
+ )
+
+ def test_substitution_multiple_to_single(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "liga" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB GLYPH "f" GLYPH "i"\n'
+ 'WITH GLYPH "f_i"\n'
+ "END_SUB\n"
+ 'SUB GLYPH "f" GLYPH "t"\n'
+ 'WITH GLYPH "f_t"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup liga {\n"
+ " sub f i by f_i;\n"
+ " sub f t by f_t;\n"
+ "} liga;\n",
+ )
+
+ def test_substitution_reverse_chaining_single(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "numr" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR REVERSAL\n"
+ "IN_CONTEXT\n"
+ "RIGHT ENUM "
+ 'GLYPH "fraction" '
+ 'RANGE "zero.numr" TO "nine.numr" '
+ "END_ENUM\n"
+ "END_CONTEXT\n"
+ "AS_SUBSTITUTION\n"
+ 'SUB RANGE "zero" TO "nine"\n'
+ 'WITH RANGE "zero.numr" TO "nine.numr"\n'
+ "END_SUB\n"
+ "END_SUBSTITUTION"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup numr {\n"
+ " rsub zero - nine' [fraction zero.numr - nine.numr] by zero.numr - nine.numr;\n"
+ "} numr;\n",
+ )
+
+ # GPOS
+ # ATTACH_CURSIVE
+ # ATTACH
+ # ADJUST_PAIR
+ # ADJUST_SINGLE
+ def test_position_attach(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ 'ATTACH GLYPH "a" GLYPH "e"\n'
+ 'TO GLYPH "acutecomb" AT ANCHOR "top" '
+ 'GLYPH "gravecomb" AT ANCHOR "top"\n'
+ "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"
+ 'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 '
+ "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"
+ 'DEF_ANCHOR "top" ON 35 GLYPH e COMPONENT 1 '
+ "AT POS DX 215 DY 450 END_POS END_ANCHOR\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Mark classes\n"
+ "markClass acutecomb <anchor 0 450> @top;\n"
+ "markClass gravecomb <anchor 0 450> @top;\n"
+ "\n"
+ "# Lookups\n"
+ "lookup anchor_top {\n"
+ " lookupflag RightToLeft;\n"
+ " pos base a\n"
+ " <anchor 210 450> mark @top;\n"
+ " pos base e\n"
+ " <anchor 215 450> mark @top;\n"
+ "} anchor_top;\n",
+ )
+
+ def test_position_attach_mkmk(self):
+ fea = self.parse(
+ 'DEF_GLYPH "brevecomb" ID 1 TYPE MARK END_GLYPH\n'
+ 'DEF_GLYPH "gravecomb" ID 2 TYPE MARK END_GLYPH\n'
+ 'DEF_LOOKUP "anchor_top" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ 'ATTACH GLYPH "gravecomb"\n'
+ 'TO GLYPH "acutecomb" AT ANCHOR "top"\n'
+ "END_ATTACH\n"
+ "END_POSITION\n"
+ 'DEF_ANCHOR "MARK_top" ON 1 GLYPH acutecomb COMPONENT 1 '
+ "AT POS DX 0 DY 450 END_POS END_ANCHOR\n"
+ 'DEF_ANCHOR "top" ON 2 GLYPH gravecomb COMPONENT 1 '
+ "AT POS DX 210 DY 450 END_POS END_ANCHOR\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Mark classes\n"
+ "markClass acutecomb <anchor 0 450> @top;\n"
+ "\n"
+ "# Lookups\n"
+ "lookup anchor_top {\n"
+ " lookupflag RightToLeft;\n"
+ " pos mark gravecomb\n"
+ " <anchor 210 450> mark @top;\n"
+ "} anchor_top;\n"
+ "\n"
+ "@GDEF_mark = [brevecomb gravecomb];\n"
+ "table GDEF {\n"
+ " GlyphClassDef , , @GDEF_mark, ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_position_attach_in_context(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "test" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION RTL\n"
+ 'EXCEPT_CONTEXT LEFT GLYPH "a" END_CONTEXT\n'
+ "AS_POSITION\n"
+ 'ATTACH GLYPH "a"\n'
+ 'TO GLYPH "acutecomb" AT ANCHOR "top" '
+ 'GLYPH "gravecomb" AT ANCHOR "top"\n'
+ "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"
+ 'DEF_ANCHOR "MARK_top" ON 121 GLYPH gravecomb COMPONENT 1 '
+ "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"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Mark classes\n"
+ "markClass acutecomb <anchor 0 450> @top;\n"
+ "markClass gravecomb <anchor 0 450> @top;\n"
+ "\n"
+ "# Lookups\n"
+ "lookup test_target {\n"
+ " pos base a\n"
+ " <anchor 210 450> mark @top;\n"
+ "} test_target;\n"
+ "\n"
+ "lookup test {\n"
+ " lookupflag RightToLeft;\n"
+ " ignore pos a [acutecomb gravecomb]';\n"
+ " pos [acutecomb gravecomb]' lookup test_target;\n"
+ "} test;\n",
+ )
+
+ def test_position_attach_cursive(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "SomeLookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ 'ATTACH_CURSIVE EXIT GLYPH "a" GLYPH "b" '
+ 'ENTER GLYPH "a" GLYPH "c"\n'
+ "END_ATTACH\n"
+ "END_POSITION\n"
+ 'DEF_ANCHOR "exit" ON 1 GLYPH a COMPONENT 1 AT POS END_POS END_ANCHOR\n'
+ 'DEF_ANCHOR "entry" ON 1 GLYPH a COMPONENT 1 AT POS END_POS END_ANCHOR\n'
+ 'DEF_ANCHOR "exit" ON 2 GLYPH b COMPONENT 1 AT POS END_POS END_ANCHOR\n'
+ 'DEF_ANCHOR "entry" ON 3 GLYPH c COMPONENT 1 AT POS END_POS END_ANCHOR\n'
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup SomeLookup {\n"
+ " lookupflag RightToLeft;\n"
+ " pos cursive a <anchor 0 0> <anchor 0 0>;\n"
+ " pos cursive c <anchor 0 0> <anchor NULL>;\n"
+ " pos cursive b <anchor NULL> <anchor 0 0>;\n"
+ "} SomeLookup;\n",
+ )
+
+ def test_position_adjust_pair(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION RTL\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ "ADJUST_PAIR\n"
+ ' FIRST GLYPH "A" FIRST GLYPH "V"\n'
+ ' SECOND GLYPH "A" SECOND GLYPH "V"\n'
+ " 1 2 BY POS ADV -30 END_POS POS END_POS\n"
+ " 2 1 BY POS ADV -25 END_POS POS END_POS\n"
+ "END_ADJUST\n"
+ "END_POSITION\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup kern1 {\n"
+ " lookupflag RightToLeft;\n"
+ " enum pos A V -30;\n"
+ " enum pos V A -25;\n"
+ "} kern1;\n",
+ )
+
+ def test_position_adjust_pair_in_context(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ 'EXCEPT_CONTEXT LEFT GLYPH "A" END_CONTEXT\n'
+ "AS_POSITION\n"
+ "ADJUST_PAIR\n"
+ ' FIRST GLYPH "A" FIRST GLYPH "V"\n'
+ ' SECOND GLYPH "A" SECOND GLYPH "V"\n'
+ " 2 1 BY POS ADV -25 END_POS POS END_POS\n"
+ "END_ADJUST\n"
+ "END_POSITION\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup kern1_target {\n"
+ " enum pos V A -25;\n"
+ "} kern1_target;\n"
+ "\n"
+ "lookup kern1 {\n"
+ " ignore pos A V' A';\n"
+ " pos V' lookup kern1_target A' lookup kern1_target;\n"
+ "} kern1;\n",
+ )
+
+ def test_position_adjust_single(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ "ADJUST_SINGLE"
+ ' GLYPH "glyph1" BY POS ADV 0 DX 123 END_POS\n'
+ ' GLYPH "glyph2" BY POS ADV 0 DX 456 END_POS\n'
+ "END_ADJUST\n"
+ "END_POSITION\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup TestLookup {\n"
+ " pos glyph1 <123 0 0 0>;\n"
+ " pos glyph2 <456 0 0 0>;\n"
+ "} TestLookup;\n",
+ )
+
+ def test_position_adjust_single_in_context(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "EXCEPT_CONTEXT\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 "glyph2" BY POS ADV 0 DX 456 END_POS\n'
+ "END_ADJUST\n"
+ "END_POSITION\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup TestLookup_target {\n"
+ " pos glyph1 <123 0 0 0>;\n"
+ " pos glyph2 <456 0 0 0>;\n"
+ "} TestLookup_target;\n"
+ "\n"
+ "lookup TestLookup {\n"
+ " ignore pos leftGlyph [glyph1 glyph2]' rightGlyph;\n"
+ " pos [glyph1 glyph2]' lookup TestLookup_target;\n"
+ "} TestLookup;\n",
+ )
+
+ def test_def_anchor(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ 'ATTACH GLYPH "a"\n'
+ 'TO GLYPH "acutecomb" AT ANCHOR "top"\n'
+ "END_ATTACH\n"
+ "END_POSITION\n"
+ 'DEF_ANCHOR "top" ON 120 GLYPH a '
+ "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"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Mark classes\n"
+ "markClass acutecomb <anchor 0 450> @top;\n"
+ "\n"
+ "# Lookups\n"
+ "lookup TestLookup {\n"
+ " pos base a\n"
+ " <anchor 250 450> mark @top;\n"
+ "} TestLookup;\n",
+ )
+
+ def test_def_anchor_multi_component(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "TestLookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ 'ATTACH GLYPH "f_f"\n'
+ 'TO GLYPH "acutecomb" AT ANCHOR "top"\n'
+ "END_ATTACH\n"
+ "END_POSITION\n"
+ 'DEF_GLYPH "f_f" ID 120 TYPE LIGATURE COMPONENTS 2 END_GLYPH\n'
+ 'DEF_ANCHOR "top" ON 120 GLYPH f_f '
+ "COMPONENT 1 AT POS DX 250 DY 450 END_POS END_ANCHOR\n"
+ 'DEF_ANCHOR "top" ON 120 GLYPH f_f '
+ "COMPONENT 2 AT POS DX 450 DY 450 END_POS END_ANCHOR\n"
+ 'DEF_ANCHOR "MARK_top" ON 120 GLYPH acutecomb '
+ "COMPONENT 1 AT POS END_POS END_ANCHOR"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Mark classes\n"
+ "markClass acutecomb <anchor 0 0> @top;\n"
+ "\n"
+ "# Lookups\n"
+ "lookup TestLookup {\n"
+ " pos ligature f_f\n"
+ " <anchor 250 450> mark @top\n"
+ " ligComponent\n"
+ " <anchor 450 450> mark @top;\n"
+ "} TestLookup;\n"
+ "\n"
+ "@GDEF_ligature = [f_f];\n"
+ "table GDEF {\n"
+ " GlyphClassDef , @GDEF_ligature, , ;\n"
+ "} GDEF;\n",
+ )
+
+ def test_anchor_adjust_device(self):
+ fea = self.parse(
+ 'DEF_ANCHOR "MARK_top" ON 123 GLYPH diacglyph '
+ "COMPONENT 1 AT POS DX 0 DY 456 ADJUST_BY 12 AT 34 "
+ "ADJUST_BY 56 AT 78 END_POS END_ANCHOR"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Mark classes\n"
+ "#markClass diacglyph <anchor 0 456 <device NULL>"
+ " <device 34 12, 78 56>> @top;",
+ )
+
+ def test_use_extension(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "kern1" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR\n"
+ "IN_CONTEXT\n"
+ "END_CONTEXT\n"
+ "AS_POSITION\n"
+ "ADJUST_PAIR\n"
+ ' FIRST GLYPH "A" FIRST GLYPH "V"\n'
+ ' SECOND GLYPH "A" SECOND GLYPH "V"\n'
+ " 1 2 BY POS ADV -30 END_POS POS END_POS\n"
+ " 2 1 BY POS ADV -25 END_POS POS END_POS\n"
+ "END_ADJUST\n"
+ "END_POSITION\n"
+ "COMPILER_USEEXTENSIONLOOKUPS\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup kern1 useExtension {\n"
+ " enum pos A V -30;\n"
+ " enum pos V A -25;\n"
+ "} kern1;\n",
+ )
+
+ def test_unsupported_compiler_flags(self):
+ with self.assertLogs(level="WARNING") as logs:
+ fea = self.parse("CMAP_FORMAT 0 3 4")
+ self.assertEqual(fea, "")
+ self.assertEqual(
+ logs.output,
+ [
+ "WARNING:fontTools.voltLib.voltToFea:Unsupported setting ignored: CMAP_FORMAT"
+ ],
+ )
+
+ def test_sanitize_lookup_name(self):
+ fea = self.parse(
+ 'DEF_LOOKUP "Test Lookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR IN_CONTEXT END_CONTEXT\n"
+ "AS_POSITION ADJUST_PAIR END_ADJUST END_POSITION\n"
+ 'DEF_LOOKUP "Test-Lookup" PROCESS_BASE PROCESS_MARKS ALL '
+ "DIRECTION LTR IN_CONTEXT END_CONTEXT\n"
+ "AS_POSITION ADJUST_PAIR END_ADJUST END_POSITION\n"
+ )
+ self.assertEqual(
+ fea,
+ "\n# Lookups\n"
+ "lookup Test_Lookup {\n"
+ " \n"
+ "} Test_Lookup;\n"
+ "\n"
+ "lookup Test_Lookup_ {\n"
+ " \n"
+ "} Test_Lookup_;\n",
+ )
+
+ def test_sanitize_group_name(self):
+ fea = self.parse(
+ 'DEF_GROUP "aaccented glyphs"\n'
+ 'ENUM GLYPH "aacute" GLYPH "abreve" END_ENUM\n'
+ "END_GROUP\n"
+ 'DEF_GROUP "aaccented+glyphs"\n'
+ 'ENUM GLYPH "aacute" GLYPH "abreve" END_ENUM\n'
+ "END_GROUP\n"
+ )
+ self.assertEqual(
+ fea,
+ "# Glyph classes\n"
+ "@aaccented_glyphs = [aacute abreve];\n"
+ "@aaccented_glyphs_ = [aacute abreve];",
+ )
+
+ def test_cli_vtp(self):
+ vtp = DATADIR / "Nutso.vtp"
+ fea = DATADIR / "Nutso.fea"
+ self.cli(vtp, fea)
+
+ def test_group_order(self):
+ vtp = DATADIR / "NamdhinggoSIL1006.vtp"
+ fea = DATADIR / "NamdhinggoSIL1006.fea"
+ self.cli(vtp, fea)
+
+ def test_cli_ttf(self):
+ ttf = DATADIR / "Nutso.ttf"
+ fea = DATADIR / "Nutso.fea"
+ self.cli(ttf, fea)
+
+ def test_cli_ttf_no_TSIV(self):
+ from fontTools.voltLib.voltToFea import main as cli
+
+ ttf = DATADIR / "Empty.ttf"
+ temp = self.temp_path()
+ self.assertEqual(1, cli([str(ttf), str(temp)]))
+
+ def cli(self, source, fea):
+ from fontTools.voltLib.voltToFea import main as cli
+
+ temp = self.temp_path()
+ cli([str(source), str(temp)])
+ with temp.open() as f:
+ res = f.read()
+ with fea.open() as f:
+ ref = f.read()
+ self.assertEqual(ref, res)
+
+ def parse(self, text):
+ return VoltToFea(StringIO(text)).convert()
+
+
+if __name__ == "__main__":
+ import sys
+
+ sys.exit(unittest.main())