You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1467 lines
56 KiB

import io
import struct
from fontTools.misc.fixedTools import floatToFixed
from fontTools.misc.testTools import getXML
from fontTools.otlLib import builder, error
from fontTools import ttLib
from fontTools.ttLib.tables import otTables
import pytest
class BuilderTest(object):
GLYPHS = (
".notdef space zero one two three four five six "
"A B C a b c grave acute cedilla f_f_i f_i c_t"
).split()
GLYPHMAP = {name: num for num, name in enumerate(GLYPHS)}
ANCHOR1 = builder.buildAnchor(11, -11)
ANCHOR2 = builder.buildAnchor(22, -22)
ANCHOR3 = builder.buildAnchor(33, -33)
def test_buildAnchor_format1(self):
anchor = builder.buildAnchor(23, 42)
assert getXML(anchor.toXML) == [
'<Anchor Format="1">',
' <XCoordinate value="23"/>',
' <YCoordinate value="42"/>',
"</Anchor>",
]
def test_buildAnchor_format2(self):
anchor = builder.buildAnchor(23, 42, point=17)
assert getXML(anchor.toXML) == [
'<Anchor Format="2">',
' <XCoordinate value="23"/>',
' <YCoordinate value="42"/>',
' <AnchorPoint value="17"/>',
"</Anchor>",
]
def test_buildAnchor_format3(self):
anchor = builder.buildAnchor(
23,
42,
deviceX=builder.buildDevice({1: 1, 0: 0}),
deviceY=builder.buildDevice({7: 7}),
)
assert getXML(anchor.toXML) == [
'<Anchor Format="3">',
' <XCoordinate value="23"/>',
' <YCoordinate value="42"/>',
" <XDeviceTable>",
' <StartSize value="0"/>',
' <EndSize value="1"/>',
' <DeltaFormat value="1"/>',
' <DeltaValue value="[0, 1]"/>',
" </XDeviceTable>",
" <YDeviceTable>",
' <StartSize value="7"/>',
' <EndSize value="7"/>',
' <DeltaFormat value="2"/>',
' <DeltaValue value="[7]"/>',
" </YDeviceTable>",
"</Anchor>",
]
def test_buildAttachList(self):
attachList = builder.buildAttachList(
{"zero": [23, 7], "one": [1]}, self.GLYPHMAP
)
assert getXML(attachList.toXML) == [
"<AttachList>",
" <Coverage>",
' <Glyph value="zero"/>',
' <Glyph value="one"/>',
" </Coverage>",
" <!-- GlyphCount=2 -->",
' <AttachPoint index="0">',
" <!-- PointCount=2 -->",
' <PointIndex index="0" value="7"/>',
' <PointIndex index="1" value="23"/>',
" </AttachPoint>",
' <AttachPoint index="1">',
" <!-- PointCount=1 -->",
' <PointIndex index="0" value="1"/>',
" </AttachPoint>",
"</AttachList>",
]
def test_buildAttachList_empty(self):
assert builder.buildAttachList({}, self.GLYPHMAP) is None
def test_buildAttachPoint(self):
attachPoint = builder.buildAttachPoint([7, 3])
assert getXML(attachPoint.toXML) == [
"<AttachPoint>",
" <!-- PointCount=2 -->",
' <PointIndex index="0" value="3"/>',
' <PointIndex index="1" value="7"/>',
"</AttachPoint>",
]
def test_buildAttachPoint_empty(self):
assert builder.buildAttachPoint([]) is None
def test_buildAttachPoint_duplicate(self):
attachPoint = builder.buildAttachPoint([7, 3, 7])
assert getXML(attachPoint.toXML) == [
"<AttachPoint>",
" <!-- PointCount=2 -->",
' <PointIndex index="0" value="3"/>',
' <PointIndex index="1" value="7"/>',
"</AttachPoint>",
]
def test_buildBaseArray(self):
anchor = builder.buildAnchor
baseArray = builder.buildBaseArray(
{"a": {2: anchor(300, 80)}, "c": {1: anchor(300, 80), 2: anchor(300, -20)}},
numMarkClasses=4,
glyphMap=self.GLYPHMAP,
)
assert getXML(baseArray.toXML) == [
"<BaseArray>",
" <!-- BaseCount=2 -->",
' <BaseRecord index="0">',
' <BaseAnchor index="0" empty="1"/>',
' <BaseAnchor index="1" empty="1"/>',
' <BaseAnchor index="2" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="80"/>',
" </BaseAnchor>",
' <BaseAnchor index="3" empty="1"/>',
" </BaseRecord>",
' <BaseRecord index="1">',
' <BaseAnchor index="0" empty="1"/>',
' <BaseAnchor index="1" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="80"/>',
" </BaseAnchor>",
' <BaseAnchor index="2" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-20"/>',
" </BaseAnchor>",
' <BaseAnchor index="3" empty="1"/>',
" </BaseRecord>",
"</BaseArray>",
]
def test_buildBaseRecord(self):
a = builder.buildAnchor
rec = builder.buildBaseRecord([a(500, -20), None, a(300, -15)])
assert getXML(rec.toXML) == [
"<BaseRecord>",
' <BaseAnchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-20"/>',
" </BaseAnchor>",
' <BaseAnchor index="1" empty="1"/>',
' <BaseAnchor index="2" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-15"/>',
" </BaseAnchor>",
"</BaseRecord>",
]
def test_buildCaretValueForCoord(self):
caret = builder.buildCaretValueForCoord(500)
assert getXML(caret.toXML) == [
'<CaretValue Format="1">',
' <Coordinate value="500"/>',
"</CaretValue>",
]
def test_buildCaretValueForPoint(self):
caret = builder.buildCaretValueForPoint(23)
assert getXML(caret.toXML) == [
'<CaretValue Format="2">',
' <CaretValuePoint value="23"/>',
"</CaretValue>",
]
def test_buildComponentRecord(self):
a = builder.buildAnchor
rec = builder.buildComponentRecord([a(500, -20), None, a(300, -15)])
assert getXML(rec.toXML) == [
"<ComponentRecord>",
' <LigatureAnchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-20"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="1" empty="1"/>',
' <LigatureAnchor index="2" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-15"/>',
" </LigatureAnchor>",
"</ComponentRecord>",
]
def test_buildComponentRecord_empty(self):
assert builder.buildComponentRecord([]) is None
def test_buildComponentRecord_None(self):
assert builder.buildComponentRecord(None) is None
def test_buildCoverage(self):
cov = builder.buildCoverage({"two", "four"}, {"two": 2, "four": 4})
assert getXML(cov.toXML) == [
"<Coverage>",
' <Glyph value="two"/>',
' <Glyph value="four"/>',
"</Coverage>",
]
def test_buildCursivePos(self):
pos = builder.buildCursivePosSubtable(
{"two": (self.ANCHOR1, self.ANCHOR2), "four": (self.ANCHOR3, self.ANCHOR1)},
self.GLYPHMAP,
)
assert getXML(pos.toXML) == [
'<CursivePos Format="1">',
" <Coverage>",
' <Glyph value="two"/>',
' <Glyph value="four"/>',
" </Coverage>",
" <!-- EntryExitCount=2 -->",
' <EntryExitRecord index="0">',
' <EntryAnchor Format="1">',
' <XCoordinate value="11"/>',
' <YCoordinate value="-11"/>',
" </EntryAnchor>",
' <ExitAnchor Format="1">',
' <XCoordinate value="22"/>',
' <YCoordinate value="-22"/>',
" </ExitAnchor>",
" </EntryExitRecord>",
' <EntryExitRecord index="1">',
' <EntryAnchor Format="1">',
' <XCoordinate value="33"/>',
' <YCoordinate value="-33"/>',
" </EntryAnchor>",
' <ExitAnchor Format="1">',
' <XCoordinate value="11"/>',
' <YCoordinate value="-11"/>',
" </ExitAnchor>",
" </EntryExitRecord>",
"</CursivePos>",
]
def test_buildDevice_format1(self):
device = builder.buildDevice({1: 1, 0: 0})
assert getXML(device.toXML) == [
"<Device>",
' <StartSize value="0"/>',
' <EndSize value="1"/>',
' <DeltaFormat value="1"/>',
' <DeltaValue value="[0, 1]"/>',
"</Device>",
]
def test_buildDevice_format2(self):
device = builder.buildDevice({2: 2, 0: 1, 1: 0})
assert getXML(device.toXML) == [
"<Device>",
' <StartSize value="0"/>',
' <EndSize value="2"/>',
' <DeltaFormat value="2"/>',
' <DeltaValue value="[1, 0, 2]"/>',
"</Device>",
]
def test_buildDevice_format3(self):
device = builder.buildDevice({5: 3, 1: 77})
assert getXML(device.toXML) == [
"<Device>",
' <StartSize value="1"/>',
' <EndSize value="5"/>',
' <DeltaFormat value="3"/>',
' <DeltaValue value="[77, 0, 0, 0, 3]"/>',
"</Device>",
]
def test_buildLigatureArray(self):
anchor = builder.buildAnchor
ligatureArray = builder.buildLigatureArray(
{
"f_i": [{2: anchor(300, -20)}, {}],
"c_t": [{}, {1: anchor(500, 350), 2: anchor(1300, -20)}],
},
numMarkClasses=4,
glyphMap=self.GLYPHMAP,
)
assert getXML(ligatureArray.toXML) == [
"<LigatureArray>",
" <!-- LigatureCount=2 -->",
' <LigatureAttach index="0">', # f_i
" <!-- ComponentCount=2 -->",
' <ComponentRecord index="0">',
' <LigatureAnchor index="0" empty="1"/>',
' <LigatureAnchor index="1" empty="1"/>',
' <LigatureAnchor index="2" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-20"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="3" empty="1"/>',
" </ComponentRecord>",
' <ComponentRecord index="1">',
' <LigatureAnchor index="0" empty="1"/>',
' <LigatureAnchor index="1" empty="1"/>',
' <LigatureAnchor index="2" empty="1"/>',
' <LigatureAnchor index="3" empty="1"/>',
" </ComponentRecord>",
" </LigatureAttach>",
' <LigatureAttach index="1">',
" <!-- ComponentCount=2 -->",
' <ComponentRecord index="0">',
' <LigatureAnchor index="0" empty="1"/>',
' <LigatureAnchor index="1" empty="1"/>',
' <LigatureAnchor index="2" empty="1"/>',
' <LigatureAnchor index="3" empty="1"/>',
" </ComponentRecord>",
' <ComponentRecord index="1">',
' <LigatureAnchor index="0" empty="1"/>',
' <LigatureAnchor index="1" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="350"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="2" Format="1">',
' <XCoordinate value="1300"/>',
' <YCoordinate value="-20"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="3" empty="1"/>',
" </ComponentRecord>",
" </LigatureAttach>",
"</LigatureArray>",
]
def test_buildLigatureAttach(self):
anchor = builder.buildAnchor
attach = builder.buildLigatureAttach(
[[anchor(500, -10), None], [None, anchor(300, -20), None]]
)
assert getXML(attach.toXML) == [
"<LigatureAttach>",
" <!-- ComponentCount=2 -->",
' <ComponentRecord index="0">',
' <LigatureAnchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-10"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="1" empty="1"/>',
" </ComponentRecord>",
' <ComponentRecord index="1">',
' <LigatureAnchor index="0" empty="1"/>',
' <LigatureAnchor index="1" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-20"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="2" empty="1"/>',
" </ComponentRecord>",
"</LigatureAttach>",
]
def test_buildLigatureAttach_emptyComponents(self):
attach = builder.buildLigatureAttach([[], None])
assert getXML(attach.toXML) == [
"<LigatureAttach>",
" <!-- ComponentCount=2 -->",
' <ComponentRecord index="0" empty="1"/>',
' <ComponentRecord index="1" empty="1"/>',
"</LigatureAttach>",
]
def test_buildLigatureAttach_noComponents(self):
attach = builder.buildLigatureAttach([])
assert getXML(attach.toXML) == [
"<LigatureAttach>",
" <!-- ComponentCount=0 -->",
"</LigatureAttach>",
]
def test_buildLigCaretList(self):
carets = builder.buildLigCaretList(
{"f_f_i": [300, 600]}, {"c_t": [42]}, self.GLYPHMAP
)
assert getXML(carets.toXML) == [
"<LigCaretList>",
" <Coverage>",
' <Glyph value="f_f_i"/>',
' <Glyph value="c_t"/>',
" </Coverage>",
" <!-- LigGlyphCount=2 -->",
' <LigGlyph index="0">',
" <!-- CaretCount=2 -->",
' <CaretValue index="0" Format="1">',
' <Coordinate value="300"/>',
" </CaretValue>",
' <CaretValue index="1" Format="1">',
' <Coordinate value="600"/>',
" </CaretValue>",
" </LigGlyph>",
' <LigGlyph index="1">',
" <!-- CaretCount=1 -->",
' <CaretValue index="0" Format="2">',
' <CaretValuePoint value="42"/>',
" </CaretValue>",
" </LigGlyph>",
"</LigCaretList>",
]
def test_buildLigCaretList_bothCoordsAndPointsForSameGlyph(self):
carets = builder.buildLigCaretList(
{"f_f_i": [300]}, {"f_f_i": [7]}, self.GLYPHMAP
)
assert getXML(carets.toXML) == [
"<LigCaretList>",
" <Coverage>",
' <Glyph value="f_f_i"/>',
" </Coverage>",
" <!-- LigGlyphCount=1 -->",
' <LigGlyph index="0">',
" <!-- CaretCount=2 -->",
' <CaretValue index="0" Format="1">',
' <Coordinate value="300"/>',
" </CaretValue>",
' <CaretValue index="1" Format="2">',
' <CaretValuePoint value="7"/>',
" </CaretValue>",
" </LigGlyph>",
"</LigCaretList>",
]
def test_buildLigCaretList_empty(self):
assert builder.buildLigCaretList({}, {}, self.GLYPHMAP) is None
def test_buildLigCaretList_None(self):
assert builder.buildLigCaretList(None, None, self.GLYPHMAP) is None
def test_buildLigGlyph_coords(self):
lig = builder.buildLigGlyph([500, 800], None)
assert getXML(lig.toXML) == [
"<LigGlyph>",
" <!-- CaretCount=2 -->",
' <CaretValue index="0" Format="1">',
' <Coordinate value="500"/>',
" </CaretValue>",
' <CaretValue index="1" Format="1">',
' <Coordinate value="800"/>',
" </CaretValue>",
"</LigGlyph>",
]
def test_buildLigGlyph_empty(self):
assert builder.buildLigGlyph([], []) is None
def test_buildLigGlyph_None(self):
assert builder.buildLigGlyph(None, None) is None
def test_buildLigGlyph_points(self):
lig = builder.buildLigGlyph(None, [2])
assert getXML(lig.toXML) == [
"<LigGlyph>",
" <!-- CaretCount=1 -->",
' <CaretValue index="0" Format="2">',
' <CaretValuePoint value="2"/>',
" </CaretValue>",
"</LigGlyph>",
]
def test_buildLookup(self):
s1 = builder.buildSingleSubstSubtable({"one": "two"})
s2 = builder.buildSingleSubstSubtable({"three": "four"})
lookup = builder.buildLookup([s1, s2], flags=7)
assert getXML(lookup.toXML) == [
"<Lookup>",
' <LookupType value="1"/>',
' <LookupFlag value="7"/><!-- rightToLeft ignoreBaseGlyphs ignoreLigatures -->',
" <!-- SubTableCount=2 -->",
' <SingleSubst index="0">',
' <Substitution in="one" out="two"/>',
" </SingleSubst>",
' <SingleSubst index="1">',
' <Substitution in="three" out="four"/>',
" </SingleSubst>",
"</Lookup>",
]
def test_buildLookup_badFlags(self):
s = builder.buildSingleSubstSubtable({"one": "two"})
with pytest.raises(
AssertionError,
match=(
"if markFilterSet is None, flags must not set "
"LOOKUP_FLAG_USE_MARK_FILTERING_SET; flags=0x0010"
),
) as excinfo:
builder.buildLookup([s], builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET, None)
def test_buildLookup_conflictingSubtableTypes(self):
s1 = builder.buildSingleSubstSubtable({"one": "two"})
s2 = builder.buildAlternateSubstSubtable({"one": ["two", "three"]})
with pytest.raises(
AssertionError, match="all subtables must have the same LookupType"
) as excinfo:
builder.buildLookup([s1, s2])
def test_buildLookup_noSubtables(self):
assert builder.buildLookup([]) is None
assert builder.buildLookup(None) is None
assert builder.buildLookup([None]) is None
assert builder.buildLookup([None, None]) is None
def test_buildLookup_markFilterSet(self):
s = builder.buildSingleSubstSubtable({"one": "two"})
flags = (
builder.LOOKUP_FLAG_RIGHT_TO_LEFT
| builder.LOOKUP_FLAG_USE_MARK_FILTERING_SET
)
lookup = builder.buildLookup([s], flags, markFilterSet=999)
assert getXML(lookup.toXML) == [
"<Lookup>",
' <LookupType value="1"/>',
' <LookupFlag value="17"/><!-- rightToLeft useMarkFilteringSet -->',
" <!-- SubTableCount=1 -->",
' <SingleSubst index="0">',
' <Substitution in="one" out="two"/>',
" </SingleSubst>",
' <MarkFilteringSet value="999"/>',
"</Lookup>",
]
def test_buildMarkArray(self):
markArray = builder.buildMarkArray(
{
"acute": (7, builder.buildAnchor(300, 800)),
"grave": (2, builder.buildAnchor(10, 80)),
},
self.GLYPHMAP,
)
assert self.GLYPHMAP["grave"] < self.GLYPHMAP["acute"]
assert getXML(markArray.toXML) == [
"<MarkArray>",
" <!-- MarkCount=2 -->",
' <MarkRecord index="0">',
' <Class value="2"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="10"/>',
' <YCoordinate value="80"/>',
" </MarkAnchor>",
" </MarkRecord>",
' <MarkRecord index="1">',
' <Class value="7"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="800"/>',
" </MarkAnchor>",
" </MarkRecord>",
"</MarkArray>",
]
def test_buildMarkBasePosSubtable(self):
anchor = builder.buildAnchor
marks = {
"acute": (0, anchor(300, 700)),
"cedilla": (1, anchor(300, -100)),
"grave": (0, anchor(300, 700)),
}
bases = {
# Make sure we can handle missing entries.
"A": {}, # no entry for any markClass
"B": {0: anchor(500, 900)}, # only markClass 0 specified
"C": {1: anchor(500, -10)}, # only markClass 1 specified
"a": {0: anchor(500, 400), 1: anchor(500, -20)},
"b": {0: anchor(500, 800), 1: anchor(500, -20)},
}
table = builder.buildMarkBasePosSubtable(marks, bases, self.GLYPHMAP)
assert getXML(table.toXML) == [
'<MarkBasePos Format="1">',
" <MarkCoverage>",
' <Glyph value="grave"/>',
' <Glyph value="acute"/>',
' <Glyph value="cedilla"/>',
" </MarkCoverage>",
" <BaseCoverage>",
' <Glyph value="A"/>',
' <Glyph value="B"/>',
' <Glyph value="C"/>',
' <Glyph value="a"/>',
' <Glyph value="b"/>',
" </BaseCoverage>",
" <!-- ClassCount=2 -->",
" <MarkArray>",
" <!-- MarkCount=3 -->",
' <MarkRecord index="0">', # grave
' <Class value="0"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="700"/>',
" </MarkAnchor>",
" </MarkRecord>",
' <MarkRecord index="1">', # acute
' <Class value="0"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="700"/>',
" </MarkAnchor>",
" </MarkRecord>",
' <MarkRecord index="2">', # cedilla
' <Class value="1"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-100"/>',
" </MarkAnchor>",
" </MarkRecord>",
" </MarkArray>",
" <BaseArray>",
" <!-- BaseCount=5 -->",
' <BaseRecord index="0">', # A
' <BaseAnchor index="0" empty="1"/>',
' <BaseAnchor index="1" empty="1"/>',
" </BaseRecord>",
' <BaseRecord index="1">', # B
' <BaseAnchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="900"/>',
" </BaseAnchor>",
' <BaseAnchor index="1" empty="1"/>',
" </BaseRecord>",
' <BaseRecord index="2">', # C
' <BaseAnchor index="0" empty="1"/>',
' <BaseAnchor index="1" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-10"/>',
" </BaseAnchor>",
" </BaseRecord>",
' <BaseRecord index="3">', # a
' <BaseAnchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="400"/>',
" </BaseAnchor>",
' <BaseAnchor index="1" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-20"/>',
" </BaseAnchor>",
" </BaseRecord>",
' <BaseRecord index="4">', # b
' <BaseAnchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="800"/>',
" </BaseAnchor>",
' <BaseAnchor index="1" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-20"/>',
" </BaseAnchor>",
" </BaseRecord>",
" </BaseArray>",
"</MarkBasePos>",
]
def test_buildMarkGlyphSetsDef(self):
marksets = builder.buildMarkGlyphSetsDef(
[{"acute", "grave"}, {"cedilla", "grave"}], self.GLYPHMAP
)
assert getXML(marksets.toXML) == [
"<MarkGlyphSetsDef>",
' <MarkSetTableFormat value="1"/>',
" <!-- MarkSetCount=2 -->",
' <Coverage index="0">',
' <Glyph value="grave"/>',
' <Glyph value="acute"/>',
" </Coverage>",
' <Coverage index="1">',
' <Glyph value="grave"/>',
' <Glyph value="cedilla"/>',
" </Coverage>",
"</MarkGlyphSetsDef>",
]
def test_buildMarkGlyphSetsDef_empty(self):
assert builder.buildMarkGlyphSetsDef([], self.GLYPHMAP) is None
def test_buildMarkGlyphSetsDef_None(self):
assert builder.buildMarkGlyphSetsDef(None, self.GLYPHMAP) is None
def test_buildMarkLigPosSubtable(self):
anchor = builder.buildAnchor
marks = {
"acute": (0, anchor(300, 700)),
"cedilla": (1, anchor(300, -100)),
"grave": (0, anchor(300, 700)),
}
bases = {
"f_i": [{}, {0: anchor(200, 400)}], # nothing on f; only 1 on i
"c_t": [
{0: anchor(500, 600), 1: anchor(500, -20)}, # c
{0: anchor(1300, 800), 1: anchor(1300, -20)}, # t
],
}
table = builder.buildMarkLigPosSubtable(marks, bases, self.GLYPHMAP)
assert getXML(table.toXML) == [
'<MarkLigPos Format="1">',
" <MarkCoverage>",
' <Glyph value="grave"/>',
' <Glyph value="acute"/>',
' <Glyph value="cedilla"/>',
" </MarkCoverage>",
" <LigatureCoverage>",
' <Glyph value="f_i"/>',
' <Glyph value="c_t"/>',
" </LigatureCoverage>",
" <!-- ClassCount=2 -->",
" <MarkArray>",
" <!-- MarkCount=3 -->",
' <MarkRecord index="0">',
' <Class value="0"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="700"/>',
" </MarkAnchor>",
" </MarkRecord>",
' <MarkRecord index="1">',
' <Class value="0"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="700"/>',
" </MarkAnchor>",
" </MarkRecord>",
' <MarkRecord index="2">',
' <Class value="1"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-100"/>',
" </MarkAnchor>",
" </MarkRecord>",
" </MarkArray>",
" <LigatureArray>",
" <!-- LigatureCount=2 -->",
' <LigatureAttach index="0">',
" <!-- ComponentCount=2 -->",
' <ComponentRecord index="0">',
' <LigatureAnchor index="0" empty="1"/>',
' <LigatureAnchor index="1" empty="1"/>',
" </ComponentRecord>",
' <ComponentRecord index="1">',
' <LigatureAnchor index="0" Format="1">',
' <XCoordinate value="200"/>',
' <YCoordinate value="400"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="1" empty="1"/>',
" </ComponentRecord>",
" </LigatureAttach>",
' <LigatureAttach index="1">',
" <!-- ComponentCount=2 -->",
' <ComponentRecord index="0">',
' <LigatureAnchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="600"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="1" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-20"/>',
" </LigatureAnchor>",
" </ComponentRecord>",
' <ComponentRecord index="1">',
' <LigatureAnchor index="0" Format="1">',
' <XCoordinate value="1300"/>',
' <YCoordinate value="800"/>',
" </LigatureAnchor>",
' <LigatureAnchor index="1" Format="1">',
' <XCoordinate value="1300"/>',
' <YCoordinate value="-20"/>',
" </LigatureAnchor>",
" </ComponentRecord>",
" </LigatureAttach>",
" </LigatureArray>",
"</MarkLigPos>",
]
def test_buildMarkRecord(self):
rec = builder.buildMarkRecord(17, builder.buildAnchor(500, -20))
assert getXML(rec.toXML) == [
"<MarkRecord>",
' <Class value="17"/>',
' <MarkAnchor Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-20"/>',
" </MarkAnchor>",
"</MarkRecord>",
]
def test_buildMark2Record(self):
a = builder.buildAnchor
rec = builder.buildMark2Record([a(500, -20), None, a(300, -15)])
assert getXML(rec.toXML) == [
"<Mark2Record>",
' <Mark2Anchor index="0" Format="1">',
' <XCoordinate value="500"/>',
' <YCoordinate value="-20"/>',
" </Mark2Anchor>",
' <Mark2Anchor index="1" empty="1"/>',
' <Mark2Anchor index="2" Format="1">',
' <XCoordinate value="300"/>',
' <YCoordinate value="-15"/>',
" </Mark2Anchor>",
"</Mark2Record>",
]
def test_buildPairPosClassesSubtable(self):
d20 = builder.buildValue({"XPlacement": -20})
d50 = builder.buildValue({"XPlacement": -50})
d0 = builder.buildValue({})
d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
subtable = builder.buildPairPosClassesSubtable(
{
(tuple("A"), tuple(["zero"])): (d0, d50),
(tuple("A"), tuple(["one", "two"])): (None, d20),
(tuple(["B", "C"]), tuple(["zero"])): (d8020, d50),
},
self.GLYPHMAP,
)
assert getXML(subtable.toXML) == [
'<PairPos Format="2">',
" <Coverage>",
' <Glyph value="A"/>',
' <Glyph value="B"/>',
' <Glyph value="C"/>',
" </Coverage>",
' <ValueFormat1 value="3"/>',
' <ValueFormat2 value="1"/>',
" <ClassDef1>",
' <ClassDef glyph="A" class="1"/>',
" </ClassDef1>",
" <ClassDef2>",
' <ClassDef glyph="one" class="1"/>',
' <ClassDef glyph="two" class="1"/>',
' <ClassDef glyph="zero" class="2"/>',
" </ClassDef2>",
" <!-- Class1Count=2 -->",
" <!-- 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"/>',
' <Value2 XPlacement="-50"/>',
" </Class2Record>",
" </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 XPlacement="0" YPlacement="0"/>',
' <Value2 XPlacement="-50"/>',
" </Class2Record>",
" </Class1Record>",
"</PairPos>",
]
def test_buildPairPosGlyphs(self):
d50 = builder.buildValue({"XPlacement": -50})
d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
subtables = builder.buildPairPosGlyphs(
{("A", "zero"): (None, d50), ("A", "one"): (d8020, d50)}, self.GLYPHMAP
)
assert sum([getXML(t.toXML) for t in subtables], []) == [
'<PairPos Format="1">',
" <Coverage>",
' <Glyph value="A"/>',
" </Coverage>",
' <ValueFormat1 value="0"/>',
' <ValueFormat2 value="1"/>',
" <!-- PairSetCount=1 -->",
' <PairSet index="0">',
" <!-- PairValueCount=1 -->",
' <PairValueRecord index="0">',
' <SecondGlyph value="zero"/>',
' <Value2 XPlacement="-50"/>',
" </PairValueRecord>",
" </PairSet>",
"</PairPos>",
'<PairPos Format="1">',
" <Coverage>",
' <Glyph value="A"/>',
" </Coverage>",
' <ValueFormat1 value="3"/>',
' <ValueFormat2 value="1"/>',
" <!-- PairSetCount=1 -->",
' <PairSet index="0">',
" <!-- PairValueCount=1 -->",
' <PairValueRecord index="0">',
' <SecondGlyph value="one"/>',
' <Value1 XPlacement="-80" YPlacement="-20"/>',
' <Value2 XPlacement="-50"/>',
" </PairValueRecord>",
" </PairSet>",
"</PairPos>",
]
def test_buildPairPosGlyphsSubtable(self):
d20 = builder.buildValue({"XPlacement": -20})
d50 = builder.buildValue({"XPlacement": -50})
d0 = builder.buildValue({})
d8020 = builder.buildValue({"XPlacement": -80, "YPlacement": -20})
subtable = builder.buildPairPosGlyphsSubtable(
{
("A", "zero"): (d0, d50),
("A", "one"): (None, d20),
("B", "five"): (d8020, d50),
},
self.GLYPHMAP,
)
assert getXML(subtable.toXML) == [
'<PairPos Format="1">',
" <Coverage>",
' <Glyph value="A"/>',
' <Glyph value="B"/>',
" </Coverage>",
' <ValueFormat1 value="3"/>',
' <ValueFormat2 value="1"/>',
" <!-- PairSetCount=2 -->",
' <PairSet index="0">',
" <!-- 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>",
' <PairSet index="1">',
" <!-- PairValueCount=1 -->",
' <PairValueRecord index="0">',
' <SecondGlyph value="five"/>',
' <Value1 XPlacement="-80" YPlacement="-20"/>',
' <Value2 XPlacement="-50"/>',
" </PairValueRecord>",
" </PairSet>",
"</PairPos>",
]
def test_buildSinglePos(self):
subtables = builder.buildSinglePos(
{
"one": builder.buildValue({"XPlacement": 500}),
"two": builder.buildValue({"XPlacement": 500}),
"three": builder.buildValue({"XPlacement": 200}),
"four": builder.buildValue({"XPlacement": 400}),
"five": builder.buildValue({"XPlacement": 500}),
"six": builder.buildValue({"YPlacement": -6}),
},
self.GLYPHMAP,
)
assert sum([getXML(t.toXML) for t in subtables], []) == [
'<SinglePos Format="2">',
" <Coverage>",
' <Glyph value="one"/>',
' <Glyph value="two"/>',
' <Glyph value="three"/>',
' <Glyph value="four"/>',
' <Glyph value="five"/>',
" </Coverage>",
' <ValueFormat value="1"/>',
" <!-- ValueCount=5 -->",
' <Value index="0" XPlacement="500"/>',
' <Value index="1" XPlacement="500"/>',
' <Value index="2" XPlacement="200"/>',
' <Value index="3" XPlacement="400"/>',
' <Value index="4" XPlacement="500"/>',
"</SinglePos>",
'<SinglePos Format="1">',
" <Coverage>",
' <Glyph value="six"/>',
" </Coverage>",
' <ValueFormat value="2"/>',
' <Value YPlacement="-6"/>',
"</SinglePos>",
]
def test_buildSinglePos_ValueFormat0(self):
subtables = builder.buildSinglePos(
{"zero": builder.buildValue({})}, self.GLYPHMAP
)
assert sum([getXML(t.toXML) for t in subtables], []) == [
'<SinglePos Format="1">',
" <Coverage>",
' <Glyph value="zero"/>',
" </Coverage>",
' <ValueFormat value="0"/>',
"</SinglePos>",
]
def test_buildSinglePosSubtable_format1(self):
subtable = builder.buildSinglePosSubtable(
{
"one": builder.buildValue({"XPlacement": 777}),
"two": builder.buildValue({"XPlacement": 777}),
},
self.GLYPHMAP,
)
assert getXML(subtable.toXML) == [
'<SinglePos Format="1">',
" <Coverage>",
' <Glyph value="one"/>',
' <Glyph value="two"/>',
" </Coverage>",
' <ValueFormat value="1"/>',
' <Value XPlacement="777"/>',
"</SinglePos>",
]
def test_buildSinglePosSubtable_format2(self):
subtable = builder.buildSinglePosSubtable(
{
"one": builder.buildValue({"XPlacement": 777}),
"two": builder.buildValue({"YPlacement": -888}),
},
self.GLYPHMAP,
)
assert getXML(subtable.toXML) == [
'<SinglePos Format="2">',
" <Coverage>",
' <Glyph value="one"/>',
' <Glyph value="two"/>',
" </Coverage>",
' <ValueFormat value="3"/>',
" <!-- ValueCount=2 -->",
' <Value index="0" XPlacement="777" YPlacement="0"/>',
' <Value index="1" XPlacement="0" YPlacement="-888"/>',
"</SinglePos>",
]
def test_buildValue(self):
value = builder.buildValue({"XPlacement": 7, "YPlacement": 23})
func = lambda writer, font: value.toXML(writer, font, valueName="Val")
assert getXML(func) == ['<Val XPlacement="7" YPlacement="23"/>']
def test_getLigatureKey(self):
components = lambda s: [tuple(word) for word in s.split()]
c = components("fi fl ff ffi fff")
c.sort(key=builder._getLigatureKey)
assert c == components("fff ffi ff fi fl")
def test_getSinglePosValueKey(self):
device = builder.buildDevice({10: 1, 11: 3})
a1 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device})
a2 = builder.buildValue({"XPlacement": 500, "XPlaDevice": device})
b = builder.buildValue({"XPlacement": 500})
keyA1 = builder._getSinglePosValueKey(a1)
keyA2 = builder._getSinglePosValueKey(a1)
keyB = builder._getSinglePosValueKey(b)
assert keyA1 == keyA2
assert hash(keyA1) == hash(keyA2)
assert keyA1 != keyB
assert hash(keyA1) != hash(keyB)
class ClassDefBuilderTest(object):
def test_build_usingClass0(self):
b = builder.ClassDefBuilder(useClass0=True)
b.add({"aa", "bb"})
b.add({"a", "b"})
b.add({"c"})
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}
def test_build_notUsingClass0(self):
b = builder.ClassDefBuilder(useClass0=False)
b.add({"a", "b"})
b.add({"c"})
b.add({"e", "f", "g", "h"})
cdef = b.build()
assert isinstance(cdef, otTables.ClassDef)
assert cdef.classDefs == {
"a": 2,
"b": 2,
"c": 3,
"e": 1,
"f": 1,
"g": 1,
"h": 1,
}
def test_canAdd(self):
b = builder.ClassDefBuilder(useClass0=True)
b.add({"a", "b", "c", "d"})
b.add({"e", "f"})
assert b.canAdd({"a", "b", "c", "d"})
assert b.canAdd({"e", "f"})
assert b.canAdd({"g", "h", "i"})
assert not b.canAdd({"b", "c", "d"})
assert not b.canAdd({"a", "b", "c", "d", "e", "f"})
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
sys.exit(pytest.main(sys.argv))