woff2: add support for unstransformed glyf and loca tables
Fixes https://github.com/fonttools/fonttools/issues/1636
This commit is contained in:
parent
1fc1d2f529
commit
c98b71af5c
@ -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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user