[glyf] Add optimizeSize option
Set to True by default. Can be turned to False on the table, or at Glyph() compile time. Also fixes Glyph's draw() to expand the glyph first. Otherwise it was failing.
This commit is contained in:
parent
afceebcda5
commit
e8146a6d07
@ -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()
|
||||
|
||||
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()
|
||||
|
@ -707,6 +707,37 @@ class GlyphComponentTest:
|
||||
'<component glyphName="a" firstPt="1" secondPt="2" flags="0x0"/>'
|
||||
]
|
||||
|
||||
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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user