* Replaced all from ...py23 import * with explicit name imports, or removed completely when possible. * Replaced tounicode() with tostr() * Changed all BytesIO ans StringIO imports to from io import ..., replaced all UnicodeIO with StringIO. * Replaced all unichr() with chr() * Misc minor tweaks and fixes
420 lines
13 KiB
Python
420 lines
13 KiB
Python
from fontTools.ttLib import newTable
|
|
from fontTools.ttLib.tables._k_e_r_n import (
|
|
KernTable_format_0, KernTable_format_unkown)
|
|
from fontTools.misc.textTools import deHexStr
|
|
from fontTools.misc.testTools import FakeFont, getXML, parseXML
|
|
import itertools
|
|
import pytest
|
|
|
|
|
|
KERN_VER_0_FMT_0_DATA = deHexStr(
|
|
'0000 ' # 0: version=0
|
|
'0001 ' # 2: nTables=1
|
|
'0000 ' # 4: version=0 (bogus field, unused)
|
|
'0020 ' # 6: length=32
|
|
'00 ' # 8: format=0
|
|
'01 ' # 9: coverage=1
|
|
'0003 ' # 10: nPairs=3
|
|
'000C ' # 12: searchRange=12
|
|
'0001 ' # 14: entrySelector=1
|
|
'0006 ' # 16: rangeShift=6
|
|
'0004 000C FFD8 ' # 18: l=4, r=12, v=-40
|
|
'0004 001C 0028 ' # 24: l=4, r=28, v=40
|
|
'0005 0028 FFCE ' # 30: l=5, r=40, v=-50
|
|
)
|
|
assert len(KERN_VER_0_FMT_0_DATA) == 36
|
|
|
|
KERN_VER_0_FMT_0_XML = [
|
|
'<version value="0"/>',
|
|
'<kernsubtable coverage="1" format="0">',
|
|
' <pair l="E" r="M" v="-40"/>',
|
|
' <pair l="E" r="c" v="40"/>',
|
|
' <pair l="F" r="o" v="-50"/>',
|
|
'</kernsubtable>',
|
|
]
|
|
|
|
KERN_VER_1_FMT_0_DATA = deHexStr(
|
|
'0001 0000 ' # 0: version=1
|
|
'0000 0001 ' # 4: nTables=1
|
|
'0000 0022 ' # 8: length=34
|
|
'00 ' # 12: coverage=0
|
|
'00 ' # 13: format=0
|
|
'0000 ' # 14: tupleIndex=0
|
|
'0003 ' # 16: nPairs=3
|
|
'000C ' # 18: searchRange=12
|
|
'0001 ' # 20: entrySelector=1
|
|
'0006 ' # 22: rangeShift=6
|
|
'0004 000C FFD8 ' # 24: l=4, r=12, v=-40
|
|
'0004 001C 0028 ' # 30: l=4, r=28, v=40
|
|
'0005 0028 FFCE ' # 36: l=5, r=40, v=-50
|
|
)
|
|
assert len(KERN_VER_1_FMT_0_DATA) == 42
|
|
|
|
KERN_VER_1_FMT_0_XML = [
|
|
'<version value="1.0"/>',
|
|
'<kernsubtable coverage="0" format="0" tupleIndex="0">',
|
|
' <pair l="E" r="M" v="-40"/>',
|
|
' <pair l="E" r="c" v="40"/>',
|
|
' <pair l="F" r="o" v="-50"/>',
|
|
'</kernsubtable>',
|
|
]
|
|
|
|
KERN_VER_0_FMT_UNKNOWN_DATA = deHexStr(
|
|
'0000 ' # 0: version=0
|
|
'0002 ' # 2: nTables=2
|
|
'0000 ' # 4: version=0
|
|
'000A ' # 6: length=10
|
|
'04 ' # 8: format=4 (format 4 doesn't exist)
|
|
'01 ' # 9: coverage=1
|
|
'1234 5678 ' # 10: garbage...
|
|
'0000 ' # 14: version=0
|
|
'000A ' # 16: length=10
|
|
'05 ' # 18: format=5 (format 5 doesn't exist)
|
|
'01 ' # 19: coverage=1
|
|
'9ABC DEF0 ' # 20: garbage...
|
|
)
|
|
assert len(KERN_VER_0_FMT_UNKNOWN_DATA) == 24
|
|
|
|
KERN_VER_0_FMT_UNKNOWN_XML = [
|
|
'<version value="0"/>',
|
|
'<kernsubtable format="4">',
|
|
" <!-- unknown 'kern' subtable format -->",
|
|
' 0000000A 04011234',
|
|
' 5678 ',
|
|
'</kernsubtable>',
|
|
'<kernsubtable format="5">',
|
|
"<!-- unknown 'kern' subtable format -->",
|
|
' 0000000A 05019ABC',
|
|
' DEF0 ',
|
|
'</kernsubtable>',
|
|
]
|
|
|
|
KERN_VER_1_FMT_UNKNOWN_DATA = deHexStr(
|
|
'0001 0000 ' # 0: version=1
|
|
'0000 0002 ' # 4: nTables=2
|
|
'0000 000C ' # 8: length=12
|
|
'00 ' # 12: coverage=0
|
|
'04 ' # 13: format=4 (format 4 doesn't exist)
|
|
'0000 ' # 14: tupleIndex=0
|
|
'1234 5678' # 16: garbage...
|
|
'0000 000C ' # 20: length=12
|
|
'00 ' # 24: coverage=0
|
|
'05 ' # 25: format=5 (format 5 doesn't exist)
|
|
'0000 ' # 26: tupleIndex=0
|
|
'9ABC DEF0 ' # 28: garbage...
|
|
)
|
|
assert len(KERN_VER_1_FMT_UNKNOWN_DATA) == 32
|
|
|
|
KERN_VER_1_FMT_UNKNOWN_XML = [
|
|
'<version value="1"/>',
|
|
'<kernsubtable format="4">',
|
|
" <!-- unknown 'kern' subtable format -->",
|
|
' 0000000C 00040000',
|
|
' 12345678 ',
|
|
'</kernsubtable>',
|
|
'<kernsubtable format="5">',
|
|
" <!-- unknown 'kern' subtable format -->",
|
|
' 0000000C 00050000',
|
|
' 9ABCDEF0 ',
|
|
'</kernsubtable>',
|
|
]
|
|
|
|
KERN_VER_0_FMT_0_OVERFLOWING_DATA = deHexStr(
|
|
'0000 ' # 0: version=0
|
|
'0001 ' # 2: nTables=1
|
|
'0000 ' # 4: version=0 (bogus field, unused)
|
|
'0274 ' # 6: length=628 (bogus value for 66164 % 0x10000)
|
|
'00 ' # 8: format=0
|
|
'01 ' # 9: coverage=1
|
|
'2B11 ' # 10: nPairs=11025
|
|
'C000 ' # 12: searchRange=49152
|
|
'000D ' # 14: entrySelector=13
|
|
'4266 ' # 16: rangeShift=16998
|
|
) + deHexStr(' '.join(
|
|
'%04X %04X %04X' % (a, b, 0)
|
|
for (a, b) in itertools.product(range(105), repeat=2)
|
|
))
|
|
|
|
|
|
@pytest.fixture
|
|
def font():
|
|
return FakeFont(list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz"))
|
|
|
|
@pytest.fixture
|
|
def overflowing_font():
|
|
return FakeFont(["glyph%i" % i for i in range(105)])
|
|
|
|
|
|
class KernTableTest(object):
|
|
|
|
@pytest.mark.parametrize(
|
|
"data, version",
|
|
[
|
|
(KERN_VER_0_FMT_0_DATA, 0),
|
|
(KERN_VER_1_FMT_0_DATA, 1.0),
|
|
],
|
|
ids=["version_0", "version_1"]
|
|
)
|
|
def test_decompile_single_format_0(self, data, font, version):
|
|
kern = newTable("kern")
|
|
kern.decompile(data, font)
|
|
|
|
assert kern.version == version
|
|
assert len(kern.kernTables) == 1
|
|
|
|
st = kern.kernTables[0]
|
|
assert st.apple is (version == 1.0)
|
|
assert st.format == 0
|
|
# horizontal kerning in OT kern is coverage 0x01, while in
|
|
# AAT kern it's the default (0)
|
|
assert st.coverage == (0 if st.apple else 1)
|
|
assert st.tupleIndex == (0 if st.apple else None)
|
|
assert len(st.kernTable) == 3
|
|
assert st.kernTable == {
|
|
('E', 'M'): -40,
|
|
('E', 'c'): 40,
|
|
('F', 'o'): -50
|
|
}
|
|
|
|
@pytest.mark.parametrize(
|
|
"version, expected",
|
|
[
|
|
(0, KERN_VER_0_FMT_0_DATA),
|
|
(1.0, KERN_VER_1_FMT_0_DATA),
|
|
],
|
|
ids=["version_0", "version_1"]
|
|
)
|
|
def test_compile_single_format_0(self, font, version, expected):
|
|
kern = newTable("kern")
|
|
kern.version = version
|
|
apple = version == 1.0
|
|
st = KernTable_format_0(apple)
|
|
kern.kernTables = [st]
|
|
st.coverage = (0 if apple else 1)
|
|
st.tupleIndex = 0 if apple else None
|
|
st.kernTable = {
|
|
('E', 'M'): -40,
|
|
('E', 'c'): 40,
|
|
('F', 'o'): -50
|
|
}
|
|
data = kern.compile(font)
|
|
assert data == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"xml, version",
|
|
[
|
|
(KERN_VER_0_FMT_0_XML, 0),
|
|
(KERN_VER_1_FMT_0_XML, 1.0),
|
|
],
|
|
ids=["version_0", "version_1"]
|
|
)
|
|
def test_fromXML_single_format_0(self, xml, font, version):
|
|
kern = newTable("kern")
|
|
for name, attrs, content in parseXML(xml):
|
|
kern.fromXML(name, attrs, content, ttFont=font)
|
|
|
|
assert kern.version == version
|
|
assert len(kern.kernTables) == 1
|
|
|
|
st = kern.kernTables[0]
|
|
assert st.apple is (version == 1.0)
|
|
assert st.format == 0
|
|
assert st.coverage == (0 if st.apple else 1)
|
|
assert st.tupleIndex == (0 if st.apple else None)
|
|
assert len(st.kernTable) == 3
|
|
assert st.kernTable == {
|
|
('E', 'M'): -40,
|
|
('E', 'c'): 40,
|
|
('F', 'o'): -50
|
|
}
|
|
|
|
@pytest.mark.parametrize(
|
|
"version, expected",
|
|
[
|
|
(0, KERN_VER_0_FMT_0_XML),
|
|
(1.0, KERN_VER_1_FMT_0_XML),
|
|
],
|
|
ids=["version_0", "version_1"]
|
|
)
|
|
def test_toXML_single_format_0(self, font, version, expected):
|
|
kern = newTable("kern")
|
|
kern.version = version
|
|
apple = version == 1.0
|
|
st = KernTable_format_0(apple)
|
|
kern.kernTables = [st]
|
|
st.coverage = 0 if apple else 1
|
|
st.tupleIndex = 0 if apple else None
|
|
st.kernTable = {
|
|
('E', 'M'): -40,
|
|
('E', 'c'): 40,
|
|
('F', 'o'): -50
|
|
}
|
|
xml = getXML(kern.toXML, font)
|
|
assert xml == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"data, version, header_length, st_length",
|
|
[
|
|
(KERN_VER_0_FMT_UNKNOWN_DATA, 0, 4, 10),
|
|
(KERN_VER_1_FMT_UNKNOWN_DATA, 1.0, 8, 12),
|
|
],
|
|
ids=["version_0", "version_1"]
|
|
)
|
|
def test_decompile_format_unknown(
|
|
self, data, font, version, header_length, st_length):
|
|
kern = newTable("kern")
|
|
kern.decompile(data, font)
|
|
|
|
assert kern.version == version
|
|
assert len(kern.kernTables) == 2
|
|
|
|
st_data = data[header_length:]
|
|
st0 = kern.kernTables[0]
|
|
assert st0.format == 4
|
|
assert st0.data == st_data[:st_length]
|
|
st_data = st_data[st_length:]
|
|
|
|
st1 = kern.kernTables[1]
|
|
assert st1.format == 5
|
|
assert st1.data == st_data[:st_length]
|
|
|
|
@pytest.mark.parametrize(
|
|
"version, st_length, expected",
|
|
[
|
|
(0, 10, KERN_VER_0_FMT_UNKNOWN_DATA),
|
|
(1.0, 12, KERN_VER_1_FMT_UNKNOWN_DATA),
|
|
],
|
|
ids=["version_0", "version_1"]
|
|
)
|
|
def test_compile_format_unknown(self, version, st_length, expected):
|
|
kern = newTable("kern")
|
|
kern.version = version
|
|
kern.kernTables = []
|
|
|
|
for unknown_fmt, kern_data in zip((4, 5), ("1234 5678", "9ABC DEF0")):
|
|
if version > 0:
|
|
coverage = 0
|
|
header_fmt = deHexStr(
|
|
"%08X %02X %02X %04X" % (
|
|
st_length, coverage, unknown_fmt, 0))
|
|
else:
|
|
coverage = 1
|
|
header_fmt = deHexStr(
|
|
"%04X %04X %02X %02X" % (
|
|
0, st_length, unknown_fmt, coverage))
|
|
st = KernTable_format_unkown(unknown_fmt)
|
|
st.data = header_fmt + deHexStr(kern_data)
|
|
kern.kernTables.append(st)
|
|
|
|
data = kern.compile(font)
|
|
assert data == expected
|
|
|
|
@pytest.mark.parametrize(
|
|
"xml, version, st_length",
|
|
[
|
|
(KERN_VER_0_FMT_UNKNOWN_XML, 0, 10),
|
|
(KERN_VER_1_FMT_UNKNOWN_XML, 1.0, 12),
|
|
],
|
|
ids=["version_0", "version_1"]
|
|
)
|
|
def test_fromXML_format_unknown(self, xml, font, version, st_length):
|
|
kern = newTable("kern")
|
|
for name, attrs, content in parseXML(xml):
|
|
kern.fromXML(name, attrs, content, ttFont=font)
|
|
|
|
assert kern.version == version
|
|
assert len(kern.kernTables) == 2
|
|
|
|
st0 = kern.kernTables[0]
|
|
assert st0.format == 4
|
|
assert len(st0.data) == st_length
|
|
|
|
st1 = kern.kernTables[1]
|
|
assert st1.format == 5
|
|
assert len(st1.data) == st_length
|
|
|
|
@pytest.mark.parametrize(
|
|
"version", [0, 1.0], ids=["version_0", "version_1"])
|
|
def test_toXML_format_unknown(self, font, version):
|
|
kern = newTable("kern")
|
|
kern.version = version
|
|
st = KernTable_format_unkown(4)
|
|
st.data = b"ABCD"
|
|
kern.kernTables = [st]
|
|
|
|
xml = getXML(kern.toXML, font)
|
|
|
|
assert xml == [
|
|
'<version value="%s"/>' % version,
|
|
'<kernsubtable format="4">',
|
|
' <!-- unknown \'kern\' subtable format -->',
|
|
' 41424344 ',
|
|
'</kernsubtable>',
|
|
]
|
|
|
|
def test_getkern(self):
|
|
table = newTable("kern")
|
|
table.version = 0
|
|
table.kernTables = []
|
|
|
|
assert table.getkern(0) is None
|
|
|
|
st0 = KernTable_format_0()
|
|
table.kernTables.append(st0)
|
|
|
|
assert table.getkern(0) is st0
|
|
assert table.getkern(4) is None
|
|
|
|
st1 = KernTable_format_unkown(4)
|
|
table.kernTables.append(st1)
|
|
|
|
|
|
class KernTable_format_0_Test(object):
|
|
|
|
def test_decompileBadGlyphId(self, font):
|
|
subtable = KernTable_format_0()
|
|
subtable.decompile(
|
|
b'\x00' + b'\x00' + b'\x00' + b'\x1a' + b'\x00' + b'\x00' +
|
|
b'\x00' + b'\x02' + b'\x00' * 6 +
|
|
b'\x00' + b'\x01' + b'\x00' + b'\x03' + b'\x00' + b'\x01' +
|
|
b'\x00' + b'\x01' + b'\xFF' + b'\xFF' + b'\x00' + b'\x02',
|
|
font)
|
|
assert subtable[("B", "D")] == 1
|
|
assert subtable[("B", "glyph65535")] == 2
|
|
|
|
def test_compileOverflowingSubtable(self, overflowing_font):
|
|
font = overflowing_font
|
|
kern = newTable("kern")
|
|
kern.version = 0
|
|
st = KernTable_format_0(0)
|
|
kern.kernTables = [st]
|
|
st.coverage = 1
|
|
st.tupleIndex = None
|
|
st.kernTable = {
|
|
(a, b): 0
|
|
for (a, b) in itertools.product(
|
|
font.getGlyphOrder(), repeat=2)
|
|
}
|
|
assert len(st.kernTable) == 11025
|
|
data = kern.compile(font)
|
|
assert data == KERN_VER_0_FMT_0_OVERFLOWING_DATA
|
|
|
|
def test_decompileOverflowingSubtable(self, overflowing_font):
|
|
font = overflowing_font
|
|
data = KERN_VER_0_FMT_0_OVERFLOWING_DATA
|
|
kern = newTable("kern")
|
|
kern.decompile(data, font)
|
|
|
|
st = kern.kernTables[0]
|
|
assert st.kernTable == {
|
|
(a, b): 0
|
|
for (a, b) in itertools.product(
|
|
font.getGlyphOrder(), repeat=2)
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
sys.exit(pytest.main(sys.argv))
|