diff options
Diffstat (limited to 'Tests/otlLib/builder_test.py')
-rw-r--r-- | Tests/otlLib/builder_test.py | 384 |
1 files changed, 368 insertions, 16 deletions
diff --git a/Tests/otlLib/builder_test.py b/Tests/otlLib/builder_test.py index 83f7c828..1c2c324d 100644 --- a/Tests/otlLib/builder_test.py +++ b/Tests/otlLib/builder_test.py @@ -1,7 +1,9 @@ -from __future__ import print_function, division, absolute_import -from __future__ import unicode_literals +import io +import struct +from fontTools.misc.fixedTools import floatToFixed from fontTools.misc.testTools import getXML -from fontTools.otlLib import builder +from fontTools.otlLib import builder, error +from fontTools import ttLib from fontTools.ttLib.tables import otTables import pytest @@ -472,7 +474,7 @@ class BuilderTest(object): assert getXML(lookup.toXML) == [ "<Lookup>", ' <LookupType value="1"/>', - ' <LookupFlag value="7"/>', + ' <LookupFlag value="7"/><!-- rightToLeft ignoreBaseGlyphs ignoreLigatures -->', " <!-- SubTableCount=2 -->", ' <SingleSubst index="0">', ' <Substitution in="one" out="two"/>', @@ -493,14 +495,6 @@ class BuilderTest(object): ), ) as excinfo: builder.buildLookup([s], builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET, None) - with pytest.raises( - AssertionError, - match=( - "if markFilterSet is not None, flags must set " - "LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x0004" - ), - ) as excinfo: - builder.buildLookup([s], builder.LOOKUP_FLAG_IGNORE_LIGATURES, 777) def test_buildLookup_conflictingSubtableTypes(self): s1 = builder.buildSingleSubstSubtable({"one": "two"}) @@ -526,7 +520,7 @@ class BuilderTest(object): assert getXML(lookup.toXML) == [ "<Lookup>", ' <LookupType value="1"/>', - ' <LookupFlag value="17"/>', + ' <LookupFlag value="17"/><!-- rightToLeft useMarkFilteringSet -->', " <!-- SubTableCount=1 -->", ' <SingleSubst index="0">', ' <Substitution in="one" out="two"/>', @@ -845,8 +839,12 @@ class BuilderTest(object): " <!-- Class2Count=3 -->", ' <Class1Record index="0">', ' <Class2Record index="0">', + ' <Value1 XPlacement="0" YPlacement="0"/>', + ' <Value2 XPlacement="0"/>', " </Class2Record>", ' <Class2Record index="1">', + ' <Value1 XPlacement="0" YPlacement="0"/>', + ' <Value2 XPlacement="0"/>', " </Class2Record>", ' <Class2Record index="2">', ' <Value1 XPlacement="-80" YPlacement="-20"/>', @@ -855,12 +853,15 @@ class BuilderTest(object): " </Class1Record>", ' <Class1Record index="1">', ' <Class2Record index="0">', + ' <Value1 XPlacement="0" YPlacement="0"/>', + ' <Value2 XPlacement="0"/>', " </Class2Record>", ' <Class2Record index="1">', + ' <Value1 XPlacement="0" YPlacement="0"/>', ' <Value2 XPlacement="-20"/>', " </Class2Record>", ' <Class2Record index="2">', - " <Value1/>", + ' <Value1 XPlacement="0" YPlacement="0"/>', ' <Value2 XPlacement="-50"/>', " </Class2Record>", " </Class1Record>", @@ -917,9 +918,11 @@ class BuilderTest(object): ("A", "zero"): (d0, d50), ("A", "one"): (None, d20), ("B", "five"): (d8020, d50), + }, self.GLYPHMAP, ) + assert getXML(subtable.toXML) == [ '<PairPos Format="1">', " <Coverage>", @@ -933,10 +936,12 @@ class BuilderTest(object): " <!-- PairValueCount=2 -->", ' <PairValueRecord index="0">', ' <SecondGlyph value="zero"/>', + ' <Value1 XPlacement="0" YPlacement="0"/>', ' <Value2 XPlacement="-50"/>', " </PairValueRecord>", ' <PairValueRecord index="1">', ' <SecondGlyph value="one"/>', + ' <Value1 XPlacement="0" YPlacement="0"/>', ' <Value2 XPlacement="-20"/>', " </PairValueRecord>", " </PairSet>", @@ -1037,8 +1042,8 @@ class BuilderTest(object): " </Coverage>", ' <ValueFormat value="3"/>', " <!-- ValueCount=2 -->", - ' <Value index="0" XPlacement="777"/>', - ' <Value index="1" YPlacement="-888"/>', + ' <Value index="0" XPlacement="777" YPlacement="0"/>', + ' <Value index="1" XPlacement="0" YPlacement="-888"/>', "</SinglePos>", ] @@ -1107,6 +1112,353 @@ class ClassDefBuilderTest(object): assert not b.canAdd({"d", "e", "f"}) assert not b.canAdd({"f"}) + def test_add_exception(self): + b = builder.ClassDefBuilder(useClass0=True) + b.add({"a", "b", "c"}) + with pytest.raises(error.OpenTypeLibError): + b.add({"a", "d"}) + + +buildStatTable_test_data = [ + ([ + dict( + tag="wght", + name="Weight", + values=[ + dict(value=100, name='Thin'), + dict(value=400, name='Regular', flags=0x2), + dict(value=900, name='Black')])], None, "Regular", [ + ' <STAT>', + ' <Version value="0x00010001"/>', + ' <DesignAxisRecordSize value="8"/>', + ' <!-- DesignAxisCount=1 -->', + ' <DesignAxisRecord>', + ' <Axis index="0">', + ' <AxisTag value="wght"/>', + ' <AxisNameID value="257"/> <!-- Weight -->', + ' <AxisOrdering value="0"/>', + ' </Axis>', + ' </DesignAxisRecord>', + ' <!-- AxisValueCount=3 -->', + ' <AxisValueArray>', + ' <AxisValue index="0" Format="1">', + ' <AxisIndex value="0"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="258"/> <!-- Thin -->', + ' <Value value="100.0"/>', + ' </AxisValue>', + ' <AxisValue index="1" Format="1">', + ' <AxisIndex value="0"/>', + ' <Flags value="2"/> <!-- ElidableAxisValueName -->', + ' <ValueNameID value="256"/> <!-- Regular -->', + ' <Value value="400.0"/>', + ' </AxisValue>', + ' <AxisValue index="2" Format="1">', + ' <AxisIndex value="0"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="259"/> <!-- Black -->', + ' <Value value="900.0"/>', + ' </AxisValue>', + ' </AxisValueArray>', + ' <ElidedFallbackNameID value="256"/> <!-- Regular -->', + ' </STAT>']), + ([ + dict( + tag="wght", + name=dict(en="Weight", nl="Gewicht"), + values=[ + dict(value=100, name=dict(en='Thin', nl='Dun')), + dict(value=400, name='Regular', flags=0x2), + dict(value=900, name='Black'), + ]), + dict( + tag="wdth", + name="Width", + values=[ + dict(value=50, name='Condensed'), + dict(value=100, name='Regular', flags=0x2), + dict(value=200, name='Extended')])], None, 2, [ + ' <STAT>', + ' <Version value="0x00010001"/>', + ' <DesignAxisRecordSize value="8"/>', + ' <!-- DesignAxisCount=2 -->', + ' <DesignAxisRecord>', + ' <Axis index="0">', + ' <AxisTag value="wght"/>', + ' <AxisNameID value="256"/> <!-- Weight -->', + ' <AxisOrdering value="0"/>', + ' </Axis>', + ' <Axis index="1">', + ' <AxisTag value="wdth"/>', + ' <AxisNameID value="260"/> <!-- Width -->', + ' <AxisOrdering value="1"/>', + ' </Axis>', + ' </DesignAxisRecord>', + ' <!-- AxisValueCount=6 -->', + ' <AxisValueArray>', + ' <AxisValue index="0" Format="1">', + ' <AxisIndex value="0"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="257"/> <!-- Thin -->', + ' <Value value="100.0"/>', + ' </AxisValue>', + ' <AxisValue index="1" Format="1">', + ' <AxisIndex value="0"/>', + ' <Flags value="2"/> <!-- ElidableAxisValueName -->', + ' <ValueNameID value="258"/> <!-- Regular -->', + ' <Value value="400.0"/>', + ' </AxisValue>', + ' <AxisValue index="2" Format="1">', + ' <AxisIndex value="0"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="259"/> <!-- Black -->', + ' <Value value="900.0"/>', + ' </AxisValue>', + ' <AxisValue index="3" Format="1">', + ' <AxisIndex value="1"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="261"/> <!-- Condensed -->', + ' <Value value="50.0"/>', + ' </AxisValue>', + ' <AxisValue index="4" Format="1">', + ' <AxisIndex value="1"/>', + ' <Flags value="2"/> <!-- ElidableAxisValueName -->', + ' <ValueNameID value="258"/> <!-- Regular -->', + ' <Value value="100.0"/>', + ' </AxisValue>', + ' <AxisValue index="5" Format="1">', + ' <AxisIndex value="1"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="262"/> <!-- Extended -->', + ' <Value value="200.0"/>', + ' </AxisValue>', + ' </AxisValueArray>', + ' <ElidedFallbackNameID value="2"/> <!-- missing from name table -->', + ' </STAT>']), + ([ + dict( + tag="wght", + name="Weight", + values=[ + dict(value=400, name='Regular', flags=0x2), + dict(value=600, linkedValue=650, name='Bold')])], None, 18, [ + ' <STAT>', + ' <Version value="0x00010001"/>', + ' <DesignAxisRecordSize value="8"/>', + ' <!-- DesignAxisCount=1 -->', + ' <DesignAxisRecord>', + ' <Axis index="0">', + ' <AxisTag value="wght"/>', + ' <AxisNameID value="256"/> <!-- Weight -->', + ' <AxisOrdering value="0"/>', + ' </Axis>', + ' </DesignAxisRecord>', + ' <!-- AxisValueCount=2 -->', + ' <AxisValueArray>', + ' <AxisValue index="0" Format="1">', + ' <AxisIndex value="0"/>', + ' <Flags value="2"/> <!-- ElidableAxisValueName -->', + ' <ValueNameID value="257"/> <!-- Regular -->', + ' <Value value="400.0"/>', + ' </AxisValue>', + ' <AxisValue index="1" Format="3">', + ' <AxisIndex value="0"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="258"/> <!-- Bold -->', + ' <Value value="600.0"/>', + ' <LinkedValue value="650.0"/>', + ' </AxisValue>', + ' </AxisValueArray>', + ' <ElidedFallbackNameID value="18"/> <!-- missing from name table -->', + ' </STAT>']), + ([ + dict( + tag="opsz", + name="Optical Size", + values=[ + dict(nominalValue=6, rangeMaxValue=10, name='Small'), + dict(rangeMinValue=10, nominalValue=14, rangeMaxValue=24, name='Text', flags=0x2), + dict(rangeMinValue=24, nominalValue=600, name='Display')])], None, 2, [ + ' <STAT>', + ' <Version value="0x00010001"/>', + ' <DesignAxisRecordSize value="8"/>', + ' <!-- DesignAxisCount=1 -->', + ' <DesignAxisRecord>', + ' <Axis index="0">', + ' <AxisTag value="opsz"/>', + ' <AxisNameID value="256"/> <!-- Optical Size -->', + ' <AxisOrdering value="0"/>', + ' </Axis>', + ' </DesignAxisRecord>', + ' <!-- AxisValueCount=3 -->', + ' <AxisValueArray>', + ' <AxisValue index="0" Format="2">', + ' <AxisIndex value="0"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="257"/> <!-- Small -->', + ' <NominalValue value="6.0"/>', + ' <RangeMinValue value="-32768.0"/>', + ' <RangeMaxValue value="10.0"/>', + ' </AxisValue>', + ' <AxisValue index="1" Format="2">', + ' <AxisIndex value="0"/>', + ' <Flags value="2"/> <!-- ElidableAxisValueName -->', + ' <ValueNameID value="258"/> <!-- Text -->', + ' <NominalValue value="14.0"/>', + ' <RangeMinValue value="10.0"/>', + ' <RangeMaxValue value="24.0"/>', + ' </AxisValue>', + ' <AxisValue index="2" Format="2">', + ' <AxisIndex value="0"/>', + ' <Flags value="0"/>', + ' <ValueNameID value="259"/> <!-- Display -->', + ' <NominalValue value="600.0"/>', + ' <RangeMinValue value="24.0"/>', + ' <RangeMaxValue value="32767.99998"/>', + ' </AxisValue>', + ' </AxisValueArray>', + ' <ElidedFallbackNameID value="2"/> <!-- missing from name table -->', + ' </STAT>']), + ([ + dict( + tag="wght", + name="Weight", + ordering=1, + values=[]), + dict( + tag="ABCD", + name="ABCDTest", + ordering=0, + values=[ + dict(value=100, name="Regular", flags=0x2)])], + [dict(location=dict(wght=300, ABCD=100), name='Regular ABCD')], 18, [ + ' <STAT>', + ' <Version value="0x00010002"/>', + ' <DesignAxisRecordSize value="8"/>', + ' <!-- DesignAxisCount=2 -->', + ' <DesignAxisRecord>', + ' <Axis index="0">', + ' <AxisTag value="wght"/>', + ' <AxisNameID value="256"/> <!-- Weight -->', + ' <AxisOrdering value="1"/>', + ' </Axis>', + ' <Axis index="1">', + ' <AxisTag value="ABCD"/>', + ' <AxisNameID value="257"/> <!-- ABCDTest -->', + ' <AxisOrdering value="0"/>', + ' </Axis>', + ' </DesignAxisRecord>', + ' <!-- AxisValueCount=2 -->', + ' <AxisValueArray>', + ' <AxisValue index="0" Format="4">', + ' <!-- AxisCount=2 -->', + ' <Flags value="0"/>', + ' <ValueNameID value="259"/> <!-- Regular ABCD -->', + ' <AxisValueRecord index="0">', + ' <AxisIndex value="0"/>', + ' <Value value="300.0"/>', + ' </AxisValueRecord>', + ' <AxisValueRecord index="1">', + ' <AxisIndex value="1"/>', + ' <Value value="100.0"/>', + ' </AxisValueRecord>', + ' </AxisValue>', + ' <AxisValue index="1" Format="1">', + ' <AxisIndex value="1"/>', + ' <Flags value="2"/> <!-- ElidableAxisValueName -->', + ' <ValueNameID value="258"/> <!-- Regular -->', + ' <Value value="100.0"/>', + ' </AxisValue>', + ' </AxisValueArray>', + ' <ElidedFallbackNameID value="18"/> <!-- missing from name table -->', + ' </STAT>']), +] + + +@pytest.mark.parametrize("axes, axisValues, elidedFallbackName, expected_ttx", buildStatTable_test_data) +def test_buildStatTable(axes, axisValues, elidedFallbackName, expected_ttx): + font = ttLib.TTFont() + font["name"] = ttLib.newTable("name") + font["name"].names = [] + # https://github.com/fonttools/fonttools/issues/1985 + # Add nameID < 256 that matches a test axis name, to test whether + # the nameID is not reused: AxisNameIDs must be > 255 according + # to the spec. + font["name"].addMultilingualName(dict(en="ABCDTest"), nameID=6) + builder.buildStatTable(font, axes, axisValues, elidedFallbackName) + f = io.StringIO() + font.saveXML(f, tables=["STAT"]) + ttx = f.getvalue().splitlines() + ttx = ttx[3:-2] # strip XML header and <ttFont> element + assert expected_ttx == ttx + # Compile and round-trip + f = io.BytesIO() + font.save(f) + font = ttLib.TTFont(f) + f = io.StringIO() + font.saveXML(f, tables=["STAT"]) + ttx = f.getvalue().splitlines() + ttx = ttx[3:-2] # strip XML header and <ttFont> element + assert expected_ttx == ttx + + +def test_stat_infinities(): + negInf = floatToFixed(builder.AXIS_VALUE_NEGATIVE_INFINITY, 16) + assert struct.pack(">l", negInf) == b"\x80\x00\x00\x00" + posInf = floatToFixed(builder.AXIS_VALUE_POSITIVE_INFINITY, 16) + assert struct.pack(">l", posInf) == b"\x7f\xff\xff\xff" + + +class ChainContextualRulesetTest(object): + def test_makeRulesets(self): + font = ttLib.TTFont() + font.setGlyphOrder(["a","b","c","d","A","B","C","D","E"]) + sb = builder.ChainContextSubstBuilder(font, None) + prefix, input_, suffix, lookups = [["a"], ["b"]], [["c"]], [], [None] + sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups)) + + prefix, input_, suffix, lookups = [["a"], ["d"]], [["c"]], [], [None] + sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups)) + + sb.add_subtable_break(None) + + # Second subtable has some glyph classes + prefix, input_, suffix, lookups = [["A"]], [["E"]], [], [None] + sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups)) + prefix, input_, suffix, lookups = [["A"]], [["C","D"]], [], [None] + sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups)) + prefix, input_, suffix, lookups = [["A", "B"]], [["E"]], [], [None] + sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups)) + + sb.add_subtable_break(None) + + # Third subtable has no pre/post context + prefix, input_, suffix, lookups = [], [["E"]], [], [None] + sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups)) + prefix, input_, suffix, lookups = [], [["C","D"]], [], [None] + sb.rules.append(builder.ChainContextualRule(prefix, input_, suffix, lookups)) + + rulesets = sb.rulesets() + assert len(rulesets) == 3 + assert rulesets[0].hasPrefixOrSuffix + assert not rulesets[0].hasAnyGlyphClasses + cd = rulesets[0].format2ClassDefs() + assert set(cd[0].classes()[1:]) == set([("d",),("b",),("a",)]) + assert set(cd[1].classes()[1:]) == set([("c",)]) + assert set(cd[2].classes()[1:]) == set() + + assert rulesets[1].hasPrefixOrSuffix + assert rulesets[1].hasAnyGlyphClasses + assert not rulesets[1].format2ClassDefs() + + assert not rulesets[2].hasPrefixOrSuffix + assert rulesets[2].hasAnyGlyphClasses + assert rulesets[2].format2ClassDefs() + cd = rulesets[2].format2ClassDefs() + assert set(cd[0].classes()[1:]) == set() + assert set(cd[1].classes()[1:]) == set([("C","D"), ("E",)]) + assert set(cd[2].classes()[1:]) == set() + if __name__ == "__main__": import sys |