Repository: fonttools/fonttools

Issue:
[woff2] Add support for overlapSimpleBitmap
The [WOFF2 spec](https://www.w3.org/TR/WOFF2/#glyf_table_format) was updated to support retaining the OVERLAP_SIMPLE glyf flags, and the official encoder/decoder has recently been updated to support that: https://github.com/google/woff2/pull/149

Our own woff2 encoder/decoder also need to be updated.


Discussion:

Resolving patch:
diff --git a/Lib/fontTools/ttLib/woff2.py b/Lib/fontTools/ttLib/woff2.py
--- a/Lib/fontTools/ttLib/woff2.py
+++ b/Lib/fontTools/ttLib/woff2.py
@@ -523,7 +523,8 @@ def reordersTables(self):
 
 woff2GlyfTableFormat = """
 		> # big endian
-		version:                  L  # = 0x00000000
+		version:                  H  # = 0x0000
+		optionFlags:              H  # Bit 0: we have overlapSimpleBitmap[], Bits 1-15: reserved
 		numGlyphs:                H  # Number of glyphs
 		indexFormat:              H  # Offset format for loca table
 		nContourStreamSize:       L  # Size of nContour stream
@@ -545,6 +546,8 @@ def reordersTables(self):
 		yMax:				h
 """
 
+woff2OverlapSimpleBitmapFlag = 0x0001
+
 
 def getKnownTagIndex(tag):
 	"""Return index of 'tag' in woff2KnownTags list. Return 63 if not found."""
@@ -690,6 +693,13 @@ def reconstruct(self, data, ttFont):
 			data = data[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:
 			raise TTLibError(
 				"incorrect size of transformed 'glyf' table: expected %d, received %d bytes"
@@ -737,15 +747,22 @@ def transform(self, ttFont):
 		bboxBitmapSize = ((self.numGlyphs + 31) >> 5) << 2
 		self.bboxBitmap = array.array('B', [0]*bboxBitmapSize)
 
+		self.overlapSimpleBitmap = array.array('B', [0]*((self.numGlyphs + 7) >> 3))
 		for glyphID in range(self.numGlyphs):
 			self._encodeGlyph(glyphID)
+		hasOverlapSimpleBitmap = any(self.overlapSimpleBitmap)
 
 		self.bboxStream = self.bboxBitmap.tobytes() + self.bboxStream
 		for stream in self.subStreams:
 			setattr(self, stream + 'Size', len(getattr(self, stream)))
 		self.version = 0
+		self.optionFlags = 0
+		if hasOverlapSimpleBitmap:
+			self.optionFlags |= woff2OverlapSimpleBitmapFlag
 		data = sstruct.pack(woff2GlyfTableFormat, self)
 		data += bytesjoin([getattr(self, s) for s in self.subStreams])
+		if hasOverlapSimpleBitmap:
+			data += self.overlapSimpleBitmap.tobytes()
 		return data
 
 	def _decodeGlyph(self, glyphID):
@@ -757,6 +774,7 @@ def _decodeGlyph(self, glyphID):
 			self._decodeComponents(glyph)
 		else:
 			self._decodeCoordinates(glyph)
+			self._decodeOverlapSimpleFlag(glyph, glyphID)
 		self._decodeBBox(glyphID, glyph)
 		return glyph
 
@@ -787,6 +805,14 @@ def _decodeCoordinates(self, glyph):
 		self._decodeTriplets(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):
 		glyphStream = self.glyphStream
 		instructionStream = self.instructionStream
@@ -885,6 +911,7 @@ def _encodeGlyph(self, glyphID):
 			self._encodeComponents(glyph)
 		else:
 			self._encodeCoordinates(glyph)
+			self._encodeOverlapSimpleFlag(glyph, glyphID)
 		self._encodeBBox(glyphID, glyph)
 
 	def _encodeComponents(self, glyph):
@@ -909,6 +936,14 @@ def _encodeCoordinates(self, glyph):
 		self._encodeTriplets(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):
 		instructions = glyph.program.getBytecode()
 		self.glyphStream += pack255UShort(len(instructions))


Test patch:
diff --git a/Tests/ttLib/woff2_test.py b/Tests/ttLib/woff2_test.py
--- a/Tests/ttLib/woff2_test.py
+++ b/Tests/ttLib/woff2_test.py
@@ -1239,7 +1239,10 @@ def test_roundtrip_off_curve_despite_overlap_bit(self):
 
 		_, ttFont2 = self.roundtrip(tmp)
 		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):