[woff2] Add support for encoding/decoding OVERLAP_SIMPLE glyf flags in WOFF2

Fixes #2576

This updates our woff2 encoder/decoder to support retaining the OVERLAP_SIMPLE glyf flag following the updated WOFF 2.0 specification and official google/woff2 implementation.

https://www.w3.org/TR/WOFF2/#glyf_table_format
This commit is contained in:
Cosimo Lupo 2022-11-09 13:56:46 +00:00
parent 12bf60aee0
commit b4e664da21
No known key found for this signature in database
GPG Key ID: DF65A8A5A119C9A8
2 changed files with 40 additions and 2 deletions

View File

@ -523,7 +523,8 @@ woff2TransformedTableTags = ('glyf', 'loca')
woff2GlyfTableFormat = """ woff2GlyfTableFormat = """
> # big endian > # big endian
version: L # = 0x00000000 version: H # = 0x0000
optionFlags: H # Bit 0: we have overlapSimpleBitmap[], Bits 1-15: reserved
numGlyphs: H # Number of glyphs numGlyphs: H # Number of glyphs
indexFormat: H # Offset format for loca table indexFormat: H # Offset format for loca table
nContourStreamSize: L # Size of nContour stream nContourStreamSize: L # Size of nContour stream
@ -545,6 +546,8 @@ bboxFormat = """
yMax: h yMax: h
""" """
woff2OverlapSimpleBitmapFlag = 0x0001
def getKnownTagIndex(tag): def getKnownTagIndex(tag):
"""Return index of 'tag' in woff2KnownTags list. Return 63 if not found.""" """Return index of 'tag' in woff2KnownTags list. Return 63 if not found."""
@ -690,6 +693,13 @@ class WOFF2GlyfTable(getTableClass('glyf')):
data = data[size:] data = data[size:]
offset += size offset += size
hasOverlapSimpleBitmap = self.optionFlags & woff2OverlapSimpleBitmapFlag
self.overlapSimpleBitmap = None
if hasOverlapSimpleBitmap:
overlapSimpleBitmapSize = (self.numGlyphs + 7) >> 3
self.overlapSimpleBitmap = array.array('B', data[:overlapSimpleBitmapSize])
offset += overlapSimpleBitmapSize
if offset != inputDataSize: if offset != inputDataSize:
raise TTLibError( raise TTLibError(
"incorrect size of transformed 'glyf' table: expected %d, received %d bytes" "incorrect size of transformed 'glyf' table: expected %d, received %d bytes"
@ -737,15 +747,22 @@ class WOFF2GlyfTable(getTableClass('glyf')):
bboxBitmapSize = ((self.numGlyphs + 31) >> 5) << 2 bboxBitmapSize = ((self.numGlyphs + 31) >> 5) << 2
self.bboxBitmap = array.array('B', [0]*bboxBitmapSize) self.bboxBitmap = array.array('B', [0]*bboxBitmapSize)
self.overlapSimpleBitmap = array.array('B', [0]*((self.numGlyphs + 7) >> 3))
for glyphID in range(self.numGlyphs): for glyphID in range(self.numGlyphs):
self._encodeGlyph(glyphID) self._encodeGlyph(glyphID)
hasOverlapSimpleBitmap = any(self.overlapSimpleBitmap)
self.bboxStream = self.bboxBitmap.tobytes() + self.bboxStream self.bboxStream = self.bboxBitmap.tobytes() + self.bboxStream
for stream in self.subStreams: for stream in self.subStreams:
setattr(self, stream + 'Size', len(getattr(self, stream))) setattr(self, stream + 'Size', len(getattr(self, stream)))
self.version = 0 self.version = 0
self.optionFlags = 0
if hasOverlapSimpleBitmap:
self.optionFlags |= woff2OverlapSimpleBitmapFlag
data = sstruct.pack(woff2GlyfTableFormat, self) data = sstruct.pack(woff2GlyfTableFormat, self)
data += bytesjoin([getattr(self, s) for s in self.subStreams]) data += bytesjoin([getattr(self, s) for s in self.subStreams])
if hasOverlapSimpleBitmap:
data += self.overlapSimpleBitmap.tobytes()
return data return data
def _decodeGlyph(self, glyphID): def _decodeGlyph(self, glyphID):
@ -757,6 +774,7 @@ class WOFF2GlyfTable(getTableClass('glyf')):
self._decodeComponents(glyph) self._decodeComponents(glyph)
else: else:
self._decodeCoordinates(glyph) self._decodeCoordinates(glyph)
self._decodeOverlapSimpleFlag(glyph, glyphID)
self._decodeBBox(glyphID, glyph) self._decodeBBox(glyphID, glyph)
return glyph return glyph
@ -787,6 +805,14 @@ class WOFF2GlyfTable(getTableClass('glyf')):
self._decodeTriplets(glyph) self._decodeTriplets(glyph)
self._decodeInstructions(glyph) self._decodeInstructions(glyph)
def _decodeOverlapSimpleFlag(self, glyph, glyphID):
if self.overlapSimpleBitmap is None or glyph.numberOfContours <= 0:
return
byte = glyphID >> 3
bit = glyphID & 7
if self.overlapSimpleBitmap[byte] & (0x80 >> bit):
glyph.flags[0] |= _g_l_y_f.flagOverlapSimple
def _decodeInstructions(self, glyph): def _decodeInstructions(self, glyph):
glyphStream = self.glyphStream glyphStream = self.glyphStream
instructionStream = self.instructionStream instructionStream = self.instructionStream
@ -885,6 +911,7 @@ class WOFF2GlyfTable(getTableClass('glyf')):
self._encodeComponents(glyph) self._encodeComponents(glyph)
else: else:
self._encodeCoordinates(glyph) self._encodeCoordinates(glyph)
self._encodeOverlapSimpleFlag(glyph, glyphID)
self._encodeBBox(glyphID, glyph) self._encodeBBox(glyphID, glyph)
def _encodeComponents(self, glyph): def _encodeComponents(self, glyph):
@ -909,6 +936,14 @@ class WOFF2GlyfTable(getTableClass('glyf')):
self._encodeTriplets(glyph) self._encodeTriplets(glyph)
self._encodeInstructions(glyph) self._encodeInstructions(glyph)
def _encodeOverlapSimpleFlag(self, glyph, glyphID):
if glyph.numberOfContours <= 0:
return
if glyph.flags[0] & _g_l_y_f.flagOverlapSimple:
byte = glyphID >> 3
bit = glyphID & 7
self.overlapSimpleBitmap[byte] |= 0x80 >> bit
def _encodeInstructions(self, glyph): def _encodeInstructions(self, glyph):
instructions = glyph.program.getBytecode() instructions = glyph.program.getBytecode()
self.glyphStream += pack255UShort(len(instructions)) self.glyphStream += pack255UShort(len(instructions))

View File

@ -1239,7 +1239,10 @@ class WOFF2RoundtripTest(object):
_, ttFont2 = self.roundtrip(tmp) _, ttFont2 = self.roundtrip(tmp)
assert ttFont2.flavor == "woff2" assert ttFont2.flavor == "woff2"
assert ttFont2["glyf"]["A"].flags[0] == 0 # check that the off-curve point is still there
assert ttFont2["glyf"]["A"].flags[0] & _g_l_y_f.flagOnCurve == 0
# check that the overlap bit is still there
assert ttFont2["glyf"]["A"].flags[0] & _g_l_y_f.flagOverlapSimple != 0
class MainTest(object): class MainTest(object):