blob: f39e27c2eeef17b17145335f7b1ef5b5dd8d73a4 [file] [log] [blame]
# coding: utf-8
from __future__ import print_function, division, absolute_import, unicode_literals
from fontTools.misc.py23 import *
from fontTools.misc.testTools import getXML, parseXML, FakeFont
from fontTools.misc.textTools import deHexStr, hexStr
from fontTools.misc.xmlWriter import XMLWriter
from fontTools.ttLib.tables.otBase import OTTableReader, OTTableWriter
import fontTools.ttLib.tables.otTables as otTables
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,
})
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 oldSubTable.Format == newSubTable.Format
assert oldSubTable.MarkCoverage.glyphs == [
"acutecomb", "gravecomb"
]
assert newSubTable.MarkCoverage.glyphs == ["cedillacomb"]
assert newSubTable.MarkCoverage.Format == 1
assert oldSubTable.BaseCoverage.glyphs == newSubTable.BaseCoverage.glyphs
assert newSubTable.BaseCoverage.Format == 1
assert oldSubTable.ClassCount == newSubTable.ClassCount == 1
assert oldSubTable.MarkArray.MarkCount == 2
assert newSubTable.MarkArray.MarkCount == 1
assert oldSubTable.BaseArray.BaseCount == newSubTable.BaseArray.BaseCount
assert newSubTable.BaseArray.BaseRecord[0].BaseAnchor[0] is None
assert newSubTable.BaseArray.BaseRecord[1].BaseAnchor[0] == buildAnchor(300, 0)
if __name__ == "__main__":
import sys
sys.exit(unittest.main())