woff2_test: add tests for transformed hmtx and untransformed glyf+loca
This commit is contained in:
parent
ba41877d10
commit
8bd83f36bd
@ -6,14 +6,18 @@ from fontTools.ttLib.woff2 import (
|
|||||||
woff2FlagsSize, woff2UnknownTagSize, woff2Base128MaxSize, WOFF2DirectoryEntry,
|
woff2FlagsSize, woff2UnknownTagSize, woff2Base128MaxSize, WOFF2DirectoryEntry,
|
||||||
getKnownTagIndex, packBase128, base128Size, woff2UnknownTagIndex,
|
getKnownTagIndex, packBase128, base128Size, woff2UnknownTagIndex,
|
||||||
WOFF2FlavorData, woff2TransformedTableTags, WOFF2GlyfTable, WOFF2LocaTable,
|
WOFF2FlavorData, woff2TransformedTableTags, WOFF2GlyfTable, WOFF2LocaTable,
|
||||||
WOFF2Writer, unpackBase128, unpack255UShort, pack255UShort)
|
WOFF2HmtxTable, WOFF2Writer, unpackBase128, unpack255UShort, pack255UShort)
|
||||||
import unittest
|
import unittest
|
||||||
from fontTools.misc import sstruct
|
from fontTools.misc import sstruct
|
||||||
|
from fontTools import fontBuilder
|
||||||
|
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
||||||
import struct
|
import struct
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import copy
|
import copy
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from functools import partial
|
||||||
|
import pytest
|
||||||
|
|
||||||
haveBrotli = False
|
haveBrotli = False
|
||||||
try:
|
try:
|
||||||
@ -288,6 +292,35 @@ class WOFF2DirectoryEntryTest(unittest.TestCase):
|
|||||||
data = self.entry.toString()
|
data = self.entry.toString()
|
||||||
self.assertEqual(len(data), expectedSize)
|
self.assertEqual(len(data), expectedSize)
|
||||||
|
|
||||||
|
def test_glyf_loca_transform_flags(self):
|
||||||
|
for tag in ("glyf", "loca"):
|
||||||
|
entry = WOFF2DirectoryEntry()
|
||||||
|
entry.tag = Tag(tag)
|
||||||
|
entry.flags = getKnownTagIndex(entry.tag)
|
||||||
|
|
||||||
|
self.assertEqual(entry.transformVersion, 0)
|
||||||
|
self.assertTrue(entry.transformed)
|
||||||
|
|
||||||
|
entry.transformed = False
|
||||||
|
|
||||||
|
self.assertEqual(entry.transformVersion, 3)
|
||||||
|
self.assertEqual(entry.flags & 0b11000000, (3 << 6))
|
||||||
|
self.assertFalse(entry.transformed)
|
||||||
|
|
||||||
|
def test_other_transform_flags(self):
|
||||||
|
entry = WOFF2DirectoryEntry()
|
||||||
|
entry.tag = Tag('ZZZZ')
|
||||||
|
entry.flags = woff2UnknownTagIndex
|
||||||
|
|
||||||
|
self.assertEqual(entry.transformVersion, 0)
|
||||||
|
self.assertFalse(entry.transformed)
|
||||||
|
|
||||||
|
entry.transformed = True
|
||||||
|
|
||||||
|
self.assertEqual(entry.transformVersion, 1)
|
||||||
|
self.assertEqual(entry.flags & 0b11000000, (1 << 6))
|
||||||
|
self.assertTrue(entry.transformed)
|
||||||
|
|
||||||
|
|
||||||
class DummyReader(WOFF2Reader):
|
class DummyReader(WOFF2Reader):
|
||||||
|
|
||||||
@ -351,6 +384,24 @@ class WOFF2FlavorDataTest(unittest.TestCase):
|
|||||||
self.assertEqual(flavorData.majorVersion, 1)
|
self.assertEqual(flavorData.majorVersion, 1)
|
||||||
self.assertEqual(flavorData.minorVersion, 1)
|
self.assertEqual(flavorData.minorVersion, 1)
|
||||||
|
|
||||||
|
def test_mutually_exclusive_args(self):
|
||||||
|
reader = DummyReader(self.file)
|
||||||
|
with self.assertRaisesRegex(TypeError, "arguments are mutually exclusive"):
|
||||||
|
WOFF2FlavorData(reader, transformedTables={"hmtx"})
|
||||||
|
|
||||||
|
def test_transformTables_default(self):
|
||||||
|
flavorData = WOFF2FlavorData()
|
||||||
|
self.assertEqual(flavorData.transformedTables, set(woff2TransformedTableTags))
|
||||||
|
|
||||||
|
def test_transformTables_invalid(self):
|
||||||
|
msg = r"'glyf' and 'loca' must be transformed \(or not\) together"
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, msg):
|
||||||
|
WOFF2FlavorData(transformedTables={"glyf"})
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, msg):
|
||||||
|
WOFF2FlavorData(transformedTables={"loca"})
|
||||||
|
|
||||||
|
|
||||||
class WOFF2WriterTest(unittest.TestCase):
|
class WOFF2WriterTest(unittest.TestCase):
|
||||||
|
|
||||||
@ -509,6 +560,30 @@ class WOFF2WriterTest(unittest.TestCase):
|
|||||||
flavorData.majorVersion, flavorData.minorVersion = (10, 11)
|
flavorData.majorVersion, flavorData.minorVersion = (10, 11)
|
||||||
self.assertEqual((10, 11), self.writer._getVersion())
|
self.assertEqual((10, 11), self.writer._getVersion())
|
||||||
|
|
||||||
|
def test_hmtx_trasform(self):
|
||||||
|
tableTransforms = {"glyf", "loca", "hmtx"}
|
||||||
|
|
||||||
|
writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
|
||||||
|
writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
|
||||||
|
|
||||||
|
for tag in self.tags:
|
||||||
|
writer[tag] = self.font.getTableData(tag)
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
# enabling hmtx transform has no effect when font has no glyf table
|
||||||
|
self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
|
||||||
|
|
||||||
|
def test_no_transforms(self):
|
||||||
|
writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
|
||||||
|
writer.flavorData = WOFF2FlavorData(transformedTables=())
|
||||||
|
|
||||||
|
for tag in self.tags:
|
||||||
|
writer[tag] = self.font.getTableData(tag)
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
# transforms settings have no effect when font is CFF-flavored, since
|
||||||
|
# all the current transforms only apply to TrueType-flavored fonts.
|
||||||
|
self.assertEqual(writer.file.getvalue(), CFF_WOFF2.getvalue())
|
||||||
|
|
||||||
class WOFF2WriterTTFTest(WOFF2WriterTest):
|
class WOFF2WriterTTFTest(WOFF2WriterTest):
|
||||||
|
|
||||||
@ -537,6 +612,35 @@ class WOFF2WriterTTFTest(WOFF2WriterTest):
|
|||||||
for tag in normTables:
|
for tag in normTables:
|
||||||
self.assertEqual(self.writer.tables[tag].data, normTables[tag])
|
self.assertEqual(self.writer.tables[tag].data, normTables[tag])
|
||||||
|
|
||||||
|
def test_hmtx_trasform(self):
|
||||||
|
tableTransforms = {"glyf", "loca", "hmtx"}
|
||||||
|
|
||||||
|
writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
|
||||||
|
writer.flavorData = WOFF2FlavorData(transformedTables=tableTransforms)
|
||||||
|
|
||||||
|
for tag in self.tags:
|
||||||
|
writer[tag] = self.font.getTableData(tag)
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
length = len(writer.file.getvalue())
|
||||||
|
|
||||||
|
# enabling optional hmtx transform shaves off a few bytes
|
||||||
|
self.assertLess(length, len(TT_WOFF2.getvalue()))
|
||||||
|
|
||||||
|
def test_no_transforms(self):
|
||||||
|
writer = WOFF2Writer(BytesIO(), self.numTables, self.font.sfntVersion)
|
||||||
|
writer.flavorData = WOFF2FlavorData(transformedTables=())
|
||||||
|
|
||||||
|
for tag in self.tags:
|
||||||
|
writer[tag] = self.font.getTableData(tag)
|
||||||
|
writer.close()
|
||||||
|
|
||||||
|
self.assertNotEqual(writer.file.getvalue(), TT_WOFF2.getvalue())
|
||||||
|
|
||||||
|
writer.file.seek(0)
|
||||||
|
reader = WOFF2Reader(writer.file)
|
||||||
|
self.assertEqual(len(reader.flavorData.transformedTables), 0)
|
||||||
|
|
||||||
|
|
||||||
class WOFF2LocaTableTest(unittest.TestCase):
|
class WOFF2LocaTableTest(unittest.TestCase):
|
||||||
|
|
||||||
@ -723,6 +827,376 @@ class WOFF2GlyfTableTest(unittest.TestCase):
|
|||||||
self.assertEqual(normGlyfData, reconstructedData)
|
self.assertEqual(normGlyfData, reconstructedData)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def fontfile():
|
||||||
|
|
||||||
|
class Glyph(object):
|
||||||
|
def __init__(self, empty=False, **kwargs):
|
||||||
|
if not empty:
|
||||||
|
self.draw = partial(self.drawRect, **kwargs)
|
||||||
|
else:
|
||||||
|
self.draw = lambda pen: None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def drawRect(pen, xMin, xMax):
|
||||||
|
pen.moveTo((xMin, 0))
|
||||||
|
pen.lineTo((xMin, 1000))
|
||||||
|
pen.lineTo((xMax, 1000))
|
||||||
|
pen.lineTo((xMax, 0))
|
||||||
|
pen.closePath()
|
||||||
|
|
||||||
|
class CompositeGlyph(object):
|
||||||
|
def __init__(self, components):
|
||||||
|
self.components = components
|
||||||
|
|
||||||
|
def draw(self, pen):
|
||||||
|
for baseGlyph, (offsetX, offsetY) in self.components:
|
||||||
|
pen.addComponent(baseGlyph, (1, 0, 0, 1, offsetX, offsetY))
|
||||||
|
|
||||||
|
fb = fontBuilder.FontBuilder(unitsPerEm=1000, isTTF=True)
|
||||||
|
fb.setupGlyphOrder(
|
||||||
|
[".notdef", "space", "A", "acutecomb", "Aacute", "zero", "one", "two"]
|
||||||
|
)
|
||||||
|
fb.setupCharacterMap(
|
||||||
|
{
|
||||||
|
0x20: "space",
|
||||||
|
0x41: "A",
|
||||||
|
0x0301: "acutecomb",
|
||||||
|
0xC1: "Aacute",
|
||||||
|
0x30: "zero",
|
||||||
|
0x31: "one",
|
||||||
|
0x32: "two",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fb.setupHorizontalMetrics(
|
||||||
|
{
|
||||||
|
".notdef": (500, 50),
|
||||||
|
"space": (600, 0),
|
||||||
|
"A": (550, 40),
|
||||||
|
"acutecomb": (0, -40),
|
||||||
|
"Aacute": (550, 40),
|
||||||
|
"zero": (500, 30),
|
||||||
|
"one": (500, 50),
|
||||||
|
"two": (500, 40),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fb.setupHorizontalHeader(ascent=1000, descent=-200)
|
||||||
|
|
||||||
|
srcGlyphs = {
|
||||||
|
".notdef": Glyph(xMin=50, xMax=450),
|
||||||
|
"space": Glyph(empty=True),
|
||||||
|
"A": Glyph(xMin=40, xMax=510),
|
||||||
|
"acutecomb": Glyph(xMin=-40, xMax=60),
|
||||||
|
"Aacute": CompositeGlyph([("A", (0, 0)), ("acutecomb", (200, 0))]),
|
||||||
|
"zero": Glyph(xMin=30, xMax=470),
|
||||||
|
"one": Glyph(xMin=50, xMax=450),
|
||||||
|
"two": Glyph(xMin=40, xMax=460),
|
||||||
|
}
|
||||||
|
pen = TTGlyphPen(srcGlyphs)
|
||||||
|
glyphSet = {}
|
||||||
|
for glyphName, glyph in srcGlyphs.items():
|
||||||
|
glyph.draw(pen)
|
||||||
|
glyphSet[glyphName] = pen.glyph()
|
||||||
|
fb.setupGlyf(glyphSet)
|
||||||
|
|
||||||
|
fb.setupNameTable(
|
||||||
|
{
|
||||||
|
"familyName": "TestWOFF2",
|
||||||
|
"styleName": "Regular",
|
||||||
|
"uniqueFontIdentifier": "TestWOFF2 Regular; Version 1.000; ABCD",
|
||||||
|
"fullName": "TestWOFF2 Regular",
|
||||||
|
"version": "Version 1.000",
|
||||||
|
"psName": "TestWOFF2-Regular",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
fb.setupOS2()
|
||||||
|
fb.setupPost()
|
||||||
|
|
||||||
|
buf = BytesIO()
|
||||||
|
fb.save(buf)
|
||||||
|
buf.seek(0)
|
||||||
|
|
||||||
|
assert fb.font["maxp"].numGlyphs == 8
|
||||||
|
assert fb.font["hhea"].numberOfHMetrics == 6
|
||||||
|
for glyphName in fb.font.getGlyphOrder():
|
||||||
|
xMin = getattr(fb.font["glyf"][glyphName], "xMin", 0)
|
||||||
|
assert xMin == fb.font["hmtx"][glyphName][1]
|
||||||
|
|
||||||
|
return buf
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def ttFont(fontfile):
|
||||||
|
return ttLib.TTFont(fontfile, recalcBBoxes=False, recalcTimestamp=False)
|
||||||
|
|
||||||
|
|
||||||
|
class WOFF2HmtxTableTest(object):
|
||||||
|
def test_transform_no_sidebearings(self, ttFont):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
hmtxTable.metrics = ttFont["hmtx"].metrics
|
||||||
|
|
||||||
|
data = hmtxTable.transform(ttFont)
|
||||||
|
|
||||||
|
assert data == (
|
||||||
|
b"\x03" # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
|
||||||
|
|
||||||
|
# advanceWidthArray
|
||||||
|
b'\x01\xf4' # .notdef: 500
|
||||||
|
b'\x02X' # space: 600
|
||||||
|
b'\x02&' # A: 550
|
||||||
|
b'\x00\x00' # acutecomb: 0
|
||||||
|
b'\x02&' # Aacute: 550
|
||||||
|
b'\x01\xf4' # zero: 500
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transform_proportional_sidebearings(self, ttFont):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
metrics = ttFont["hmtx"].metrics
|
||||||
|
# force one of the proportional glyphs to have its left sidebearing be
|
||||||
|
# different from its xMin (40)
|
||||||
|
metrics["A"] = (550, 39)
|
||||||
|
hmtxTable.metrics = metrics
|
||||||
|
|
||||||
|
assert ttFont["glyf"]["A"].xMin != metrics["A"][1]
|
||||||
|
|
||||||
|
data = hmtxTable.transform(ttFont)
|
||||||
|
|
||||||
|
assert data == (
|
||||||
|
b"\x02" # 00000010 | bits 0 unset: explicit proportional sidebearings
|
||||||
|
|
||||||
|
# advanceWidthArray
|
||||||
|
b'\x01\xf4' # .notdef: 500
|
||||||
|
b'\x02X' # space: 600
|
||||||
|
b'\x02&' # A: 550
|
||||||
|
b'\x00\x00' # acutecomb: 0
|
||||||
|
b'\x02&' # Aacute: 550
|
||||||
|
b'\x01\xf4' # zero: 500
|
||||||
|
|
||||||
|
# lsbArray
|
||||||
|
b'\x002' # .notdef: 50
|
||||||
|
b'\x00\x00' # space: 0
|
||||||
|
b"\x00'" # A: 39 (xMin: 40)
|
||||||
|
b'\xff\xd8' # acutecomb: -40
|
||||||
|
b'\x00(' # Aacute: 40
|
||||||
|
b'\x00\x1e' # zero: 30
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transform_monospaced_sidebearings(self, ttFont):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
metrics = ttFont["hmtx"].metrics
|
||||||
|
hmtxTable.metrics = metrics
|
||||||
|
|
||||||
|
# force one of the monospaced glyphs at the end of hmtx table to have
|
||||||
|
# its xMin different from its left sidebearing (50)
|
||||||
|
ttFont["glyf"]["one"].xMin = metrics["one"][1] + 1
|
||||||
|
|
||||||
|
data = hmtxTable.transform(ttFont)
|
||||||
|
|
||||||
|
assert data == (
|
||||||
|
b"\x01" # 00000001 | bits 1 unset: explicit monospaced sidebearings
|
||||||
|
|
||||||
|
# advanceWidthArray
|
||||||
|
b'\x01\xf4' # .notdef: 500
|
||||||
|
b'\x02X' # space: 600
|
||||||
|
b'\x02&' # A: 550
|
||||||
|
b'\x00\x00' # acutecomb: 0
|
||||||
|
b'\x02&' # Aacute: 550
|
||||||
|
b'\x01\xf4' # zero: 500
|
||||||
|
|
||||||
|
# leftSideBearingArray
|
||||||
|
b'\x002' # one: 50 (xMin: 51)
|
||||||
|
b'\x00(' # two: 40
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_transform_not_applicable(self, ttFont):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
metrics = ttFont["hmtx"].metrics
|
||||||
|
# force both a proportional and monospaced glyph to have sidebearings
|
||||||
|
# different from the respective xMin coordinates
|
||||||
|
metrics["A"] = (550, 39)
|
||||||
|
metrics["one"] = (500, 51)
|
||||||
|
hmtxTable.metrics = metrics
|
||||||
|
|
||||||
|
# 'None' signals to fall back using untransformed hmtx table data
|
||||||
|
assert hmtxTable.transform(ttFont) is None
|
||||||
|
|
||||||
|
def test_reconstruct_no_sidebearings(self, ttFont):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
|
||||||
|
data = (
|
||||||
|
b"\x03" # 00000011 | bits 0 and 1 are set (no sidebearings arrays)
|
||||||
|
|
||||||
|
# advanceWidthArray
|
||||||
|
b'\x01\xf4' # .notdef: 500
|
||||||
|
b'\x02X' # space: 600
|
||||||
|
b'\x02&' # A: 550
|
||||||
|
b'\x00\x00' # acutecomb: 0
|
||||||
|
b'\x02&' # Aacute: 550
|
||||||
|
b'\x01\xf4' # zero: 500
|
||||||
|
)
|
||||||
|
|
||||||
|
hmtxTable.reconstruct(data, ttFont)
|
||||||
|
|
||||||
|
assert hmtxTable.metrics == {
|
||||||
|
".notdef": (500, 50),
|
||||||
|
"space": (600, 0),
|
||||||
|
"A": (550, 40),
|
||||||
|
"acutecomb": (0, -40),
|
||||||
|
"Aacute": (550, 40),
|
||||||
|
"zero": (500, 30),
|
||||||
|
"one": (500, 50),
|
||||||
|
"two": (500, 40),
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_reconstruct_proportional_sidebearings(self, ttFont):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
|
||||||
|
data = (
|
||||||
|
b"\x02" # 00000010 | bits 0 unset: explicit proportional sidebearings
|
||||||
|
|
||||||
|
# advanceWidthArray
|
||||||
|
b'\x01\xf4' # .notdef: 500
|
||||||
|
b'\x02X' # space: 600
|
||||||
|
b'\x02&' # A: 550
|
||||||
|
b'\x00\x00' # acutecomb: 0
|
||||||
|
b'\x02&' # Aacute: 550
|
||||||
|
b'\x01\xf4' # zero: 500
|
||||||
|
|
||||||
|
# lsbArray
|
||||||
|
b'\x002' # .notdef: 50
|
||||||
|
b'\x00\x00' # space: 0
|
||||||
|
b"\x00'" # A: 39 (xMin: 40)
|
||||||
|
b'\xff\xd8' # acutecomb: -40
|
||||||
|
b'\x00(' # Aacute: 40
|
||||||
|
b'\x00\x1e' # zero: 30
|
||||||
|
)
|
||||||
|
|
||||||
|
hmtxTable.reconstruct(data, ttFont)
|
||||||
|
|
||||||
|
assert hmtxTable.metrics == {
|
||||||
|
".notdef": (500, 50),
|
||||||
|
"space": (600, 0),
|
||||||
|
"A": (550, 39),
|
||||||
|
"acutecomb": (0, -40),
|
||||||
|
"Aacute": (550, 40),
|
||||||
|
"zero": (500, 30),
|
||||||
|
"one": (500, 50),
|
||||||
|
"two": (500, 40),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert ttFont["glyf"]["A"].xMin == 40
|
||||||
|
|
||||||
|
def test_reconstruct_monospaced_sidebearings(self, ttFont):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
|
||||||
|
data = (
|
||||||
|
b"\x01" # 00000001 | bits 1 unset: explicit monospaced sidebearings
|
||||||
|
|
||||||
|
# advanceWidthArray
|
||||||
|
b'\x01\xf4' # .notdef: 500
|
||||||
|
b'\x02X' # space: 600
|
||||||
|
b'\x02&' # A: 550
|
||||||
|
b'\x00\x00' # acutecomb: 0
|
||||||
|
b'\x02&' # Aacute: 550
|
||||||
|
b'\x01\xf4' # zero: 500
|
||||||
|
|
||||||
|
# leftSideBearingArray
|
||||||
|
b'\x003' # one: 51 (xMin: 50)
|
||||||
|
b'\x00(' # two: 40
|
||||||
|
)
|
||||||
|
|
||||||
|
hmtxTable.reconstruct(data, ttFont)
|
||||||
|
|
||||||
|
assert hmtxTable.metrics == {
|
||||||
|
".notdef": (500, 50),
|
||||||
|
"space": (600, 0),
|
||||||
|
"A": (550, 40),
|
||||||
|
"acutecomb": (0, -40),
|
||||||
|
"Aacute": (550, 40),
|
||||||
|
"zero": (500, 30),
|
||||||
|
"one": (500, 51),
|
||||||
|
"two": (500, 40),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert ttFont["glyf"]["one"].xMin == 50
|
||||||
|
|
||||||
|
def test_reconstruct_flags_reserved_bits(self):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ttLib.TTLibError, match="Bits 2-7 of 'hmtx' flags are reserved"
|
||||||
|
):
|
||||||
|
hmtxTable.reconstruct(b"\xFF", ttFont=None)
|
||||||
|
|
||||||
|
def test_reconstruct_flags_required_bits(self):
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
|
||||||
|
with pytest.raises(ttLib.TTLibError, match="either bits 0 or 1 .* must set"):
|
||||||
|
hmtxTable.reconstruct(b"\x00", ttFont=None)
|
||||||
|
|
||||||
|
def test_reconstruct_too_much_data(self, ttFont):
|
||||||
|
ttFont["hhea"].numberOfHMetrics = 2
|
||||||
|
data = b'\x03\x01\xf4\x02X\x02&'
|
||||||
|
hmtxTable = WOFF2HmtxTable()
|
||||||
|
|
||||||
|
with pytest.raises(ttLib.TTLibError, match="too much 'hmtx' table data"):
|
||||||
|
hmtxTable.reconstruct(data, ttFont)
|
||||||
|
|
||||||
|
|
||||||
|
class WOFF2RoundtripTest(object):
|
||||||
|
@staticmethod
|
||||||
|
def roundtrip(infile):
|
||||||
|
infile.seek(0)
|
||||||
|
ttFont = ttLib.TTFont(infile, recalcBBoxes=False, recalcTimestamp=False)
|
||||||
|
outfile = BytesIO()
|
||||||
|
ttFont.save(outfile)
|
||||||
|
return outfile, ttFont
|
||||||
|
|
||||||
|
def test_roundtrip_default_transforms(self, ttFont):
|
||||||
|
ttFont.flavor = "woff2"
|
||||||
|
# ttFont.flavorData = None
|
||||||
|
tmp = BytesIO()
|
||||||
|
ttFont.save(tmp)
|
||||||
|
|
||||||
|
tmp2, ttFont2 = self.roundtrip(tmp)
|
||||||
|
|
||||||
|
assert tmp.getvalue() == tmp2.getvalue()
|
||||||
|
assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca"}
|
||||||
|
|
||||||
|
def test_roundtrip_no_transforms(self, ttFont):
|
||||||
|
ttFont.flavor = "woff2"
|
||||||
|
ttFont.flavorData = WOFF2FlavorData(transformedTables=[])
|
||||||
|
tmp = BytesIO()
|
||||||
|
ttFont.save(tmp)
|
||||||
|
|
||||||
|
tmp2, ttFont2 = self.roundtrip(tmp)
|
||||||
|
|
||||||
|
assert tmp.getvalue() == tmp2.getvalue()
|
||||||
|
assert not ttFont2.reader.flavorData.transformedTables
|
||||||
|
|
||||||
|
def test_roundtrip_all_transforms(self, ttFont):
|
||||||
|
ttFont.flavor = "woff2"
|
||||||
|
ttFont.flavorData = WOFF2FlavorData(transformedTables=["glyf", "loca", "hmtx"])
|
||||||
|
tmp = BytesIO()
|
||||||
|
ttFont.save(tmp)
|
||||||
|
|
||||||
|
tmp2, ttFont2 = self.roundtrip(tmp)
|
||||||
|
|
||||||
|
assert tmp.getvalue() == tmp2.getvalue()
|
||||||
|
assert ttFont2.reader.flavorData.transformedTables == {"glyf", "loca", "hmtx"}
|
||||||
|
|
||||||
|
def test_roundtrip_only_hmtx_no_glyf_transform(self, ttFont):
|
||||||
|
ttFont.flavor = "woff2"
|
||||||
|
ttFont.flavorData = WOFF2FlavorData(transformedTables=["hmtx"])
|
||||||
|
tmp = BytesIO()
|
||||||
|
ttFont.save(tmp)
|
||||||
|
|
||||||
|
tmp2, ttFont2 = self.roundtrip(tmp)
|
||||||
|
|
||||||
|
assert tmp.getvalue() == tmp2.getvalue()
|
||||||
|
assert ttFont2.reader.flavorData.transformedTables == {"hmtx"}
|
||||||
|
|
||||||
|
|
||||||
class Base128Test(unittest.TestCase):
|
class Base128Test(unittest.TestCase):
|
||||||
|
|
||||||
def test_unpackBase128(self):
|
def test_unpackBase128(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user