diff --git a/Lib/fontTools/ttLib/tables/_g_l_y_f.py b/Lib/fontTools/ttLib/tables/_g_l_y_f.py index fa11cf8f4..bc7d4bf1e 100644 --- a/Lib/fontTools/ttLib/tables/_g_l_y_f.py +++ b/Lib/fontTools/ttLib/tables/_g_l_y_f.py @@ -713,7 +713,9 @@ class Glyph(object): else: self.decompileCoordinates(data) - def compile(self, glyfTable, recalcBBoxes=True, *, boundsDone=None): + def compile( + self, glyfTable, recalcBBoxes=True, *, boundsDone=None, optimizeSize=None + ): if hasattr(self, "data"): if recalcBBoxes: # must unpack glyph in order to recalculate bounding box @@ -730,7 +732,9 @@ class Glyph(object): if self.isComposite(): data = data + self.compileComponents(glyfTable) else: - data = data + self.compileCoordinates() + if optimizeSize is None: + optimizeSize = getattr(glyfTable, "optimizeSize", True) + data = data + self.compileCoordinates(optimizeSize=optimizeSize) return data def toXML(self, writer, ttFont): @@ -976,7 +980,7 @@ class Glyph(object): data = data + struct.pack(">h", len(instructions)) + instructions return data - def compileCoordinates(self): + def compileCoordinates(self, *, optimizeSize=True): assert len(self.coordinates) == len(self.flags) data = [] endPtsOfContours = array.array("H", self.endPtsOfContours) @@ -991,9 +995,12 @@ class Glyph(object): deltas.toInt() deltas.absoluteToRelative() - # TODO(behdad): Add a configuration option for this? - deltas = self.compileDeltasGreedy(self.flags, deltas) - # deltas = self.compileDeltasOptimal(self.flags, deltas) + if optimizeSize: + # TODO(behdad): Add a configuration option for this? + deltas = self.compileDeltasGreedy(self.flags, deltas) + # deltas = self.compileDeltasOptimal(self.flags, deltas) + else: + deltas = self.compileDeltasForSpeed(self.flags, deltas) data.extend(deltas) return b"".join(data) @@ -1110,6 +1117,63 @@ class Glyph(object): return (compressedFlags, compressedXs, compressedYs) + def compileDeltasForSpeed(self, flags, deltas): + # uses widest representation needed, for all deltas. + compressedFlags = bytearray() + compressedXs = bytearray() + compressedYs = bytearray() + + # Compute the necessary width for each axis + xs = [d[0] for d in deltas] + ys = [d[1] for d in deltas] + minX, minY, maxX, maxY = min(xs), min(ys), max(xs), max(ys) + xZero = minX == 0 and maxX == 0 + yZero = minY == 0 and maxY == 0 + xShort = -255 <= minX <= maxX <= 255 + yShort = -255 <= minY <= maxY <= 255 + + lastflag = None + repeat = 0 + for flag, (x, y) in zip(flags, deltas): + # Oh, the horrors of TrueType + # do x + if xZero: + flag = flag | flagXsame + elif xShort: + flag = flag | flagXShort + if x > 0: + flag = flag | flagXsame + else: + x = -x + compressedXs.append(x) + else: + compressedXs.extend(struct.pack(">h", x)) + # do y + if yZero: + flag = flag | flagYsame + elif yShort: + flag = flag | flagYShort + if y > 0: + flag = flag | flagYsame + else: + y = -y + compressedYs.append(y) + else: + compressedYs.extend(struct.pack(">h", y)) + # handle repeating flags + if flag == lastflag and repeat != 255: + repeat = repeat + 1 + if repeat == 1: + compressedFlags.append(flag) + else: + compressedFlags[-2] = flag | flagRepeat + compressedFlags[-1] = repeat + else: + repeat = 0 + compressedFlags.append(flag) + lastflag = flag + return (compressedFlags, compressedXs, compressedYs) + def recalcBounds(self, glyfTable, *, boundsDone=None): """Recalculates the bounds of the glyph. @@ -1404,6 +1468,7 @@ class Glyph(object): pen.addComponent(glyphName, transform) return + self.expand(glyfTable) coordinates, endPts, flags = self.getCoordinates(glyfTable) if offset: coordinates = coordinates.copy() diff --git a/Tests/ttLib/tables/_g_l_y_f_test.py b/Tests/ttLib/tables/_g_l_y_f_test.py index 9a3fd2eaf..10e053c4d 100644 --- a/Tests/ttLib/tables/_g_l_y_f_test.py +++ b/Tests/ttLib/tables/_g_l_y_f_test.py @@ -707,6 +707,37 @@ class GlyphComponentTest: '' ] + def test_compile_for_speed(self): + glyph = Glyph() + glyph.numberOfContours = 1 + glyph.coordinates = GlyphCoordinates( + [(0, 0), (1, 0), (1, 0), (1, 1), (1, 1), (0, 1), (0, 1)] + ) + glyph.flags = array.array("B", [flagOnCurve] + [flagCubic] * 6) + glyph.endPtsOfContours = [6] + glyph.program = ttProgram.Program() + + glyph.expand(None) + sizeBytes = glyph.compile(None, optimizeSize=True) + glyph.expand(None) + speedBytes = glyph.compile(None, optimizeSize=False) + + assert len(sizeBytes) < len(speedBytes) + + for data in sizeBytes, speedBytes: + glyph = Glyph(data) + + pen = RecordingPen() + glyph.draw(pen, None) + + assert pen.value == [ + ("moveTo", ((0, 0),)), + ("curveTo", ((1, 0), (1, 0), (1.0, 0.5))), + ("curveTo", ((1, 1), (1, 1), (0.5, 1.0))), + ("curveTo", ((0, 1), (0, 1), (0, 0))), + ("closePath", ()), + ] + def test_fromXML_reference_points(self): comp = GlyphComponent() for name, attrs, content in parseXML(