fonttools/Tests/ttLib/woff2_test.py

752 lines
27 KiB
Python
Raw Normal View History

from __future__ import print_function, division, absolute_import, unicode_literals
from fontTools.misc.py23 import *
from fontTools import ttLib
from fontTools.ttLib.woff2 import (
WOFF2Reader, woff2DirectorySize, woff2DirectoryFormat,
woff2FlagsSize, woff2UnknownTagSize, woff2Base128MaxSize, WOFF2DirectoryEntry,
getKnownTagIndex, packBase128, base128Size, woff2UnknownTagIndex,
WOFF2FlavorData, woff2TransformedTableTags, WOFF2GlyfTable, WOFF2LocaTable,
WOFF2Writer)
import unittest
from fontTools.misc import sstruct
import os
import random
import copy
from collections import OrderedDict
haveBrotli = False
try:
import brotli
haveBrotli = True
except ImportError:
pass
# Python 3 renamed 'assertRaisesRegexp' to 'assertRaisesRegex', and fires
# deprecation warnings if a program uses the old name.
if not hasattr(unittest.TestCase, 'assertRaisesRegex'):
unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
current_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
data_dir = os.path.join(current_dir, 'testdata')
TTX = os.path.join(data_dir, 'TestTTF-Regular.ttx')
OTX = os.path.join(data_dir, 'TestOTF-Regular.otx')
METADATA = os.path.join(data_dir, 'test_woff2_metadata.xml')
TT_WOFF2 = BytesIO()
CFF_WOFF2 = BytesIO()
def setUpModule():
if not haveBrotli:
raise unittest.SkipTest("No module named brotli")
assert os.path.exists(TTX)
assert os.path.exists(OTX)
# import TT-flavoured test font and save it as WOFF2
ttf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
ttf.importXML(TTX)
ttf.flavor = "woff2"
ttf.save(TT_WOFF2, reorderTables=None)
# import CFF-flavoured test font and save it as WOFF2
otf = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
otf.importXML(OTX)
otf.flavor = "woff2"
otf.save(CFF_WOFF2, reorderTables=None)
class WOFF2ReaderTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.file = BytesIO(CFF_WOFF2.getvalue())
cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
cls.font.importXML(OTX)
def setUp(self):
self.file.seek(0)
def test_bad_signature(self):
with self.assertRaisesRegex(ttLib.TTLibError, 'bad signature'):
WOFF2Reader(BytesIO(b"wOFF"))
def test_not_enough_data_header(self):
incomplete_header = self.file.read(woff2DirectorySize - 1)
with self.assertRaisesRegex(ttLib.TTLibError, 'not enough data'):
WOFF2Reader(BytesIO(incomplete_header))
def test_incorrect_compressed_size(self):
data = self.file.read(woff2DirectorySize)
header = sstruct.unpack(woff2DirectoryFormat, data)
header['totalCompressedSize'] = 0
data = sstruct.pack(woff2DirectoryFormat, header)
with self.assertRaises((brotli.error, ttLib.TTLibError)):
WOFF2Reader(BytesIO(data + self.file.read()))
def test_incorrect_uncompressed_size(self):
decompress_backup = brotli.decompress
brotli.decompress = lambda data: b"" # return empty byte string
with self.assertRaisesRegex(ttLib.TTLibError, 'unexpected size for decompressed'):
WOFF2Reader(self.file)
brotli.decompress = decompress_backup
def test_incorrect_file_size(self):
data = self.file.read(woff2DirectorySize)
header = sstruct.unpack(woff2DirectoryFormat, data)
header['length'] -= 1
data = sstruct.pack(woff2DirectoryFormat, header)
with self.assertRaisesRegex(
ttLib.TTLibError, "doesn't match the actual file size"):
WOFF2Reader(BytesIO(data + self.file.read()))
def test_num_tables(self):
tags = [t for t in self.font.keys() if t not in ('GlyphOrder', 'DSIG')]
data = self.file.read(woff2DirectorySize)
header = sstruct.unpack(woff2DirectoryFormat, data)
self.assertEqual(header['numTables'], len(tags))
def test_table_tags(self):
tags = set([t for t in self.font.keys() if t not in ('GlyphOrder', 'DSIG')])
reader = WOFF2Reader(self.file)
self.assertEqual(set(reader.keys()), tags)
def test_get_normal_tables(self):
woff2Reader = WOFF2Reader(self.file)
specialTags = woff2TransformedTableTags + ('head', 'GlyphOrder', 'DSIG')
for tag in [t for t in self.font.keys() if t not in specialTags]:
origData = self.font.getTableData(tag)
decompressedData = woff2Reader[tag]
self.assertEqual(origData, decompressedData)
def test_reconstruct_unknown(self):
reader = WOFF2Reader(self.file)
with self.assertRaisesRegex(ttLib.TTLibError, 'transform for table .* unknown'):
reader.reconstructTable('ZZZZ')
class WOFF2ReaderTTFTest(WOFF2ReaderTest):
""" Tests specific to TT-flavored fonts. """
@classmethod
def setUpClass(cls):
cls.file = BytesIO(TT_WOFF2.getvalue())
cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
cls.font.importXML(TTX)
def setUp(self):
self.file.seek(0)
def test_reconstruct_glyf(self):
woff2Reader = WOFF2Reader(self.file)
reconstructedData = woff2Reader['glyf']
self.assertEqual(self.font.getTableData('glyf'), reconstructedData)
def test_reconstruct_loca(self):
woff2Reader = WOFF2Reader(self.file)
reconstructedData = woff2Reader['loca']
self.assertEqual(self.font.getTableData('loca'), reconstructedData)
self.assertTrue(hasattr(woff2Reader.tables['glyf'], 'data'))
def test_reconstruct_loca_not_match_orig_size(self):
reader = WOFF2Reader(self.file)
reader.tables['loca'].origLength -= 1
with self.assertRaisesRegex(
ttLib.TTLibError, "'loca' table doesn't match original size"):
reader.reconstructTable('loca')
def normalise_table(font, tag, padding=4):
""" Return normalised table data. Keep 'font' instance unmodified. """
assert tag in ('glyf', 'loca', 'head')
assert tag in font
if tag == 'head':
origHeadFlags = font['head'].flags
font['head'].flags |= (1 << 11)
tableData = font['head'].compile(font)
if font.sfntVersion in ("\x00\x01\x00\x00", "true"):
assert {'glyf', 'loca', 'head'}.issubset(font.keys())
origIndexFormat = font['head'].indexToLocFormat
if hasattr(font['loca'], 'locations'):
origLocations = font['loca'].locations[:]
else:
origLocations = []
glyfTable = ttLib.newTable('glyf')
glyfTable.decompile(font.getTableData('glyf'), font)
glyfTable.padding = padding
if tag == 'glyf':
tableData = glyfTable.compile(font)
elif tag == 'loca':
glyfTable.compile(font)
tableData = font['loca'].compile(font)
if tag == 'head':
glyfTable.compile(font)
font['loca'].compile(font)
tableData = font['head'].compile(font)
font['head'].indexToLocFormat = origIndexFormat
font['loca'].set(origLocations)
if tag == 'head':
font['head'].flags = origHeadFlags
return tableData
def normalise_font(font, padding=4):
""" Return normalised font data. Keep 'font' instance unmodified. """
# drop DSIG but keep a copy
DSIG_copy = copy.deepcopy(font['DSIG'])
del font['DSIG']
# ovverride TTFont attributes
origFlavor = font.flavor
origRecalcBBoxes = font.recalcBBoxes
origRecalcTimestamp = font.recalcTimestamp
origLazy = font.lazy
font.flavor = None
font.recalcBBoxes = False
font.recalcTimestamp = False
font.lazy = True
# save font to temporary stream
infile = BytesIO()
font.save(infile)
infile.seek(0)
# reorder tables alphabetically
outfile = BytesIO()
reader = ttLib.sfnt.SFNTReader(infile)
writer = ttLib.sfnt.SFNTWriter(
outfile, len(reader.tables), reader.sfntVersion, reader.flavor, reader.flavorData)
for tag in sorted(reader.keys()):
if tag in woff2TransformedTableTags + ('head',):
writer[tag] = normalise_table(font, tag, padding)
else:
writer[tag] = reader[tag]
writer.close()
# restore font attributes
font['DSIG'] = DSIG_copy
font.flavor = origFlavor
font.recalcBBoxes = origRecalcBBoxes
font.recalcTimestamp = origRecalcTimestamp
font.lazy = origLazy
return outfile.getvalue()
class WOFF2DirectoryEntryTest(unittest.TestCase):
def setUp(self):
self.entry = WOFF2DirectoryEntry()
def test_not_enough_data_table_flags(self):
with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'flags'"):
self.entry.fromString(b"")
def test_not_enough_data_table_tag(self):
incompleteData = bytearray([0x3F, 0, 0, 0])
with self.assertRaisesRegex(ttLib.TTLibError, "can't read table 'tag'"):
self.entry.fromString(bytes(incompleteData))
def test_table_reserved_flags(self):
with self.assertRaisesRegex(ttLib.TTLibError, "bits 6-7 are reserved"):
self.entry.fromString(bytechr(0xC0))
def test_loca_zero_transformLength(self):
data = bytechr(getKnownTagIndex('loca')) # flags
data += packBase128(random.randint(1, 100)) # origLength
data += packBase128(1) # non-zero transformLength
with self.assertRaisesRegex(
ttLib.TTLibError, "transformLength of the 'loca' table must be 0"):
self.entry.fromString(data)
def test_fromFile(self):
unknownTag = Tag('ZZZZ')
data = bytechr(getKnownTagIndex(unknownTag))
data += unknownTag.tobytes()
data += packBase128(random.randint(1, 100))
expectedPos = len(data)
f = BytesIO(data + b'\0'*100)
self.entry.fromFile(f)
self.assertEqual(f.tell(), expectedPos)
def test_transformed_toString(self):
self.entry.tag = Tag('glyf')
self.entry.flags = getKnownTagIndex(self.entry.tag)
self.entry.origLength = random.randint(101, 200)
self.entry.length = random.randint(1, 100)
expectedSize = (woff2FlagsSize + base128Size(self.entry.origLength) +
base128Size(self.entry.length))
data = self.entry.toString()
self.assertEqual(len(data), expectedSize)
def test_known_toString(self):
self.entry.tag = Tag('head')
self.entry.flags = getKnownTagIndex(self.entry.tag)
self.entry.origLength = 54
expectedSize = (woff2FlagsSize + base128Size(self.entry.origLength))
data = self.entry.toString()
self.assertEqual(len(data), expectedSize)
def test_unknown_toString(self):
self.entry.tag = Tag('ZZZZ')
self.entry.flags = woff2UnknownTagIndex
self.entry.origLength = random.randint(1, 100)
expectedSize = (woff2FlagsSize + woff2UnknownTagSize +
base128Size(self.entry.origLength))
data = self.entry.toString()
self.assertEqual(len(data), expectedSize)
class DummyReader(WOFF2Reader):
def __init__(self, file, checkChecksums=1, fontNumber=-1):
self.file = file
for attr in ('majorVersion', 'minorVersion', 'metaOffset', 'metaLength',
'metaOrigLength', 'privLength', 'privOffset'):
setattr(self, attr, 0)
class WOFF2FlavorDataTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
assert os.path.exists(METADATA)
with open(METADATA, 'rb') as f:
cls.xml_metadata = f.read()
cls.compressed_metadata = brotli.compress(cls.xml_metadata, mode=brotli.MODE_TEXT)
# make random byte strings; font data must be 4-byte aligned
cls.fontdata = bytes(bytearray(random.sample(range(0, 256), 80)))
cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
def setUp(self):
self.file = BytesIO(self.fontdata)
self.file.seek(0, 2)
def test_get_metaData_no_privData(self):
self.file.write(self.compressed_metadata)
reader = DummyReader(self.file)
reader.metaOffset = len(self.fontdata)
reader.metaLength = len(self.compressed_metadata)
reader.metaOrigLength = len(self.xml_metadata)
flavorData = WOFF2FlavorData(reader)
self.assertEqual(self.xml_metadata, flavorData.metaData)
def test_get_privData_no_metaData(self):
self.file.write(self.privData)
reader = DummyReader(self.file)
reader.privOffset = len(self.fontdata)
reader.privLength = len(self.privData)
flavorData = WOFF2FlavorData(reader)
self.assertEqual(self.privData, flavorData.privData)
def test_get_metaData_and_privData(self):
self.file.write(self.compressed_metadata + self.privData)
reader = DummyReader(self.file)
reader.metaOffset = len(self.fontdata)
reader.metaLength = len(self.compressed_metadata)
reader.metaOrigLength = len(self.xml_metadata)
reader.privOffset = reader.metaOffset + reader.metaLength
reader.privLength = len(self.privData)
flavorData = WOFF2FlavorData(reader)
self.assertEqual(self.xml_metadata, flavorData.metaData)
self.assertEqual(self.privData, flavorData.privData)
def test_get_major_minorVersion(self):
reader = DummyReader(self.file)
reader.majorVersion = reader.minorVersion = 1
flavorData = WOFF2FlavorData(reader)
self.assertEqual(flavorData.majorVersion, 1)
self.assertEqual(flavorData.minorVersion, 1)
class WOFF2WriterTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False, flavor="woff2")
cls.font.importXML(OTX)
cls.tags = [t for t in cls.font.keys() if t != 'GlyphOrder']
cls.numTables = len(cls.tags)
cls.file = BytesIO(CFF_WOFF2.getvalue())
cls.file.seek(0, 2)
cls.length = (cls.file.tell() + 3) & ~3
cls.setUpFlavorData()
@classmethod
def setUpFlavorData(cls):
assert os.path.exists(METADATA)
with open(METADATA, 'rb') as f:
cls.xml_metadata = f.read()
cls.compressed_metadata = brotli.compress(cls.xml_metadata, mode=brotli.MODE_TEXT)
cls.privData = bytes(bytearray(random.sample(range(0, 256), 20)))
def setUp(self):
self.file.seek(0)
self.writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
def test_DSIG_dropped(self):
self.writer['DSIG'] = b"\0"
self.assertEqual(len(self.writer.tables), 0)
self.assertEqual(self.writer.numTables, self.numTables-1)
def test_no_rewrite_table(self):
self.writer['ZZZZ'] = b"\0"
with self.assertRaisesRegex(ttLib.TTLibError, "cannot rewrite"):
self.writer['ZZZZ'] = b"\0"
def test_num_tables(self):
self.writer['ABCD'] = b"\0"
with self.assertRaisesRegex(ttLib.TTLibError, "wrong number of tables"):
self.writer.close()
def test_required_tables(self):
font = ttLib.TTFont(flavor="woff2")
with self.assertRaisesRegex(ttLib.TTLibError, "missing required table"):
font.save(BytesIO())
def test_head_transform_flag(self):
headData = self.font.getTableData('head')
origFlags = byteord(headData[16])
woff2font = ttLib.TTFont(self.file)
newHeadData = woff2font.getTableData('head')
modifiedFlags = byteord(newHeadData[16])
self.assertNotEqual(origFlags, modifiedFlags)
restoredFlags = modifiedFlags & ~0x08 # turn off bit 11
self.assertEqual(origFlags, restoredFlags)
def test_tables_sorted_alphabetically(self):
expected = sorted([t for t in self.tags if t != 'DSIG'])
woff2font = ttLib.TTFont(self.file)
2015-08-19 16:40:45 +01:00
self.assertEqual(expected, list(woff2font.reader.keys()))
def test_checksums(self):
normFile = BytesIO(normalise_font(self.font, padding=4))
normFile.seek(0)
normFont = ttLib.TTFont(normFile, checkChecksums=2)
w2font = ttLib.TTFont(self.file)
# force reconstructing glyf table using 4-byte padding
w2font.reader.padding = 4
for tag in [t for t in self.tags if t != 'DSIG']:
w2data = w2font.reader[tag]
normData = normFont.reader[tag]
if tag == "head":
w2data = w2data[:8] + b'\0\0\0\0' + w2data[12:]
normData = normData[:8] + b'\0\0\0\0' + normData[12:]
w2CheckSum = ttLib.sfnt.calcChecksum(w2data)
normCheckSum = ttLib.sfnt.calcChecksum(normData)
self.assertEqual(w2CheckSum, normCheckSum)
normCheckSumAdjustment = normFont['head'].checkSumAdjustment
self.assertEqual(normCheckSumAdjustment, w2font['head'].checkSumAdjustment)
def test_calcSFNTChecksumsLengthsAndOffsets(self):
normFont = ttLib.TTFont(BytesIO(normalise_font(self.font, padding=4)))
for tag in self.tags:
self.writer[tag] = self.font.getTableData(tag)
self.writer._normaliseGlyfAndLoca(padding=4)
self.writer._setHeadTransformFlag()
self.writer.tables = OrderedDict(sorted(self.writer.tables.items()))
self.writer._calcSFNTChecksumsLengthsAndOffsets()
for tag, entry in normFont.reader.tables.items():
self.assertEqual(entry.offset, self.writer.tables[tag].origOffset)
self.assertEqual(entry.length, self.writer.tables[tag].origLength)
self.assertEqual(entry.checkSum, self.writer.tables[tag].checkSum)
def test_bad_sfntVersion(self):
for i in range(self.numTables):
self.writer[bytechr(65 + i)*4] = b"\0"
self.writer.sfntVersion = 'ZZZZ'
with self.assertRaisesRegex(ttLib.TTLibError, "bad sfntVersion"):
self.writer.close()
def test_calcTotalSize_no_flavorData(self):
expected = self.length
self.writer.file = BytesIO()
for tag in self.tags:
self.writer[tag] = self.font.getTableData(tag)
self.writer.close()
self.assertEqual(expected, self.writer.length)
self.assertEqual(expected, self.writer.file.tell())
def test_calcTotalSize_with_metaData(self):
expected = self.length + len(self.compressed_metadata)
flavorData = self.writer.flavorData = WOFF2FlavorData()
flavorData.metaData = self.xml_metadata
self.writer.file = BytesIO()
for tag in self.tags:
self.writer[tag] = self.font.getTableData(tag)
self.writer.close()
self.assertEqual(expected, self.writer.length)
self.assertEqual(expected, self.writer.file.tell())
def test_calcTotalSize_with_privData(self):
expected = self.length + len(self.privData)
flavorData = self.writer.flavorData = WOFF2FlavorData()
flavorData.privData = self.privData
self.writer.file = BytesIO()
for tag in self.tags:
self.writer[tag] = self.font.getTableData(tag)
self.writer.close()
self.assertEqual(expected, self.writer.length)
self.assertEqual(expected, self.writer.file.tell())
def test_calcTotalSize_with_metaData_and_privData(self):
metaDataLength = (len(self.compressed_metadata) + 3) & ~3
expected = self.length + metaDataLength + len(self.privData)
flavorData = self.writer.flavorData = WOFF2FlavorData()
flavorData.metaData = self.xml_metadata
flavorData.privData = self.privData
self.writer.file = BytesIO()
for tag in self.tags:
self.writer[tag] = self.font.getTableData(tag)
self.writer.close()
self.assertEqual(expected, self.writer.length)
self.assertEqual(expected, self.writer.file.tell())
def test_getVersion(self):
# no version
self.assertEqual((0, 0), self.writer._getVersion())
# version from head.fontRevision
fontRevision = self.font['head'].fontRevision
versionTuple = tuple(int(i) for i in str(fontRevision).split("."))
entry = self.writer.tables['head'] = ttLib.newTable('head')
entry.data = self.font.getTableData('head')
self.assertEqual(versionTuple, self.writer._getVersion())
# version from writer.flavorData
flavorData = self.writer.flavorData = WOFF2FlavorData()
flavorData.majorVersion, flavorData.minorVersion = (10, 11)
self.assertEqual((10, 11), self.writer._getVersion())
class WOFF2WriterTTFTest(WOFF2WriterTest):
@classmethod
def setUpClass(cls):
cls.font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False, flavor="woff2")
cls.font.importXML(TTX)
cls.tags = [t for t in cls.font.keys() if t != 'GlyphOrder']
cls.numTables = len(cls.tags)
cls.file = BytesIO(TT_WOFF2.getvalue())
cls.file.seek(0, 2)
cls.length = (cls.file.tell() + 3) & ~3
cls.setUpFlavorData()
def test_normaliseGlyfAndLoca(self):
normTables = {}
for tag in ('head', 'loca', 'glyf'):
normTables[tag] = normalise_table(self.font, tag, padding=4)
for tag in self.tags:
tableData = self.font.getTableData(tag)
self.writer[tag] = tableData
if tag in normTables:
self.assertNotEqual(tableData, normTables[tag])
self.writer._normaliseGlyfAndLoca(padding=4)
self.writer._setHeadTransformFlag()
for tag in normTables:
self.assertEqual(self.writer.tables[tag].data, normTables[tag])
class WOFF2LocaTableTest(unittest.TestCase):
def setUp(self):
self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
font['head'] = ttLib.newTable('head')
font['loca'] = WOFF2LocaTable()
font['glyf'] = WOFF2GlyfTable()
def test_compile_short_loca(self):
locaTable = self.font['loca']
locaTable.set(list(range(0, 0x20000, 2)))
self.font['glyf'].indexFormat = 0
locaData = locaTable.compile(self.font)
self.assertEqual(len(locaData), 0x20000)
def test_compile_short_loca_overflow(self):
locaTable = self.font['loca']
locaTable.set(list(range(0x20000 + 1)))
self.font['glyf'].indexFormat = 0
with self.assertRaisesRegex(
ttLib.TTLibError, "indexFormat is 0 but local offsets > 0x20000"):
locaTable.compile(self.font)
def test_compile_short_loca_not_multiples_of_2(self):
locaTable = self.font['loca']
locaTable.set([1, 3, 5, 7])
self.font['glyf'].indexFormat = 0
with self.assertRaisesRegex(ttLib.TTLibError, "offsets not multiples of 2"):
locaTable.compile(self.font)
def test_compile_long_loca(self):
locaTable = self.font['loca']
locaTable.set(list(range(0x20001)))
self.font['glyf'].indexFormat = 1
locaData = locaTable.compile(self.font)
self.assertEqual(len(locaData), 0x20001 * 4)
def test_compile_set_indexToLocFormat_0(self):
locaTable = self.font['loca']
# offsets are all multiples of 2 and max length is < 0x10000
locaTable.set(list(range(0, 0x20000, 2)))
locaTable.compile(self.font)
newIndexFormat = self.font['head'].indexToLocFormat
self.assertEqual(0, newIndexFormat)
def test_compile_set_indexToLocFormat_1(self):
locaTable = self.font['loca']
# offsets are not multiples of 2
locaTable.set(list(range(10)))
locaTable.compile(self.font)
newIndexFormat = self.font['head'].indexToLocFormat
self.assertEqual(1, newIndexFormat)
# max length is >= 0x10000
locaTable.set(list(range(0, 0x20000 + 1, 2)))
locaTable.compile(self.font)
newIndexFormat = self.font['head'].indexToLocFormat
self.assertEqual(1, newIndexFormat)
class WOFF2GlyfTableTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.importXML(TTX)
cls.tables = {}
cls.transformedTags = ('maxp', 'head', 'loca', 'glyf')
for tag in reversed(cls.transformedTags): # compile in inverse order
cls.tables[tag] = font.getTableData(tag)
infile = BytesIO(TT_WOFF2.getvalue())
reader = WOFF2Reader(infile)
cls.transformedGlyfData = reader.tables['glyf'].loadData(
reader.transformBuffer)
cls.glyphOrder = ['.notdef'] + ["glyph%.5d" % i for i in range(1, font['maxp'].numGlyphs)]
def setUp(self):
self.font = font = ttLib.TTFont(recalcBBoxes=False, recalcTimestamp=False)
font.setGlyphOrder(self.glyphOrder)
font['head'] = ttLib.newTable('head')
font['maxp'] = ttLib.newTable('maxp')
font['loca'] = WOFF2LocaTable()
font['glyf'] = WOFF2GlyfTable()
for tag in self.transformedTags:
font[tag].decompile(self.tables[tag], font)
def test_reconstruct_glyf_padded_4(self):
glyfTable = WOFF2GlyfTable()
glyfTable.reconstruct(self.transformedGlyfData, self.font)
glyfTable.padding = 4
data = glyfTable.compile(self.font)
normGlyfData = normalise_table(self.font, 'glyf', glyfTable.padding)
self.assertEqual(normGlyfData, data)
def test_reconstruct_glyf_padded_2(self):
glyfTable = WOFF2GlyfTable()
glyfTable.reconstruct(self.transformedGlyfData, self.font)
glyfTable.padding = 2
data = glyfTable.compile(self.font)
normGlyfData = normalise_table(self.font, 'glyf', glyfTable.padding)
self.assertEqual(normGlyfData, data)
def test_reconstruct_glyf_unpadded(self):
glyfTable = WOFF2GlyfTable()
glyfTable.reconstruct(self.transformedGlyfData, self.font)
data = glyfTable.compile(self.font)
self.assertEqual(self.tables['glyf'], data)
def test_reconstruct_glyf_incorrect_glyphOrder(self):
glyfTable = WOFF2GlyfTable()
badGlyphOrder = self.font.getGlyphOrder()[:-1]
self.font.setGlyphOrder(badGlyphOrder)
with self.assertRaisesRegex(ttLib.TTLibError, "incorrect glyphOrder"):
glyfTable.reconstruct(self.transformedGlyfData, self.font)
def test_reconstruct_glyf_missing_glyphOrder(self):
glyfTable = WOFF2GlyfTable()
del self.font.glyphOrder
numGlyphs = self.font['maxp'].numGlyphs
del self.font['maxp']
glyfTable.reconstruct(self.transformedGlyfData, self.font)
expected = [".notdef"]
expected.extend(["glyph%.5d" % i for i in range(1, numGlyphs)])
self.assertEqual(expected, glyfTable.glyphOrder)
def test_reconstruct_loca_padded_4(self):
locaTable = self.font['loca'] = WOFF2LocaTable()
glyfTable = self.font['glyf'] = WOFF2GlyfTable()
glyfTable.reconstruct(self.transformedGlyfData, self.font)
glyfTable.padding = 4
glyfTable.compile(self.font)
data = locaTable.compile(self.font)
normLocaData = normalise_table(self.font, 'loca', glyfTable.padding)
self.assertEqual(normLocaData, data)
def test_reconstruct_loca_padded_2(self):
locaTable = self.font['loca'] = WOFF2LocaTable()
glyfTable = self.font['glyf'] = WOFF2GlyfTable()
glyfTable.reconstruct(self.transformedGlyfData, self.font)
glyfTable.padding = 2
glyfTable.compile(self.font)
data = locaTable.compile(self.font)
normLocaData = normalise_table(self.font, 'loca', glyfTable.padding)
self.assertEqual(normLocaData, data)
def test_reconstruct_loca_unpadded(self):
locaTable = self.font['loca'] = WOFF2LocaTable()
glyfTable = self.font['glyf'] = WOFF2GlyfTable()
glyfTable.reconstruct(self.transformedGlyfData, self.font)
glyfTable.compile(self.font)
data = locaTable.compile(self.font)
self.assertEqual(self.tables['loca'], data)
def test_reconstruct_glyf_header_not_enough_data(self):
with self.assertRaisesRegex(ttLib.TTLibError, "not enough 'glyf' data"):
WOFF2GlyfTable().reconstruct(b"", self.font)
def test_reconstruct_glyf_table_incorrect_size(self):
msg = "incorrect size of transformed 'glyf'"
with self.assertRaisesRegex(ttLib.TTLibError, msg):
WOFF2GlyfTable().reconstruct(self.transformedGlyfData + b"\x00", self.font)
with self.assertRaisesRegex(ttLib.TTLibError, msg):
WOFF2GlyfTable().reconstruct(self.transformedGlyfData[:-1], self.font)
def test_transform_glyf(self):
glyfTable = self.font['glyf']
data = glyfTable.transform(self.font)
self.assertEqual(self.transformedGlyfData, data)
def test_transform_glyf_incorrect_glyphOrder(self):
glyfTable = self.font['glyf']
badGlyphOrder = self.font.getGlyphOrder()[:-1]
del glyfTable.glyphOrder
self.font.setGlyphOrder(badGlyphOrder)
with self.assertRaisesRegex(ttLib.TTLibError, "incorrect glyphOrder"):
glyfTable.transform(self.font)
glyfTable.glyphOrder = badGlyphOrder
with self.assertRaisesRegex(ttLib.TTLibError, "incorrect glyphOrder"):
glyfTable.transform(self.font)
def test_transform_glyf_missing_glyphOrder(self):
glyfTable = self.font['glyf']
del glyfTable.glyphOrder
del self.font.glyphOrder
numGlyphs = self.font['maxp'].numGlyphs
del self.font['maxp']
glyfTable.transform(self.font)
expected = [".notdef"]
expected.extend(["glyph%.5d" % i for i in range(1, numGlyphs)])
self.assertEqual(expected, glyfTable.glyphOrder)
def test_roundtrip_glyf_reconstruct_and_transform(self):
glyfTable = WOFF2GlyfTable()
glyfTable.reconstruct(self.transformedGlyfData, self.font)
data = glyfTable.transform(self.font)
self.assertEqual(self.transformedGlyfData, data)
def test_roundtrip_glyf_transform_and_reconstruct(self):
glyfTable = self.font['glyf']
transformedData = glyfTable.transform(self.font)
newGlyfTable = WOFF2GlyfTable()
newGlyfTable.reconstruct(transformedData, self.font)
newGlyfTable.padding = 4
reconstructedData = newGlyfTable.compile(self.font)
normGlyfData = normalise_table(self.font, 'glyf', newGlyfTable.padding)
self.assertEqual(normGlyfData, reconstructedData)
if __name__ == "__main__":
import sys
sys.exit(unittest.main())