woff2: add support for unstransformed glyf and loca tables

Fixes https://github.com/fonttools/fonttools/issues/1636
This commit is contained in:
Cosimo Lupo 2019-06-11 12:43:54 +01:00
parent 1fc1d2f529
commit c98b71af5c
No known key found for this signature in database
GPG Key ID: 20D4A261E4A0E642

View File

@ -82,7 +82,7 @@ class WOFF2Reader(SFNTReader):
"""Fetch the raw table data. Reconstruct transformed tables.""" """Fetch the raw table data. Reconstruct transformed tables."""
entry = self.tables[Tag(tag)] entry = self.tables[Tag(tag)]
if not hasattr(entry, 'data'): if not hasattr(entry, 'data'):
if tag in woff2TransformedTableTags: if entry.transformed:
entry.data = self.reconstructTable(tag) entry.data = self.reconstructTable(tag)
else: else:
entry.data = entry.loadData(self.transformBuffer) entry.data = entry.loadData(self.transformBuffer)
@ -90,8 +90,6 @@ class WOFF2Reader(SFNTReader):
def reconstructTable(self, tag): def reconstructTable(self, tag):
"""Reconstruct table named 'tag' from transformed data.""" """Reconstruct table named 'tag' from transformed data."""
if tag not in woff2TransformedTableTags:
raise TTLibError("transform for table '%s' is unknown" % tag)
entry = self.tables[Tag(tag)] entry = self.tables[Tag(tag)]
rawData = entry.loadData(self.transformBuffer) rawData = entry.loadData(self.transformBuffer)
if tag == 'glyf': if tag == 'glyf':
@ -101,7 +99,7 @@ class WOFF2Reader(SFNTReader):
elif tag == 'loca': elif tag == 'loca':
data = self._reconstructLoca() data = self._reconstructLoca()
else: else:
raise NotImplementedError raise TTLibError("transform for table '%s' is unknown" % tag)
return data return data
def _reconstructGlyf(self, data, padding=None): def _reconstructGlyf(self, data, padding=None):
@ -293,11 +291,23 @@ class WOFF2Writer(SFNTWriter):
def _transformTables(self): def _transformTables(self):
"""Return transformed font data.""" """Return transformed font data."""
transformedTables = self.flavorData.transformedTables
if (
"glyf" in transformedTables and "loca" not in transformedTables
or "loca" in transformedTables and "glyf" not in transformedTables
):
raise ValueError(
"'glyf' and 'loca' must be transformed (or not) together"
)
for tag, entry in self.tables.items(): for tag, entry in self.tables.items():
if tag in woff2TransformedTableTags: if tag in transformedTables:
data = self.transformTable(tag) data = self.transformTable(tag)
entry.transformed = True
else: else:
# pass-through the table data without transformation
data = entry.data data = entry.data
entry.transformed = False
entry.offset = self.nextTableOffset entry.offset = self.nextTableOffset
entry.saveData(self.transformBuffer, data) entry.saveData(self.transformBuffer, data)
self.nextTableOffset += entry.length self.nextTableOffset += entry.length
@ -307,8 +317,6 @@ class WOFF2Writer(SFNTWriter):
def transformTable(self, tag): def transformTable(self, tag):
"""Return transformed table data.""" """Return transformed table data."""
if tag not in woff2TransformedTableTags:
raise TTLibError("Transform for table '%s' is unknown" % tag)
if tag == "loca": if tag == "loca":
data = b"" data = b""
elif tag == "glyf": elif tag == "glyf":
@ -317,7 +325,7 @@ class WOFF2Writer(SFNTWriter):
glyfTable = self.ttFont['glyf'] glyfTable = self.ttFont['glyf']
data = glyfTable.transform(self.ttFont) data = glyfTable.transform(self.ttFont)
else: else:
raise NotImplementedError raise TTLibError("Transform for table '%s' is unknown" % tag)
return data return data
def _calcMasterChecksum(self): def _calcMasterChecksum(self):
@ -533,11 +541,9 @@ class WOFF2DirectoryEntry(DirectoryEntry):
# otherwise, tag is derived from a fixed 'Known Tags' table # otherwise, tag is derived from a fixed 'Known Tags' table
self.tag = woff2KnownTags[self.flags & 0x3F] self.tag = woff2KnownTags[self.flags & 0x3F]
self.tag = Tag(self.tag) self.tag = Tag(self.tag)
if self.flags & 0xC0 != 0:
raise TTLibError('bits 6-7 are reserved and must be 0')
self.origLength, data = unpackBase128(data) self.origLength, data = unpackBase128(data)
self.length = self.origLength self.length = self.origLength
if self.tag in woff2TransformedTableTags: if self.transformed:
self.length, data = unpackBase128(data) self.length, data = unpackBase128(data)
if self.tag == 'loca' and self.length != 0: if self.tag == 'loca' and self.length != 0:
raise TTLibError( raise TTLibError(
@ -550,10 +556,44 @@ class WOFF2DirectoryEntry(DirectoryEntry):
if (self.flags & 0x3F) == 0x3F: if (self.flags & 0x3F) == 0x3F:
data += struct.pack('>4s', self.tag.tobytes()) data += struct.pack('>4s', self.tag.tobytes())
data += packBase128(self.origLength) data += packBase128(self.origLength)
if self.tag in woff2TransformedTableTags: if self.transformed:
data += packBase128(self.length) data += packBase128(self.length)
return data return data
@property
def transformVersion(self):
"""Return bits 6-7 of table entry's flags, which indicate the preprocessing
transformation version number (between 0 and 3).
"""
return self.flags >> 6
@transformVersion.setter
def transformVersion(self, value):
assert 0 <= value <= 3
self.flags |= value << 6
@property
def transformed(self):
"""Return True if the table has any transformation, else return False."""
# For all tables in a font, except for 'glyf' and 'loca', the transformation
# version 0 indicates the null transform (where the original table data is
# passed directly to the Brotli compressor). For 'glyf' and 'loca' tables,
# transformation version 3 indicates the null transform
if self.tag in {"glyf", "loca"}:
return self.transformVersion != 3
else:
return self.transformVersion != 0
@transformed.setter
def transformed(self, booleanValue):
# here we assume that a non-null transform means version 0 for 'glyf' and
# 'loca' and 1 for every other table (e.g. hmtx); but that may change as
# new transformation formats are introduced in the future (if ever).
if self.tag in {"glyf", "loca"}:
self.transformVersion = 3 if not booleanValue else 0
else:
self.transformVersion = int(booleanValue)
class WOFF2LocaTable(getTableClass('loca')): class WOFF2LocaTable(getTableClass('loca')):
"""Same as parent class. The only difference is that it attempts to preserve """Same as parent class. The only difference is that it attempts to preserve
@ -913,9 +953,22 @@ class WOFF2FlavorData(WOFFFlavorData):
Flavor = 'woff2' Flavor = 'woff2'
def __init__(self, reader=None): def __init__(self, reader=None, transformedTables=None):
"""Data class that holds the WOFF2 header major/minor version, any
metadata or private data (as bytes strings), and the set of
table tags that have transformations applied (if reader is not None),
or will have once the WOFF2 font is compiled.
"""
if not haveBrotli: if not haveBrotli:
raise ImportError("No module named brotli") raise ImportError("No module named brotli")
if reader is not None and transformedTables is not None:
raise TypeError(
"'reader' and 'transformedTables' arguments are mutually exclusive"
)
if transformedTables is None:
transformedTables = woff2TransformedTableTags
self.majorVersion = None self.majorVersion = None
self.minorVersion = None self.minorVersion = None
self.metaData = None self.metaData = None
@ -935,6 +988,13 @@ class WOFF2FlavorData(WOFFFlavorData):
data = reader.file.read(reader.privLength) data = reader.file.read(reader.privLength)
assert len(data) == reader.privLength assert len(data) == reader.privLength
self.privData = data self.privData = data
transformedTables = [
tag
for tag, entry in reader.tables.items()
if entry.transformed
]
self.transformedTables = set(transformedTables)
def unpackBase128(data): def unpackBase128(data):