This tests will fail because of this bug: https://github.com/googlefonts/noto-source/issues/145 The mark records' Class values of the second subtable should be updated to match the new class count (the two split subtable contain half of the original mark classes). Otherwise the base records would implicitly reference the wrong or non-existent mark classes... The fix is in the following commit.
694 lines
26 KiB
Python
694 lines
26 KiB
Python
# coding: utf-8
|
|
from fontTools.misc.py23 import *
|
|
from fontTools.misc.testTools import getXML, parseXML, FakeFont
|
|
from fontTools.misc.textTools import deHexStr, hexStr
|
|
from fontTools.misc.xmlWriter import XMLWriter
|
|
from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
|
|
import fontTools.ttLib.tables.otTables as otTables
|
|
import unittest
|
|
|
|
|
|
def makeCoverage(glyphs):
|
|
coverage = otTables.Coverage()
|
|
coverage.glyphs = glyphs
|
|
return coverage
|
|
|
|
|
|
class SingleSubstTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.glyphs = ".notdef A B C D E a b c d e".split()
|
|
self.font = FakeFont(self.glyphs)
|
|
|
|
def test_postRead_format1(self):
|
|
table = otTables.SingleSubst()
|
|
table.Format = 1
|
|
rawTable = {
|
|
"Coverage": makeCoverage(["A", "B", "C"]),
|
|
"DeltaGlyphID": 5
|
|
}
|
|
table.postRead(rawTable, self.font)
|
|
self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"})
|
|
|
|
def test_postRead_format2(self):
|
|
table = otTables.SingleSubst()
|
|
table.Format = 2
|
|
rawTable = {
|
|
"Coverage": makeCoverage(["A", "B", "C"]),
|
|
"GlyphCount": 3,
|
|
"Substitute": ["c", "b", "a"]
|
|
}
|
|
table.postRead(rawTable, self.font)
|
|
self.assertEqual(table.mapping, {"A": "c", "B": "b", "C": "a"})
|
|
|
|
def test_postRead_formatUnknown(self):
|
|
table = otTables.SingleSubst()
|
|
table.Format = 987
|
|
rawTable = {"Coverage": makeCoverage(["A", "B", "C"])}
|
|
self.assertRaises(AssertionError, table.postRead, rawTable, self.font)
|
|
|
|
def test_preWrite_format1(self):
|
|
table = otTables.SingleSubst()
|
|
table.mapping = {"A": "a", "B": "b", "C": "c"}
|
|
rawTable = table.preWrite(self.font)
|
|
self.assertEqual(table.Format, 1)
|
|
self.assertEqual(rawTable["Coverage"].glyphs, ["A", "B", "C"])
|
|
self.assertEqual(rawTable["DeltaGlyphID"], 5)
|
|
|
|
def test_preWrite_format2(self):
|
|
table = otTables.SingleSubst()
|
|
table.mapping = {"A": "c", "B": "b", "C": "a"}
|
|
rawTable = table.preWrite(self.font)
|
|
self.assertEqual(table.Format, 2)
|
|
self.assertEqual(rawTable["Coverage"].glyphs, ["A", "B", "C"])
|
|
self.assertEqual(rawTable["Substitute"], ["c", "b", "a"])
|
|
|
|
def test_preWrite_emptyMapping(self):
|
|
table = otTables.SingleSubst()
|
|
table.mapping = {}
|
|
rawTable = table.preWrite(self.font)
|
|
self.assertEqual(table.Format, 2)
|
|
self.assertEqual(rawTable["Coverage"].glyphs, [])
|
|
self.assertEqual(rawTable["Substitute"], [])
|
|
|
|
def test_toXML2(self):
|
|
writer = XMLWriter(StringIO())
|
|
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"/>',
|
|
])
|
|
|
|
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"/>'):
|
|
table.fromXML(name, attrs, content, self.font)
|
|
self.assertEqual(table.mapping, {"A": "a", "B": "b", "C": "c"})
|
|
|
|
|
|
class MultipleSubstTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.glyphs = ".notdef c f i t c_t f_f_i".split()
|
|
self.font = FakeFont(self.glyphs)
|
|
|
|
def test_postRead_format1(self):
|
|
makeSequence = otTables.MultipleSubst.makeSequence_
|
|
table = otTables.MultipleSubst()
|
|
table.Format = 1
|
|
rawTable = {
|
|
"Coverage": makeCoverage(["c_t", "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"]
|
|
})
|
|
|
|
def test_postRead_formatUnknown(self):
|
|
table = otTables.MultipleSubst()
|
|
table.Format = 987
|
|
self.assertRaises(AssertionError, table.postRead, {}, self.font)
|
|
|
|
def test_preWrite_format1(self):
|
|
table = otTables.MultipleSubst()
|
|
table.mapping = {"c_t": ["c", "t"], "f_f_i": ["f", "f", "i"]}
|
|
rawTable = table.preWrite(self.font)
|
|
self.assertEqual(table.Format, 1)
|
|
self.assertEqual(rawTable["Coverage"].glyphs, ["c_t", "f_f_i"])
|
|
|
|
def test_toXML2(self):
|
|
writer = XMLWriter(StringIO())
|
|
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"/>',
|
|
])
|
|
|
|
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"/>'):
|
|
table.fromXML(name, attrs, content, self.font)
|
|
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>'):
|
|
table.fromXML(name, attrs, content, self.font)
|
|
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 Format="1">'
|
|
' <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']})
|
|
|
|
|
|
class LigatureSubstTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.glyphs = ".notdef c f i t c_t f_f f_i f_f_i".split()
|
|
self.font = FakeFont(self.glyphs)
|
|
|
|
def makeLigature(self, s):
|
|
"""'ffi' --> Ligature(LigGlyph='f_f_i', Component=['f', 'f', 'i'])"""
|
|
lig = otTables.Ligature()
|
|
lig.Component = list(s)
|
|
lig.LigGlyph = "_".join(lig.Component)
|
|
return lig
|
|
|
|
def makeLigatures(self, s):
|
|
"""'ffi fi' --> [otTables.Ligature, otTables.Ligature]"""
|
|
return [self.makeLigature(lig) for lig in s.split()]
|
|
|
|
def test_postRead_format1(self):
|
|
table = otTables.LigatureSubst()
|
|
table.Format = 1
|
|
ligs_c = otTables.LigatureSet()
|
|
ligs_c.Ligature = self.makeLigatures("ct")
|
|
ligs_f = otTables.LigatureSet()
|
|
ligs_f.Ligature = self.makeLigatures("ffi ff fi")
|
|
rawTable = {
|
|
"Coverage": makeCoverage(["c", "f"]),
|
|
"LigatureSet": [ligs_c, ligs_f]
|
|
}
|
|
table.postRead(rawTable, self.font)
|
|
self.assertEqual(set(table.ligatures.keys()), {"c", "f"})
|
|
self.assertEqual(len(table.ligatures["c"]), 1)
|
|
self.assertEqual(table.ligatures["c"][0].LigGlyph, "c_t")
|
|
self.assertEqual(table.ligatures["c"][0].Component, ["c", "t"])
|
|
self.assertEqual(len(table.ligatures["f"]), 3)
|
|
self.assertEqual(table.ligatures["f"][0].LigGlyph, "f_f_i")
|
|
self.assertEqual(table.ligatures["f"][0].Component, ["f", "f", "i"])
|
|
self.assertEqual(table.ligatures["f"][1].LigGlyph, "f_f")
|
|
self.assertEqual(table.ligatures["f"][1].Component, ["f", "f"])
|
|
self.assertEqual(table.ligatures["f"][2].LigGlyph, "f_i")
|
|
self.assertEqual(table.ligatures["f"][2].Component, ["f", "i"])
|
|
|
|
def test_postRead_formatUnknown(self):
|
|
table = otTables.LigatureSubst()
|
|
table.Format = 987
|
|
rawTable = {"Coverage": makeCoverage(["f"])}
|
|
self.assertRaises(AssertionError, table.postRead, rawTable, self.font)
|
|
|
|
def test_preWrite_format1(self):
|
|
table = otTables.LigatureSubst()
|
|
table.ligatures = {
|
|
"c": self.makeLigatures("ct"),
|
|
"f": self.makeLigatures("ffi ff fi")
|
|
}
|
|
rawTable = table.preWrite(self.font)
|
|
self.assertEqual(table.Format, 1)
|
|
self.assertEqual(rawTable["Coverage"].glyphs, ["c", "f"])
|
|
[c, f] = rawTable["LigatureSet"]
|
|
self.assertIsInstance(c, otTables.LigatureSet)
|
|
self.assertIsInstance(f, otTables.LigatureSet)
|
|
[ct] = c.Ligature
|
|
self.assertIsInstance(ct, otTables.Ligature)
|
|
self.assertEqual(ct.LigGlyph, "c_t")
|
|
self.assertEqual(ct.Component, ["c", "t"])
|
|
[ffi, ff, fi] = f.Ligature
|
|
self.assertIsInstance(ffi, otTables.Ligature)
|
|
self.assertEqual(ffi.LigGlyph, "f_f_i")
|
|
self.assertEqual(ffi.Component, ["f", "f", "i"])
|
|
self.assertIsInstance(ff, otTables.Ligature)
|
|
self.assertEqual(ff.LigGlyph, "f_f")
|
|
self.assertEqual(ff.Component, ["f", "f"])
|
|
self.assertIsInstance(fi, otTables.Ligature)
|
|
self.assertEqual(fi.LigGlyph, "f_i")
|
|
self.assertEqual(fi.Component, ["f", "i"])
|
|
|
|
def test_toXML2(self):
|
|
writer = XMLWriter(StringIO())
|
|
table = otTables.LigatureSubst()
|
|
table.ligatures = {
|
|
"c": self.makeLigatures("ct"),
|
|
"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>'
|
|
])
|
|
|
|
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>'):
|
|
table.fromXML(name, attrs, content, self.font)
|
|
self.assertEqual(set(table.ligatures.keys()), {"f"})
|
|
[ffi, ff] = table.ligatures["f"]
|
|
self.assertEqual(ffi.LigGlyph, "f_f_i")
|
|
self.assertEqual(ffi.Component, ["f", "f", "i"])
|
|
self.assertEqual(ff.LigGlyph, "f_f")
|
|
self.assertEqual(ff.Component, ["f", "f"])
|
|
|
|
|
|
class AlternateSubstTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.glyphs = ".notdef G G.alt1 G.alt2 Z Z.fina".split()
|
|
self.font = FakeFont(self.glyphs)
|
|
|
|
def makeAlternateSet(self, s):
|
|
result = otTables.AlternateSet()
|
|
result.Alternate = s.split()
|
|
return result
|
|
|
|
def test_postRead_format1(self):
|
|
table = otTables.AlternateSubst()
|
|
table.Format = 1
|
|
rawTable = {
|
|
"Coverage": makeCoverage(["G", "Z"]),
|
|
"AlternateSet": [
|
|
self.makeAlternateSet("G.alt2 G.alt1"),
|
|
self.makeAlternateSet("Z.fina")
|
|
]
|
|
}
|
|
table.postRead(rawTable, self.font)
|
|
self.assertEqual(table.alternates, {
|
|
"G": ["G.alt2", "G.alt1"],
|
|
"Z": ["Z.fina"]
|
|
})
|
|
|
|
def test_postRead_formatUnknown(self):
|
|
table = otTables.AlternateSubst()
|
|
table.Format = 987
|
|
self.assertRaises(AssertionError, table.postRead, {}, self.font)
|
|
|
|
def test_preWrite_format1(self):
|
|
table = otTables.AlternateSubst()
|
|
table.alternates = {"G": ["G.alt2", "G.alt1"], "Z": ["Z.fina"]}
|
|
rawTable = table.preWrite(self.font)
|
|
self.assertEqual(table.Format, 1)
|
|
self.assertEqual(rawTable["Coverage"].glyphs, ["G", "Z"])
|
|
[g, z] = rawTable["AlternateSet"]
|
|
self.assertIsInstance(g, otTables.AlternateSet)
|
|
self.assertEqual(g.Alternate, ["G.alt2", "G.alt1"])
|
|
self.assertIsInstance(z, otTables.AlternateSet)
|
|
self.assertEqual(z.Alternate, ["Z.fina"])
|
|
|
|
def test_toXML2(self):
|
|
writer = XMLWriter(StringIO())
|
|
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>'
|
|
])
|
|
|
|
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>'):
|
|
table.fromXML(name, attrs, content, self.font)
|
|
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'])
|
|
|
|
def testCompile(self):
|
|
r = otTables.RearrangementMorphAction()
|
|
r.NewState = 0x1234
|
|
r.MarkFirst = r.DontAdvance = r.MarkLast = True
|
|
r.ReservedFlags, r.Verb = 0x1FF0, 0xD
|
|
writer = OTTableWriter()
|
|
r.compile(writer, self.font, actionIndex=None)
|
|
self.assertEqual(hexStr(writer.getAllData()), "1234fffd")
|
|
|
|
def testCompileActions(self):
|
|
act = otTables.RearrangementMorphAction()
|
|
self.assertEqual(act.compileActions(self.font, []), (None, None))
|
|
|
|
def testDecompileToXML(self):
|
|
r = otTables.RearrangementMorphAction()
|
|
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), [
|
|
'<Transition Test="Foo">',
|
|
' <NewState value="4660"/>', # 0x1234 = 4660
|
|
' <Flags value="MarkFirst,DontAdvance,MarkLast"/>',
|
|
' <ReservedFlags value="0x1FF0"/>',
|
|
' <Verb value="13"/><!-- ABxCD ⇒ CDxBA -->',
|
|
'</Transition>',
|
|
])
|
|
|
|
|
|
class ContextualMorphActionTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.font = FakeFont(['.notdef', 'A', 'B', 'C'])
|
|
|
|
def testCompile(self):
|
|
a = otTables.ContextualMorphAction()
|
|
a.NewState = 0x1234
|
|
a.SetMark, a.DontAdvance, a.ReservedFlags = True, True, 0x3117
|
|
a.MarkIndex, a.CurrentIndex = 0xDEAD, 0xBEEF
|
|
writer = OTTableWriter()
|
|
a.compile(writer, self.font, actionIndex=None)
|
|
self.assertEqual(hexStr(writer.getAllData()), "1234f117deadbeef")
|
|
|
|
def testCompileActions(self):
|
|
act = otTables.ContextualMorphAction()
|
|
self.assertEqual(act.compileActions(self.font, []), (None, None))
|
|
|
|
def testDecompileToXML(self):
|
|
a = otTables.ContextualMorphAction()
|
|
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), [
|
|
'<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>',
|
|
])
|
|
|
|
|
|
class LigatureMorphActionTest(unittest.TestCase):
|
|
def setUp(self):
|
|
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)
|
|
toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition")
|
|
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>',
|
|
])
|
|
|
|
def testCompileActions_empty(self):
|
|
act = otTables.LigatureMorphAction()
|
|
actions, actionIndex = act.compileActions(self.font, [])
|
|
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)}
|
|
ligs = [otTables.LigAction() for _ in range(3)]
|
|
for i, lig in enumerate(ligs):
|
|
lig.GlyphIndexDelta = i
|
|
t[0].Actions = ligs[1:2]
|
|
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,
|
|
})
|
|
|
|
|
|
class InsertionMorphActionTest(unittest.TestCase):
|
|
MORPH_ACTION_XML = [
|
|
'<Transition Test="Foo">',
|
|
' <NewState value="4660"/>', # 0x1234 = 4660
|
|
' <Flags value="SetMark,DontAdvance,CurrentIsKashidaLike,'
|
|
'MarkedIsKashidaLike,CurrentInsertBefore,MarkedInsertBefore"/>',
|
|
' <CurrentInsertionAction glyph="B"/>',
|
|
' <CurrentInsertionAction glyph="C"/>',
|
|
' <MarkedInsertionAction glyph="B"/>',
|
|
' <MarkedInsertionAction glyph="A"/>',
|
|
' <MarkedInsertionAction glyph="D"/>',
|
|
'</Transition>'
|
|
]
|
|
|
|
def setUp(self):
|
|
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)
|
|
toXML = lambda w, f: a.toXML(w, f, {"Test": "Foo"}, "Transition")
|
|
self.assertEqual(getXML(toXML, self.font), self.MORPH_ACTION_XML)
|
|
|
|
def testCompileFromXML(self):
|
|
a = otTables.InsertionMorphAction()
|
|
for name, attrs, content in parseXML(self.MORPH_ACTION_XML):
|
|
a.fromXML(name, attrs, content, self.font)
|
|
writer = OTTableWriter()
|
|
a.compile(writer, self.font,
|
|
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(actionIndex, {})
|
|
|
|
def testCompileActions_shouldShareSubsequences(self):
|
|
state = otTables.AATState()
|
|
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']
|
|
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,
|
|
})
|
|
|
|
|
|
class SplitMultipleSubstTest:
|
|
def overflow(self, itemName, itemRecord):
|
|
from fontTools.otlLib.builder import buildMultipleSubstSubtable
|
|
from fontTools.ttLib.tables.otBase import OverflowErrorRecord
|
|
|
|
oldSubTable = buildMultipleSubstSubtable({'e': 1, 'a': 2, 'b': 3, 'c': 4, 'd': 5})
|
|
oldSubTable.Format = 1
|
|
newSubTable = otTables.MultipleSubst()
|
|
|
|
ok = otTables.splitMultipleSubst(oldSubTable, newSubTable, OverflowErrorRecord((None, None, None, itemName, itemRecord)))
|
|
|
|
assert ok
|
|
assert oldSubTable.Format == newSubTable.Format
|
|
return oldSubTable.mapping, newSubTable.mapping
|
|
|
|
def test_Coverage(self):
|
|
oldMapping, newMapping = self.overflow('Coverage', None)
|
|
assert oldMapping == {'a': 2, 'b': 3}
|
|
assert newMapping == {'c': 4, 'd': 5, 'e': 1}
|
|
|
|
def test_RangeRecord(self):
|
|
oldMapping, newMapping = self.overflow('RangeRecord', None)
|
|
assert oldMapping == {'a': 2, 'b': 3}
|
|
assert newMapping == {'c': 4, 'd': 5, 'e': 1}
|
|
|
|
def test_Sequence(self):
|
|
oldMapping, newMapping = self.overflow('Sequence', 4)
|
|
assert oldMapping == {'a': 2, 'b': 3,'c': 4}
|
|
assert newMapping == {'d': 5, 'e': 1}
|
|
|
|
|
|
def test_splitMarkBasePos():
|
|
from fontTools.otlLib.builder import buildAnchor, buildMarkBasePosSubtable
|
|
|
|
marks = {
|
|
"acutecomb": (0, buildAnchor(0, 600)),
|
|
"gravecomb": (0, buildAnchor(0, 590)),
|
|
"cedillacomb": (1, buildAnchor(0, 0)),
|
|
}
|
|
bases = {
|
|
"a": {
|
|
0: buildAnchor(350, 500),
|
|
1: None,
|
|
},
|
|
"c": {
|
|
0: buildAnchor(300, 700),
|
|
1: buildAnchor(300, 0),
|
|
},
|
|
}
|
|
glyphOrder = ["a", "c", "acutecomb", "gravecomb", "cedillacomb"]
|
|
glyphMap = {g: i for i, g in enumerate(glyphOrder)}
|
|
|
|
oldSubTable = buildMarkBasePosSubtable(marks, bases, glyphMap)
|
|
oldSubTable.MarkCoverage.Format = oldSubTable.BaseCoverage.Format = 1
|
|
newSubTable = otTables.MarkBasePos()
|
|
|
|
ok = otTables.splitMarkBasePos(oldSubTable, newSubTable, overflowRecord=None)
|
|
|
|
assert ok
|
|
|
|
assert getXML(oldSubTable.toXML) == [
|
|
'<MarkBasePos Format="1">',
|
|
' <MarkCoverage Format="1">',
|
|
' <Glyph value="acutecomb"/>',
|
|
' <Glyph value="gravecomb"/>',
|
|
' </MarkCoverage>',
|
|
' <BaseCoverage Format="1">',
|
|
' <Glyph value="a"/>',
|
|
' <Glyph value="c"/>',
|
|
' </BaseCoverage>',
|
|
' <!-- ClassCount=1 -->',
|
|
' <MarkArray>',
|
|
' <!-- MarkCount=2 -->',
|
|
' <MarkRecord index="0">',
|
|
' <Class value="0"/>',
|
|
' <MarkAnchor Format="1">',
|
|
' <XCoordinate value="0"/>',
|
|
' <YCoordinate value="600"/>',
|
|
' </MarkAnchor>',
|
|
' </MarkRecord>',
|
|
' <MarkRecord index="1">',
|
|
' <Class value="0"/>',
|
|
' <MarkAnchor Format="1">',
|
|
' <XCoordinate value="0"/>',
|
|
' <YCoordinate value="590"/>',
|
|
' </MarkAnchor>',
|
|
' </MarkRecord>',
|
|
' </MarkArray>',
|
|
' <BaseArray>',
|
|
' <!-- BaseCount=2 -->',
|
|
' <BaseRecord index="0">',
|
|
' <BaseAnchor index="0" Format="1">',
|
|
' <XCoordinate value="350"/>',
|
|
' <YCoordinate value="500"/>',
|
|
' </BaseAnchor>',
|
|
' </BaseRecord>',
|
|
' <BaseRecord index="1">',
|
|
' <BaseAnchor index="0" Format="1">',
|
|
' <XCoordinate value="300"/>',
|
|
' <YCoordinate value="700"/>',
|
|
' </BaseAnchor>',
|
|
' </BaseRecord>',
|
|
' </BaseArray>',
|
|
'</MarkBasePos>',
|
|
]
|
|
|
|
assert getXML(newSubTable.toXML) == [
|
|
'<MarkBasePos Format="1">',
|
|
' <MarkCoverage Format="1">',
|
|
' <Glyph value="cedillacomb"/>',
|
|
' </MarkCoverage>',
|
|
' <BaseCoverage Format="1">',
|
|
' <Glyph value="a"/>',
|
|
' <Glyph value="c"/>',
|
|
' </BaseCoverage>',
|
|
' <!-- ClassCount=1 -->',
|
|
' <MarkArray>',
|
|
' <!-- MarkCount=1 -->',
|
|
' <MarkRecord index="0">',
|
|
' <Class value="0"/>',
|
|
' <MarkAnchor Format="1">',
|
|
' <XCoordinate value="0"/>',
|
|
' <YCoordinate value="0"/>',
|
|
' </MarkAnchor>',
|
|
' </MarkRecord>',
|
|
' </MarkArray>',
|
|
' <BaseArray>',
|
|
' <!-- BaseCount=2 -->',
|
|
' <BaseRecord index="0">',
|
|
' <BaseAnchor index="0" empty="1"/>',
|
|
' </BaseRecord>',
|
|
' <BaseRecord index="1">',
|
|
' <BaseAnchor index="0" Format="1">',
|
|
' <XCoordinate value="300"/>',
|
|
' <YCoordinate value="0"/>',
|
|
' </BaseAnchor>',
|
|
' </BaseRecord>',
|
|
' </BaseArray>',
|
|
'</MarkBasePos>',
|
|
]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
sys.exit(unittest.main())
|