Merge pull request #2261 from fonttools/faster-GlyphCoordinates
Faster glyph coordinates
This commit is contained in:
commit
3cfc87be71
@ -71,10 +71,10 @@ def strjoin(iterable, joiner=""):
|
|||||||
|
|
||||||
|
|
||||||
def tobytes(s, encoding="ascii", errors="strict"):
|
def tobytes(s, encoding="ascii", errors="strict"):
|
||||||
if not isinstance(s, bytes):
|
if isinstance(s, str):
|
||||||
return s.encode(encoding, errors)
|
return s.encode(encoding, errors)
|
||||||
else:
|
else:
|
||||||
return s
|
return bytes(s)
|
||||||
|
|
||||||
|
|
||||||
def tounicode(s, encoding="ascii", errors="strict"):
|
def tounicode(s, encoding="ascii", errors="strict"):
|
||||||
|
@ -2203,7 +2203,7 @@ def subset_glyphs(self, s):
|
|||||||
def remapComponentsFast(self, glyphidmap):
|
def remapComponentsFast(self, glyphidmap):
|
||||||
if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
|
if not self.data or struct.unpack(">h", self.data[:2])[0] >= 0:
|
||||||
return # Not composite
|
return # Not composite
|
||||||
data = array.array("B", self.data)
|
data = self.data = bytearray(self.data)
|
||||||
i = 10
|
i = 10
|
||||||
more = 1
|
more = 1
|
||||||
while more:
|
while more:
|
||||||
@ -2223,8 +2223,6 @@ def remapComponentsFast(self, glyphidmap):
|
|||||||
elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
|
elif flags & 0x0080: i += 8 # WE_HAVE_A_TWO_BY_TWO
|
||||||
more = flags & 0x0020 # MORE_COMPONENTS
|
more = flags & 0x0020 # MORE_COMPONENTS
|
||||||
|
|
||||||
self.data = data.tobytes()
|
|
||||||
|
|
||||||
@_add_method(ttLib.getTableClass('glyf'))
|
@_add_method(ttLib.getTableClass('glyf'))
|
||||||
def closure_glyphs(self, s):
|
def closure_glyphs(self, s):
|
||||||
glyphSet = self.glyphs
|
glyphSet = self.glyphs
|
||||||
@ -2247,7 +2245,7 @@ def prune_pre_subset(self, font, options):
|
|||||||
g = self[self.glyphOrder[0]]
|
g = self[self.glyphOrder[0]]
|
||||||
# Yay, easy!
|
# Yay, easy!
|
||||||
g.__dict__.clear()
|
g.__dict__.clear()
|
||||||
g.data = ""
|
g.data = b''
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@_add_method(ttLib.getTableClass('glyf'))
|
@_add_method(ttLib.getTableClass('glyf'))
|
||||||
@ -2262,7 +2260,7 @@ def subset_glyphs(self, s):
|
|||||||
Glyph = ttLib.getTableModule('glyf').Glyph
|
Glyph = ttLib.getTableModule('glyf').Glyph
|
||||||
for g in s.glyphs_emptied:
|
for g in s.glyphs_emptied:
|
||||||
self.glyphs[g] = Glyph()
|
self.glyphs[g] = Glyph()
|
||||||
self.glyphs[g].data = ''
|
self.glyphs[g].data = b''
|
||||||
self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs or g in s.glyphs_emptied]
|
self.glyphOrder = [g for g in self.glyphOrder if g in s.glyphs or g in s.glyphs_emptied]
|
||||||
# Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
|
# Don't drop empty 'glyf' tables, otherwise 'loca' doesn't get subset.
|
||||||
return True
|
return True
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from fontTools.misc.py23 import bytechr, byteord, bytesjoin
|
|
||||||
from fontTools.misc.fixedTools import (
|
from fontTools.misc.fixedTools import (
|
||||||
fixedToFloat as fi2fl,
|
fixedToFloat as fi2fl,
|
||||||
floatToFixed as fl2fi,
|
floatToFixed as fl2fi,
|
||||||
@ -8,6 +7,7 @@ from fontTools.misc.fixedTools import (
|
|||||||
)
|
)
|
||||||
from fontTools.misc.textTools import safeEval
|
from fontTools.misc.textTools import safeEval
|
||||||
import array
|
import array
|
||||||
|
from collections import Counter, defaultdict
|
||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
@ -38,7 +38,7 @@ class TupleVariation(object):
|
|||||||
|
|
||||||
def __init__(self, axes, coordinates):
|
def __init__(self, axes, coordinates):
|
||||||
self.axes = axes.copy()
|
self.axes = axes.copy()
|
||||||
self.coordinates = coordinates[:]
|
self.coordinates = list(coordinates)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
axes = ",".join(sorted(["%s=%s" % (name, value) for (name, value) in self.axes.items()]))
|
axes = ",".join(sorted(["%s=%s" % (name, value) for (name, value) in self.axes.items()]))
|
||||||
@ -48,11 +48,12 @@ class TupleVariation(object):
|
|||||||
return self.coordinates == other.coordinates and self.axes == other.axes
|
return self.coordinates == other.coordinates and self.axes == other.axes
|
||||||
|
|
||||||
def getUsedPoints(self):
|
def getUsedPoints(self):
|
||||||
result = set()
|
# Empty set means "all points used".
|
||||||
for i, point in enumerate(self.coordinates):
|
if None not in self.coordinates:
|
||||||
if point is not None:
|
return frozenset()
|
||||||
result.add(i)
|
used = frozenset([i for i,p in enumerate(self.coordinates) if p is not None])
|
||||||
return result
|
# Return None if no points used.
|
||||||
|
return used if used else None
|
||||||
|
|
||||||
def hasImpact(self):
|
def hasImpact(self):
|
||||||
"""Returns True if this TupleVariation has any visible impact.
|
"""Returns True if this TupleVariation has any visible impact.
|
||||||
@ -126,15 +127,21 @@ class TupleVariation(object):
|
|||||||
log.warning("bad delta format: %s" %
|
log.warning("bad delta format: %s" %
|
||||||
", ".join(sorted(attrs.keys())))
|
", ".join(sorted(attrs.keys())))
|
||||||
|
|
||||||
def compile(self, axisTags, sharedCoordIndices, sharedPoints):
|
def compile(self, axisTags, sharedCoordIndices={}, pointData=None):
|
||||||
tupleData = []
|
assert set(self.axes.keys()) <= set(axisTags), ("Unknown axis tag found.", self.axes.keys(), axisTags)
|
||||||
|
|
||||||
assert all(tag in axisTags for tag in self.axes.keys()), ("Unknown axis tag found.", self.axes.keys(), axisTags)
|
tupleData = []
|
||||||
|
auxData = []
|
||||||
|
|
||||||
|
if pointData is None:
|
||||||
|
usedPoints = self.getUsedPoints()
|
||||||
|
if usedPoints is None: # Nothing to encode
|
||||||
|
return b'', b''
|
||||||
|
pointData = self.compilePoints(usedPoints)
|
||||||
|
|
||||||
coord = self.compileCoord(axisTags)
|
coord = self.compileCoord(axisTags)
|
||||||
if coord in sharedCoordIndices:
|
flags = sharedCoordIndices.get(coord)
|
||||||
flags = sharedCoordIndices[coord]
|
if flags is None:
|
||||||
else:
|
|
||||||
flags = EMBEDDED_PEAK_TUPLE
|
flags = EMBEDDED_PEAK_TUPLE
|
||||||
tupleData.append(coord)
|
tupleData.append(coord)
|
||||||
|
|
||||||
@ -143,26 +150,27 @@ class TupleVariation(object):
|
|||||||
flags |= INTERMEDIATE_REGION
|
flags |= INTERMEDIATE_REGION
|
||||||
tupleData.append(intermediateCoord)
|
tupleData.append(intermediateCoord)
|
||||||
|
|
||||||
points = self.getUsedPoints()
|
# pointData of b'' implies "use shared points".
|
||||||
if sharedPoints == points:
|
if pointData:
|
||||||
# Only use the shared points if they are identical to the actually used points
|
|
||||||
auxData = self.compileDeltas(sharedPoints)
|
|
||||||
usesSharedPoints = True
|
|
||||||
else:
|
|
||||||
flags |= PRIVATE_POINT_NUMBERS
|
flags |= PRIVATE_POINT_NUMBERS
|
||||||
numPointsInGlyph = len(self.coordinates)
|
auxData.append(pointData)
|
||||||
auxData = self.compilePoints(points, numPointsInGlyph) + self.compileDeltas(points)
|
|
||||||
usesSharedPoints = False
|
|
||||||
|
|
||||||
tupleData = struct.pack('>HH', len(auxData), flags) + bytesjoin(tupleData)
|
auxData.append(self.compileDeltas())
|
||||||
return (tupleData, auxData, usesSharedPoints)
|
auxData = b''.join(auxData)
|
||||||
|
|
||||||
|
tupleData.insert(0, struct.pack('>HH', len(auxData), flags))
|
||||||
|
return b''.join(tupleData), auxData
|
||||||
|
|
||||||
def compileCoord(self, axisTags):
|
def compileCoord(self, axisTags):
|
||||||
result = []
|
result = bytearray()
|
||||||
|
axes = self.axes
|
||||||
for axis in axisTags:
|
for axis in axisTags:
|
||||||
_minValue, value, _maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
|
triple = axes.get(axis)
|
||||||
result.append(struct.pack(">h", fl2fi(value, 14)))
|
if triple is None:
|
||||||
return bytesjoin(result)
|
result.extend(b'\0\0')
|
||||||
|
else:
|
||||||
|
result.extend(struct.pack(">h", fl2fi(triple[1], 14)))
|
||||||
|
return bytes(result)
|
||||||
|
|
||||||
def compileIntermediateCoord(self, axisTags):
|
def compileIntermediateCoord(self, axisTags):
|
||||||
needed = False
|
needed = False
|
||||||
@ -175,13 +183,13 @@ class TupleVariation(object):
|
|||||||
break
|
break
|
||||||
if not needed:
|
if not needed:
|
||||||
return None
|
return None
|
||||||
minCoords = []
|
minCoords = bytearray()
|
||||||
maxCoords = []
|
maxCoords = bytearray()
|
||||||
for axis in axisTags:
|
for axis in axisTags:
|
||||||
minValue, value, maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
|
minValue, value, maxValue = self.axes.get(axis, (0.0, 0.0, 0.0))
|
||||||
minCoords.append(struct.pack(">h", fl2fi(minValue, 14)))
|
minCoords.extend(struct.pack(">h", fl2fi(minValue, 14)))
|
||||||
maxCoords.append(struct.pack(">h", fl2fi(maxValue, 14)))
|
maxCoords.extend(struct.pack(">h", fl2fi(maxValue, 14)))
|
||||||
return bytesjoin(minCoords + maxCoords)
|
return minCoords + maxCoords
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decompileCoord_(axisTags, data, offset):
|
def decompileCoord_(axisTags, data, offset):
|
||||||
@ -193,11 +201,15 @@ class TupleVariation(object):
|
|||||||
return coord, pos
|
return coord, pos
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compilePoints(points, numPointsInGlyph):
|
def compilePoints(points):
|
||||||
# If the set consists of all points in the glyph, it gets encoded with
|
# If the set consists of all points in the glyph, it gets encoded with
|
||||||
# a special encoding: a single zero byte.
|
# a special encoding: a single zero byte.
|
||||||
if len(points) == numPointsInGlyph:
|
#
|
||||||
return b"\0"
|
# To use this optimization, points passed in must be empty set.
|
||||||
|
# The following two lines are not strictly necessary as the main code
|
||||||
|
# below would emit the same. But this is most common and faster.
|
||||||
|
if not points:
|
||||||
|
return b'\0'
|
||||||
|
|
||||||
# In the 'gvar' table, the packing of point numbers is a little surprising.
|
# In the 'gvar' table, the packing of point numbers is a little surprising.
|
||||||
# It consists of multiple runs, each being a delta-encoded list of integers.
|
# It consists of multiple runs, each being a delta-encoded list of integers.
|
||||||
@ -209,19 +221,24 @@ class TupleVariation(object):
|
|||||||
points.sort()
|
points.sort()
|
||||||
numPoints = len(points)
|
numPoints = len(points)
|
||||||
|
|
||||||
|
result = bytearray()
|
||||||
# The binary representation starts with the total number of points in the set,
|
# The binary representation starts with the total number of points in the set,
|
||||||
# encoded into one or two bytes depending on the value.
|
# encoded into one or two bytes depending on the value.
|
||||||
if numPoints < 0x80:
|
if numPoints < 0x80:
|
||||||
result = [bytechr(numPoints)]
|
result.append(numPoints)
|
||||||
else:
|
else:
|
||||||
result = [bytechr((numPoints >> 8) | 0x80) + bytechr(numPoints & 0xff)]
|
result.append((numPoints >> 8) | 0x80)
|
||||||
|
result.append(numPoints & 0xff)
|
||||||
|
|
||||||
MAX_RUN_LENGTH = 127
|
MAX_RUN_LENGTH = 127
|
||||||
pos = 0
|
pos = 0
|
||||||
lastValue = 0
|
lastValue = 0
|
||||||
while pos < numPoints:
|
while pos < numPoints:
|
||||||
run = io.BytesIO()
|
|
||||||
runLength = 0
|
runLength = 0
|
||||||
|
|
||||||
|
headerPos = len(result)
|
||||||
|
result.append(0)
|
||||||
|
|
||||||
useByteEncoding = None
|
useByteEncoding = None
|
||||||
while pos < numPoints and runLength <= MAX_RUN_LENGTH:
|
while pos < numPoints and runLength <= MAX_RUN_LENGTH:
|
||||||
curValue = points[pos]
|
curValue = points[pos]
|
||||||
@ -234,38 +251,36 @@ class TupleVariation(object):
|
|||||||
# TODO This never switches back to a byte-encoding from a short-encoding.
|
# TODO This never switches back to a byte-encoding from a short-encoding.
|
||||||
# That's suboptimal.
|
# That's suboptimal.
|
||||||
if useByteEncoding:
|
if useByteEncoding:
|
||||||
run.write(bytechr(delta))
|
result.append(delta)
|
||||||
else:
|
else:
|
||||||
run.write(bytechr(delta >> 8))
|
result.append(delta >> 8)
|
||||||
run.write(bytechr(delta & 0xff))
|
result.append(delta & 0xff)
|
||||||
lastValue = curValue
|
lastValue = curValue
|
||||||
pos += 1
|
pos += 1
|
||||||
runLength += 1
|
runLength += 1
|
||||||
if useByteEncoding:
|
if useByteEncoding:
|
||||||
runHeader = bytechr(runLength - 1)
|
result[headerPos] = runLength - 1
|
||||||
else:
|
else:
|
||||||
runHeader = bytechr((runLength - 1) | POINTS_ARE_WORDS)
|
result[headerPos] = (runLength - 1) | POINTS_ARE_WORDS
|
||||||
result.append(runHeader)
|
|
||||||
result.append(run.getvalue())
|
|
||||||
|
|
||||||
return bytesjoin(result)
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decompilePoints_(numPoints, data, offset, tableTag):
|
def decompilePoints_(numPoints, data, offset, tableTag):
|
||||||
"""(numPoints, data, offset, tableTag) --> ([point1, point2, ...], newOffset)"""
|
"""(numPoints, data, offset, tableTag) --> ([point1, point2, ...], newOffset)"""
|
||||||
assert tableTag in ('cvar', 'gvar')
|
assert tableTag in ('cvar', 'gvar')
|
||||||
pos = offset
|
pos = offset
|
||||||
numPointsInData = byteord(data[pos])
|
numPointsInData = data[pos]
|
||||||
pos += 1
|
pos += 1
|
||||||
if (numPointsInData & POINTS_ARE_WORDS) != 0:
|
if (numPointsInData & POINTS_ARE_WORDS) != 0:
|
||||||
numPointsInData = (numPointsInData & POINT_RUN_COUNT_MASK) << 8 | byteord(data[pos])
|
numPointsInData = (numPointsInData & POINT_RUN_COUNT_MASK) << 8 | data[pos]
|
||||||
pos += 1
|
pos += 1
|
||||||
if numPointsInData == 0:
|
if numPointsInData == 0:
|
||||||
return (range(numPoints), pos)
|
return (range(numPoints), pos)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
while len(result) < numPointsInData:
|
while len(result) < numPointsInData:
|
||||||
runHeader = byteord(data[pos])
|
runHeader = data[pos]
|
||||||
pos += 1
|
pos += 1
|
||||||
numPointsInRun = (runHeader & POINT_RUN_COUNT_MASK) + 1
|
numPointsInRun = (runHeader & POINT_RUN_COUNT_MASK) + 1
|
||||||
point = 0
|
point = 0
|
||||||
@ -298,23 +313,28 @@ class TupleVariation(object):
|
|||||||
(",".join(sorted(badPoints)), tableTag))
|
(",".join(sorted(badPoints)), tableTag))
|
||||||
return (result, pos)
|
return (result, pos)
|
||||||
|
|
||||||
def compileDeltas(self, points):
|
def compileDeltas(self):
|
||||||
deltaX = []
|
deltaX = []
|
||||||
deltaY = []
|
deltaY = []
|
||||||
for p in sorted(list(points)):
|
if self.getCoordWidth() == 2:
|
||||||
c = self.coordinates[p]
|
for c in self.coordinates:
|
||||||
if type(c) is tuple and len(c) == 2:
|
if c is None:
|
||||||
|
continue
|
||||||
deltaX.append(c[0])
|
deltaX.append(c[0])
|
||||||
deltaY.append(c[1])
|
deltaY.append(c[1])
|
||||||
elif type(c) is int:
|
else:
|
||||||
|
for c in self.coordinates:
|
||||||
|
if c is None:
|
||||||
|
continue
|
||||||
deltaX.append(c)
|
deltaX.append(c)
|
||||||
elif c is not None:
|
bytearr = bytearray()
|
||||||
raise TypeError("invalid type of delta: %s" % type(c))
|
self.compileDeltaValues_(deltaX, bytearr)
|
||||||
return self.compileDeltaValues_(deltaX) + self.compileDeltaValues_(deltaY)
|
self.compileDeltaValues_(deltaY, bytearr)
|
||||||
|
return bytearr
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compileDeltaValues_(deltas):
|
def compileDeltaValues_(deltas, bytearr=None):
|
||||||
"""[value1, value2, value3, ...] --> bytestring
|
"""[value1, value2, value3, ...] --> bytearray
|
||||||
|
|
||||||
Emits a sequence of runs. Each run starts with a
|
Emits a sequence of runs. Each run starts with a
|
||||||
byte-sized header whose 6 least significant bits
|
byte-sized header whose 6 least significant bits
|
||||||
@ -329,38 +349,41 @@ class TupleVariation(object):
|
|||||||
bytes; if (header & 0x40) is set, the delta values are
|
bytes; if (header & 0x40) is set, the delta values are
|
||||||
signed 16-bit integers.
|
signed 16-bit integers.
|
||||||
""" # Explaining the format because the 'gvar' spec is hard to understand.
|
""" # Explaining the format because the 'gvar' spec is hard to understand.
|
||||||
stream = io.BytesIO()
|
if bytearr is None:
|
||||||
|
bytearr = bytearray()
|
||||||
pos = 0
|
pos = 0
|
||||||
while pos < len(deltas):
|
numDeltas = len(deltas)
|
||||||
|
while pos < numDeltas:
|
||||||
value = deltas[pos]
|
value = deltas[pos]
|
||||||
if value == 0:
|
if value == 0:
|
||||||
pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, stream)
|
pos = TupleVariation.encodeDeltaRunAsZeroes_(deltas, pos, bytearr)
|
||||||
elif value >= -128 and value <= 127:
|
elif -128 <= value <= 127:
|
||||||
pos = TupleVariation.encodeDeltaRunAsBytes_(deltas, pos, stream)
|
pos = TupleVariation.encodeDeltaRunAsBytes_(deltas, pos, bytearr)
|
||||||
else:
|
else:
|
||||||
pos = TupleVariation.encodeDeltaRunAsWords_(deltas, pos, stream)
|
pos = TupleVariation.encodeDeltaRunAsWords_(deltas, pos, bytearr)
|
||||||
return stream.getvalue()
|
return bytearr
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def encodeDeltaRunAsZeroes_(deltas, offset, stream):
|
def encodeDeltaRunAsZeroes_(deltas, offset, bytearr):
|
||||||
runLength = 0
|
|
||||||
pos = offset
|
pos = offset
|
||||||
numDeltas = len(deltas)
|
numDeltas = len(deltas)
|
||||||
while pos < numDeltas and runLength < 64 and deltas[pos] == 0:
|
while pos < numDeltas and deltas[pos] == 0:
|
||||||
pos += 1
|
pos += 1
|
||||||
runLength += 1
|
runLength = pos - offset
|
||||||
assert runLength >= 1 and runLength <= 64
|
while runLength >= 64:
|
||||||
stream.write(bytechr(DELTAS_ARE_ZERO | (runLength - 1)))
|
bytearr.append(DELTAS_ARE_ZERO | 63)
|
||||||
|
runLength -= 64
|
||||||
|
if runLength:
|
||||||
|
bytearr.append(DELTAS_ARE_ZERO | (runLength - 1))
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def encodeDeltaRunAsBytes_(deltas, offset, stream):
|
def encodeDeltaRunAsBytes_(deltas, offset, bytearr):
|
||||||
runLength = 0
|
|
||||||
pos = offset
|
pos = offset
|
||||||
numDeltas = len(deltas)
|
numDeltas = len(deltas)
|
||||||
while pos < numDeltas and runLength < 64:
|
while pos < numDeltas:
|
||||||
value = deltas[pos]
|
value = deltas[pos]
|
||||||
if value < -128 or value > 127:
|
if not (-128 <= value <= 127):
|
||||||
break
|
break
|
||||||
# Within a byte-encoded run of deltas, a single zero
|
# Within a byte-encoded run of deltas, a single zero
|
||||||
# is best stored literally as 0x00 value. However,
|
# is best stored literally as 0x00 value. However,
|
||||||
@ -373,19 +396,22 @@ class TupleVariation(object):
|
|||||||
if value == 0 and pos+1 < numDeltas and deltas[pos+1] == 0:
|
if value == 0 and pos+1 < numDeltas and deltas[pos+1] == 0:
|
||||||
break
|
break
|
||||||
pos += 1
|
pos += 1
|
||||||
runLength += 1
|
runLength = pos - offset
|
||||||
assert runLength >= 1 and runLength <= 64
|
while runLength >= 64:
|
||||||
stream.write(bytechr(runLength - 1))
|
bytearr.append(63)
|
||||||
for i in range(offset, pos):
|
bytearr.extend(array.array('b', deltas[offset:offset+64]))
|
||||||
stream.write(struct.pack('b', otRound(deltas[i])))
|
offset += 64
|
||||||
|
runLength -= 64
|
||||||
|
if runLength:
|
||||||
|
bytearr.append(runLength - 1)
|
||||||
|
bytearr.extend(array.array('b', deltas[offset:pos]))
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def encodeDeltaRunAsWords_(deltas, offset, stream):
|
def encodeDeltaRunAsWords_(deltas, offset, bytearr):
|
||||||
runLength = 0
|
|
||||||
pos = offset
|
pos = offset
|
||||||
numDeltas = len(deltas)
|
numDeltas = len(deltas)
|
||||||
while pos < numDeltas and runLength < 64:
|
while pos < numDeltas:
|
||||||
value = deltas[pos]
|
value = deltas[pos]
|
||||||
# Within a word-encoded run of deltas, it is easiest
|
# Within a word-encoded run of deltas, it is easiest
|
||||||
# to start a new run (with a different encoding)
|
# to start a new run (with a different encoding)
|
||||||
@ -403,15 +429,22 @@ class TupleVariation(object):
|
|||||||
# [0x6666, 2, 0x7777] becomes 7 bytes when storing
|
# [0x6666, 2, 0x7777] becomes 7 bytes when storing
|
||||||
# the value literally (42 66 66 00 02 77 77), but 8 bytes
|
# the value literally (42 66 66 00 02 77 77), but 8 bytes
|
||||||
# when starting a new run (40 66 66 00 02 40 77 77).
|
# when starting a new run (40 66 66 00 02 40 77 77).
|
||||||
isByteEncodable = lambda value: value >= -128 and value <= 127
|
if (-128 <= value <= 127) and pos+1 < numDeltas and (-128 <= deltas[pos+1] <= 127):
|
||||||
if isByteEncodable(value) and pos+1 < numDeltas and isByteEncodable(deltas[pos+1]):
|
|
||||||
break
|
break
|
||||||
pos += 1
|
pos += 1
|
||||||
runLength += 1
|
runLength = pos - offset
|
||||||
assert runLength >= 1 and runLength <= 64
|
while runLength >= 64:
|
||||||
stream.write(bytechr(DELTAS_ARE_WORDS | (runLength - 1)))
|
bytearr.append(DELTAS_ARE_WORDS | 63)
|
||||||
for i in range(offset, pos):
|
a = array.array('h', deltas[offset:offset+64])
|
||||||
stream.write(struct.pack('>h', otRound(deltas[i])))
|
if sys.byteorder != "big": a.byteswap()
|
||||||
|
bytearr.extend(a)
|
||||||
|
offset += 64
|
||||||
|
runLength -= 64
|
||||||
|
if runLength:
|
||||||
|
bytearr.append(DELTAS_ARE_WORDS | (runLength - 1))
|
||||||
|
a = array.array('h', deltas[offset:pos])
|
||||||
|
if sys.byteorder != "big": a.byteswap()
|
||||||
|
bytearr.extend(a)
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -420,7 +453,7 @@ class TupleVariation(object):
|
|||||||
result = []
|
result = []
|
||||||
pos = offset
|
pos = offset
|
||||||
while len(result) < numDeltas:
|
while len(result) < numDeltas:
|
||||||
runHeader = byteord(data[pos])
|
runHeader = data[pos]
|
||||||
pos += 1
|
pos += 1
|
||||||
numDeltasInRun = (runHeader & DELTA_RUN_COUNT_MASK) + 1
|
numDeltasInRun = (runHeader & DELTA_RUN_COUNT_MASK) + 1
|
||||||
if (runHeader & DELTAS_ARE_ZERO) != 0:
|
if (runHeader & DELTAS_ARE_ZERO) != 0:
|
||||||
@ -523,9 +556,9 @@ class TupleVariation(object):
|
|||||||
|
|
||||||
# Shouldn't matter that this is different from fvar...?
|
# Shouldn't matter that this is different from fvar...?
|
||||||
axisTags = sorted(self.axes.keys())
|
axisTags = sorted(self.axes.keys())
|
||||||
tupleData, auxData, _ = self.compile(axisTags, [], None)
|
tupleData, auxData = self.compile(axisTags)
|
||||||
unoptimizedLength = len(tupleData) + len(auxData)
|
unoptimizedLength = len(tupleData) + len(auxData)
|
||||||
tupleData, auxData, _ = varOpt.compile(axisTags, [], None)
|
tupleData, auxData = varOpt.compile(axisTags)
|
||||||
optimizedLength = len(tupleData) + len(auxData)
|
optimizedLength = len(tupleData) + len(auxData)
|
||||||
|
|
||||||
if optimizedLength < unoptimizedLength:
|
if optimizedLength < unoptimizedLength:
|
||||||
@ -577,87 +610,72 @@ def decompileSharedTuples(axisTags, sharedTupleCount, data, offset):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def compileSharedTuples(axisTags, variations):
|
def compileSharedTuples(axisTags, variations,
|
||||||
coordCount = {}
|
MAX_NUM_SHARED_COORDS = TUPLE_INDEX_MASK + 1):
|
||||||
|
coordCount = Counter()
|
||||||
for var in variations:
|
for var in variations:
|
||||||
coord = var.compileCoord(axisTags)
|
coord = var.compileCoord(axisTags)
|
||||||
coordCount[coord] = coordCount.get(coord, 0) + 1
|
coordCount[coord] += 1
|
||||||
sharedCoords = [(count, coord)
|
sharedCoords = coordCount.most_common(MAX_NUM_SHARED_COORDS)
|
||||||
for (coord, count) in coordCount.items() if count > 1]
|
return [c[0] for c in sharedCoords if c[1] > 1]
|
||||||
sharedCoords.sort(reverse=True)
|
|
||||||
MAX_NUM_SHARED_COORDS = TUPLE_INDEX_MASK + 1
|
|
||||||
sharedCoords = sharedCoords[:MAX_NUM_SHARED_COORDS]
|
|
||||||
return [c[1] for c in sharedCoords] # Strip off counts.
|
|
||||||
|
|
||||||
|
|
||||||
def compileTupleVariationStore(variations, pointCount,
|
def compileTupleVariationStore(variations, pointCount,
|
||||||
axisTags, sharedTupleIndices,
|
axisTags, sharedTupleIndices,
|
||||||
useSharedPoints=True):
|
useSharedPoints=True):
|
||||||
variations = [v for v in variations if v.hasImpact()]
|
newVariations = []
|
||||||
if len(variations) == 0:
|
pointDatas = []
|
||||||
|
# Compile all points and figure out sharing if desired
|
||||||
|
sharedPoints = None
|
||||||
|
|
||||||
|
# Collect, count, and compile point-sets for all variation sets
|
||||||
|
pointSetCount = defaultdict(int)
|
||||||
|
for v in variations:
|
||||||
|
points = v.getUsedPoints()
|
||||||
|
if points is None: # Empty variations
|
||||||
|
continue
|
||||||
|
pointSetCount[points] += 1
|
||||||
|
newVariations.append(v)
|
||||||
|
pointDatas.append(points)
|
||||||
|
variations = newVariations
|
||||||
|
del newVariations
|
||||||
|
|
||||||
|
if not variations:
|
||||||
return (0, b"", b"")
|
return (0, b"", b"")
|
||||||
|
|
||||||
# Each glyph variation tuples modifies a set of control points. To
|
n = len(variations[0].coordinates)
|
||||||
# indicate which exact points are getting modified, a single tuple
|
assert all(len(v.coordinates) == n for v in variations), "Variation sets have different sizes"
|
||||||
# can either refer to a shared set of points, or the tuple can
|
|
||||||
# supply its private point numbers. Because the impact of sharing
|
|
||||||
# can be positive (no need for a private point list) or negative
|
|
||||||
# (need to supply 0,0 deltas for unused points), it is not obvious
|
|
||||||
# how to determine which tuples should take their points from the
|
|
||||||
# shared pool versus have their own. Perhaps we should resort to
|
|
||||||
# brute force, and try all combinations? However, if a glyph has n
|
|
||||||
# variation tuples, we would need to try 2^n combinations (because
|
|
||||||
# each tuple may or may not be part of the shared set). How many
|
|
||||||
# variations tuples do glyphs have?
|
|
||||||
#
|
|
||||||
# Skia.ttf: {3: 1, 5: 11, 6: 41, 7: 62, 8: 387, 13: 1, 14: 3}
|
|
||||||
# JamRegular.ttf: {3: 13, 4: 122, 5: 1, 7: 4, 8: 1, 9: 1, 10: 1}
|
|
||||||
# BuffaloGalRegular.ttf: {1: 16, 2: 13, 4: 2, 5: 4, 6: 19, 7: 1, 8: 3, 9: 8}
|
|
||||||
# (Reading example: In Skia.ttf, 41 glyphs have 6 variation tuples).
|
|
||||||
#
|
|
||||||
|
|
||||||
# Is this even worth optimizing? If we never use a shared point
|
compiledPoints = {pointSet:TupleVariation.compilePoints(pointSet)
|
||||||
# list, the private lists will consume 112K for Skia, 5K for
|
for pointSet in pointSetCount}
|
||||||
# BuffaloGalRegular, and 15K for JamRegular. If we always use a
|
|
||||||
# shared point list, the shared lists will consume 16K for Skia,
|
tupleVariationCount = len(variations)
|
||||||
# 3K for BuffaloGalRegular, and 10K for JamRegular. However, in
|
|
||||||
# the latter case the delta arrays will become larger, but I
|
|
||||||
# haven't yet measured by how much. From gut feeling (which may be
|
|
||||||
# wrong), the optimum is to share some but not all points;
|
|
||||||
# however, then we would need to try all combinations.
|
|
||||||
#
|
|
||||||
# For the time being, we try two variants and then pick the better one:
|
|
||||||
# (a) each tuple supplies its own private set of points;
|
|
||||||
# (b) all tuples refer to a shared set of points, which consists of
|
|
||||||
# "every control point in the glyph that has explicit deltas".
|
|
||||||
usedPoints = set()
|
|
||||||
for v in variations:
|
|
||||||
usedPoints |= v.getUsedPoints()
|
|
||||||
tuples = []
|
tuples = []
|
||||||
data = []
|
data = []
|
||||||
someTuplesSharePoints = False
|
|
||||||
sharedPointVariation = None # To keep track of a variation that uses shared points
|
if useSharedPoints:
|
||||||
for v in variations:
|
# Find point-set which saves most bytes.
|
||||||
privateTuple, privateData, _ = v.compile(
|
def key(pn):
|
||||||
axisTags, sharedTupleIndices, sharedPoints=None)
|
pointSet = pn[0]
|
||||||
sharedTuple, sharedData, usesSharedPoints = v.compile(
|
count = pn[1]
|
||||||
axisTags, sharedTupleIndices, sharedPoints=usedPoints)
|
return len(compiledPoints[pointSet]) * (count - 1)
|
||||||
if useSharedPoints and (len(sharedTuple) + len(sharedData)) < (len(privateTuple) + len(privateData)):
|
sharedPoints = max(pointSetCount.items(), key=key)[0]
|
||||||
tuples.append(sharedTuple)
|
|
||||||
data.append(sharedData)
|
data.append(compiledPoints[sharedPoints])
|
||||||
someTuplesSharePoints |= usesSharedPoints
|
tupleVariationCount |= TUPLES_SHARE_POINT_NUMBERS
|
||||||
sharedPointVariation = v
|
|
||||||
else:
|
# b'' implies "use shared points"
|
||||||
tuples.append(privateTuple)
|
pointDatas = [compiledPoints[points] if points != sharedPoints else b''
|
||||||
data.append(privateData)
|
for points in pointDatas]
|
||||||
if someTuplesSharePoints:
|
|
||||||
# Use the last of the variations that share points for compiling the packed point data
|
for v,p in zip(variations, pointDatas):
|
||||||
data = sharedPointVariation.compilePoints(usedPoints, len(sharedPointVariation.coordinates)) + bytesjoin(data)
|
thisTuple, thisData = v.compile(axisTags, sharedTupleIndices, pointData=p)
|
||||||
tupleVariationCount = TUPLES_SHARE_POINT_NUMBERS | len(tuples)
|
|
||||||
else:
|
tuples.append(thisTuple)
|
||||||
data = bytesjoin(data)
|
data.append(thisData)
|
||||||
tupleVariationCount = len(tuples)
|
|
||||||
tuples = bytesjoin(tuples)
|
tuples = b''.join(tuples)
|
||||||
|
data = b''.join(data)
|
||||||
return tupleVariationCount, tuples, data
|
return tupleVariationCount, tuples, data
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class table__c_v_a_r(DefaultTable.DefaultTable):
|
|||||||
"tupleVariationCount": tupleVariationCount,
|
"tupleVariationCount": tupleVariationCount,
|
||||||
"offsetToData": CVAR_HEADER_SIZE + len(tuples),
|
"offsetToData": CVAR_HEADER_SIZE + len(tuples),
|
||||||
}
|
}
|
||||||
return bytesjoin([
|
return b''.join([
|
||||||
sstruct.pack(CVAR_HEADER_FORMAT, header),
|
sstruct.pack(CVAR_HEADER_FORMAT, header),
|
||||||
tuples,
|
tuples,
|
||||||
data
|
data
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
|
"""_g_l_y_f.py -- Converter classes for the 'glyf' table."""
|
||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from fontTools.misc.py23 import bytechr, byteord, bytesjoin, tostr
|
from fontTools.misc.py23 import tostr
|
||||||
from fontTools.misc import sstruct
|
from fontTools.misc import sstruct
|
||||||
from fontTools import ttLib
|
from fontTools import ttLib
|
||||||
from fontTools import version
|
from fontTools import version
|
||||||
@ -117,7 +117,7 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
|||||||
currentLocation += len(glyphData)
|
currentLocation += len(glyphData)
|
||||||
locations[len(dataList)] = currentLocation
|
locations[len(dataList)] = currentLocation
|
||||||
|
|
||||||
data = bytesjoin(dataList)
|
data = b''.join(dataList)
|
||||||
if 'loca' in ttFont:
|
if 'loca' in ttFont:
|
||||||
ttFont['loca'].set(locations)
|
ttFont['loca'].set(locations)
|
||||||
if 'maxp' in ttFont:
|
if 'maxp' in ttFont:
|
||||||
@ -488,8 +488,7 @@ def flagEncodeCoord(flag, mask, coord, coordBytes):
|
|||||||
elif byteCount == -1:
|
elif byteCount == -1:
|
||||||
coordBytes.append(-coord)
|
coordBytes.append(-coord)
|
||||||
elif byteCount == 2:
|
elif byteCount == 2:
|
||||||
coordBytes.append((coord >> 8) & 0xFF)
|
coordBytes.extend(struct.pack('>h', coord))
|
||||||
coordBytes.append(coord & 0xFF)
|
|
||||||
|
|
||||||
def flagEncodeCoords(flag, x, y, xBytes, yBytes):
|
def flagEncodeCoords(flag, x, y, xBytes, yBytes):
|
||||||
flagEncodeCoord(flag, flagXsame|flagXShort, x, xBytes)
|
flagEncodeCoord(flag, flagXsame|flagXShort, x, xBytes)
|
||||||
@ -516,7 +515,7 @@ CompositeMaxpValues = namedtuple('CompositeMaxpValues', ['nPoints', 'nContours',
|
|||||||
|
|
||||||
class Glyph(object):
|
class Glyph(object):
|
||||||
|
|
||||||
def __init__(self, data=""):
|
def __init__(self, data=b""):
|
||||||
if not data:
|
if not data:
|
||||||
# empty char
|
# empty char
|
||||||
self.numberOfContours = 0
|
self.numberOfContours = 0
|
||||||
@ -557,7 +556,7 @@ class Glyph(object):
|
|||||||
else:
|
else:
|
||||||
return self.data
|
return self.data
|
||||||
if self.numberOfContours == 0:
|
if self.numberOfContours == 0:
|
||||||
return ""
|
return b''
|
||||||
if recalcBBoxes:
|
if recalcBBoxes:
|
||||||
self.recalcBounds(glyfTable)
|
self.recalcBounds(glyfTable)
|
||||||
data = sstruct.pack(glyphHeaderFormat, self)
|
data = sstruct.pack(glyphHeaderFormat, self)
|
||||||
@ -608,7 +607,7 @@ class Glyph(object):
|
|||||||
raise ttLib.TTLibError("can't mix composites and contours in glyph")
|
raise ttLib.TTLibError("can't mix composites and contours in glyph")
|
||||||
self.numberOfContours = self.numberOfContours + 1
|
self.numberOfContours = self.numberOfContours + 1
|
||||||
coordinates = GlyphCoordinates()
|
coordinates = GlyphCoordinates()
|
||||||
flags = []
|
flags = bytearray()
|
||||||
for element in content:
|
for element in content:
|
||||||
if not isinstance(element, tuple):
|
if not isinstance(element, tuple):
|
||||||
continue
|
continue
|
||||||
@ -616,11 +615,10 @@ class Glyph(object):
|
|||||||
if name != "pt":
|
if name != "pt":
|
||||||
continue # ignore anything but "pt"
|
continue # ignore anything but "pt"
|
||||||
coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
|
coordinates.append((safeEval(attrs["x"]), safeEval(attrs["y"])))
|
||||||
flag = not not safeEval(attrs["on"])
|
flag = bool(safeEval(attrs["on"]))
|
||||||
if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
|
if "overlap" in attrs and bool(safeEval(attrs["overlap"])):
|
||||||
flag |= flagOverlapSimple
|
flag |= flagOverlapSimple
|
||||||
flags.append(flag)
|
flags.append(flag)
|
||||||
flags = array.array("B", flags)
|
|
||||||
if not hasattr(self, "coordinates"):
|
if not hasattr(self, "coordinates"):
|
||||||
self.coordinates = coordinates
|
self.coordinates = coordinates
|
||||||
self.flags = flags
|
self.flags = flags
|
||||||
@ -695,16 +693,14 @@ class Glyph(object):
|
|||||||
if sys.byteorder != "big": endPtsOfContours.byteswap()
|
if sys.byteorder != "big": endPtsOfContours.byteswap()
|
||||||
self.endPtsOfContours = endPtsOfContours.tolist()
|
self.endPtsOfContours = endPtsOfContours.tolist()
|
||||||
|
|
||||||
data = data[2*self.numberOfContours:]
|
pos = 2*self.numberOfContours
|
||||||
|
instructionLength, = struct.unpack(">h", data[pos:pos+2])
|
||||||
instructionLength, = struct.unpack(">h", data[:2])
|
|
||||||
data = data[2:]
|
|
||||||
self.program = ttProgram.Program()
|
self.program = ttProgram.Program()
|
||||||
self.program.fromBytecode(data[:instructionLength])
|
self.program.fromBytecode(data[pos+2:pos+2+instructionLength])
|
||||||
data = data[instructionLength:]
|
pos += 2 + instructionLength
|
||||||
nCoordinates = self.endPtsOfContours[-1] + 1
|
nCoordinates = self.endPtsOfContours[-1] + 1
|
||||||
flags, xCoordinates, yCoordinates = \
|
flags, xCoordinates, yCoordinates = \
|
||||||
self.decompileCoordinatesRaw(nCoordinates, data)
|
self.decompileCoordinatesRaw(nCoordinates, data, pos)
|
||||||
|
|
||||||
# fill in repetitions and apply signs
|
# fill in repetitions and apply signs
|
||||||
self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
|
self.coordinates = coordinates = GlyphCoordinates.zeros(nCoordinates)
|
||||||
@ -741,24 +737,26 @@ class Glyph(object):
|
|||||||
assert yIndex == len(yCoordinates)
|
assert yIndex == len(yCoordinates)
|
||||||
coordinates.relativeToAbsolute()
|
coordinates.relativeToAbsolute()
|
||||||
# discard all flags except "keepFlags"
|
# discard all flags except "keepFlags"
|
||||||
self.flags = array.array("B", (f & keepFlags for f in flags))
|
for i in range(len(flags)):
|
||||||
|
flags[i] &= keepFlags
|
||||||
|
self.flags = flags
|
||||||
|
|
||||||
def decompileCoordinatesRaw(self, nCoordinates, data):
|
def decompileCoordinatesRaw(self, nCoordinates, data, pos=0):
|
||||||
# unpack flags and prepare unpacking of coordinates
|
# unpack flags and prepare unpacking of coordinates
|
||||||
flags = array.array("B", [0] * nCoordinates)
|
flags = bytearray(nCoordinates)
|
||||||
# Warning: deep Python trickery going on. We use the struct module to unpack
|
# Warning: deep Python trickery going on. We use the struct module to unpack
|
||||||
# the coordinates. We build a format string based on the flags, so we can
|
# the coordinates. We build a format string based on the flags, so we can
|
||||||
# unpack the coordinates in one struct.unpack() call.
|
# unpack the coordinates in one struct.unpack() call.
|
||||||
xFormat = ">" # big endian
|
xFormat = ">" # big endian
|
||||||
yFormat = ">" # big endian
|
yFormat = ">" # big endian
|
||||||
i = j = 0
|
j = 0
|
||||||
while True:
|
while True:
|
||||||
flag = byteord(data[i])
|
flag = data[pos]
|
||||||
i = i + 1
|
pos += 1
|
||||||
repeat = 1
|
repeat = 1
|
||||||
if flag & flagRepeat:
|
if flag & flagRepeat:
|
||||||
repeat = byteord(data[i]) + 1
|
repeat = data[pos] + 1
|
||||||
i = i + 1
|
pos += 1
|
||||||
for k in range(repeat):
|
for k in range(repeat):
|
||||||
if flag & flagXShort:
|
if flag & flagXShort:
|
||||||
xFormat = xFormat + 'B'
|
xFormat = xFormat + 'B'
|
||||||
@ -773,15 +771,14 @@ class Glyph(object):
|
|||||||
if j >= nCoordinates:
|
if j >= nCoordinates:
|
||||||
break
|
break
|
||||||
assert j == nCoordinates, "bad glyph flags"
|
assert j == nCoordinates, "bad glyph flags"
|
||||||
data = data[i:]
|
|
||||||
# unpack raw coordinates, krrrrrr-tching!
|
# unpack raw coordinates, krrrrrr-tching!
|
||||||
xDataLen = struct.calcsize(xFormat)
|
xDataLen = struct.calcsize(xFormat)
|
||||||
yDataLen = struct.calcsize(yFormat)
|
yDataLen = struct.calcsize(yFormat)
|
||||||
if len(data) - (xDataLen + yDataLen) >= 4:
|
if len(data) - pos - (xDataLen + yDataLen) >= 4:
|
||||||
log.warning(
|
log.warning(
|
||||||
"too much glyph data: %d excess bytes", len(data) - (xDataLen + yDataLen))
|
"too much glyph data: %d excess bytes", len(data) - pos - (xDataLen + yDataLen))
|
||||||
xCoordinates = struct.unpack(xFormat, data[:xDataLen])
|
xCoordinates = struct.unpack(xFormat, data[pos:pos+xDataLen])
|
||||||
yCoordinates = struct.unpack(yFormat, data[xDataLen:xDataLen+yDataLen])
|
yCoordinates = struct.unpack(yFormat, data[pos+xDataLen:pos+xDataLen+yDataLen])
|
||||||
return flags, xCoordinates, yCoordinates
|
return flags, xCoordinates, yCoordinates
|
||||||
|
|
||||||
def compileComponents(self, glyfTable):
|
def compileComponents(self, glyfTable):
|
||||||
@ -811,8 +808,6 @@ class Glyph(object):
|
|||||||
data.append(instructions)
|
data.append(instructions)
|
||||||
|
|
||||||
deltas = self.coordinates.copy()
|
deltas = self.coordinates.copy()
|
||||||
if deltas.isFloat():
|
|
||||||
# Warn?
|
|
||||||
deltas.toInt()
|
deltas.toInt()
|
||||||
deltas.absoluteToRelative()
|
deltas.absoluteToRelative()
|
||||||
|
|
||||||
@ -821,14 +816,14 @@ class Glyph(object):
|
|||||||
#deltas = self.compileDeltasOptimal(self.flags, deltas)
|
#deltas = self.compileDeltasOptimal(self.flags, deltas)
|
||||||
|
|
||||||
data.extend(deltas)
|
data.extend(deltas)
|
||||||
return bytesjoin(data)
|
return b''.join(data)
|
||||||
|
|
||||||
def compileDeltasGreedy(self, flags, deltas):
|
def compileDeltasGreedy(self, flags, deltas):
|
||||||
# Implements greedy algorithm for packing coordinate deltas:
|
# Implements greedy algorithm for packing coordinate deltas:
|
||||||
# uses shortest representation one coordinate at a time.
|
# uses shortest representation one coordinate at a time.
|
||||||
compressedflags = []
|
compressedFlags = bytearray()
|
||||||
xPoints = []
|
compressedXs = bytearray()
|
||||||
yPoints = []
|
compressedYs = bytearray()
|
||||||
lastflag = None
|
lastflag = None
|
||||||
repeat = 0
|
repeat = 0
|
||||||
for flag,(x,y) in zip(flags, deltas):
|
for flag,(x,y) in zip(flags, deltas):
|
||||||
@ -842,9 +837,9 @@ class Glyph(object):
|
|||||||
flag = flag | flagXsame
|
flag = flag | flagXsame
|
||||||
else:
|
else:
|
||||||
x = -x
|
x = -x
|
||||||
xPoints.append(bytechr(x))
|
compressedXs.append(x)
|
||||||
else:
|
else:
|
||||||
xPoints.append(struct.pack(">h", x))
|
compressedXs.extend(struct.pack('>h', x))
|
||||||
# do y
|
# do y
|
||||||
if y == 0:
|
if y == 0:
|
||||||
flag = flag | flagYsame
|
flag = flag | flagYsame
|
||||||
@ -854,24 +849,21 @@ class Glyph(object):
|
|||||||
flag = flag | flagYsame
|
flag = flag | flagYsame
|
||||||
else:
|
else:
|
||||||
y = -y
|
y = -y
|
||||||
yPoints.append(bytechr(y))
|
compressedYs.append(y)
|
||||||
else:
|
else:
|
||||||
yPoints.append(struct.pack(">h", y))
|
compressedYs.extend(struct.pack('>h', y))
|
||||||
# handle repeating flags
|
# handle repeating flags
|
||||||
if flag == lastflag and repeat != 255:
|
if flag == lastflag and repeat != 255:
|
||||||
repeat = repeat + 1
|
repeat = repeat + 1
|
||||||
if repeat == 1:
|
if repeat == 1:
|
||||||
compressedflags.append(flag)
|
compressedFlags.append(flag)
|
||||||
else:
|
else:
|
||||||
compressedflags[-2] = flag | flagRepeat
|
compressedFlags[-2] = flag | flagRepeat
|
||||||
compressedflags[-1] = repeat
|
compressedFlags[-1] = repeat
|
||||||
else:
|
else:
|
||||||
repeat = 0
|
repeat = 0
|
||||||
compressedflags.append(flag)
|
compressedFlags.append(flag)
|
||||||
lastflag = flag
|
lastflag = flag
|
||||||
compressedFlags = array.array("B", compressedflags).tobytes()
|
|
||||||
compressedXs = bytesjoin(xPoints)
|
|
||||||
compressedYs = bytesjoin(yPoints)
|
|
||||||
return (compressedFlags, compressedXs, compressedYs)
|
return (compressedFlags, compressedXs, compressedYs)
|
||||||
|
|
||||||
def compileDeltasOptimal(self, flags, deltas):
|
def compileDeltasOptimal(self, flags, deltas):
|
||||||
@ -902,9 +894,9 @@ class Glyph(object):
|
|||||||
flags.append(flag)
|
flags.append(flag)
|
||||||
flags.reverse()
|
flags.reverse()
|
||||||
|
|
||||||
compressedFlags = array.array("B")
|
compressedFlags = bytearray()
|
||||||
compressedXs = array.array("B")
|
compressedXs = bytearray()
|
||||||
compressedYs = array.array("B")
|
compressedYs = bytearray()
|
||||||
coords = iter(deltas)
|
coords = iter(deltas)
|
||||||
ff = []
|
ff = []
|
||||||
for flag in flags:
|
for flag in flags:
|
||||||
@ -924,9 +916,6 @@ class Glyph(object):
|
|||||||
raise Exception("internal error")
|
raise Exception("internal error")
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
pass
|
pass
|
||||||
compressedFlags = compressedFlags.tobytes()
|
|
||||||
compressedXs = compressedXs.tobytes()
|
|
||||||
compressedYs = compressedYs.tobytes()
|
|
||||||
|
|
||||||
return (compressedFlags, compressedXs, compressedYs)
|
return (compressedFlags, compressedXs, compressedYs)
|
||||||
|
|
||||||
@ -1006,7 +995,7 @@ class Glyph(object):
|
|||||||
elif self.isComposite():
|
elif self.isComposite():
|
||||||
# it's a composite
|
# it's a composite
|
||||||
allCoords = GlyphCoordinates()
|
allCoords = GlyphCoordinates()
|
||||||
allFlags = array.array("B")
|
allFlags = bytearray()
|
||||||
allEndPts = []
|
allEndPts = []
|
||||||
for compo in self.components:
|
for compo in self.components:
|
||||||
g = glyfTable[compo.glyphName]
|
g = glyfTable[compo.glyphName]
|
||||||
@ -1051,7 +1040,7 @@ class Glyph(object):
|
|||||||
allFlags.extend(flags)
|
allFlags.extend(flags)
|
||||||
return allCoords, allEndPts, allFlags
|
return allCoords, allEndPts, allFlags
|
||||||
else:
|
else:
|
||||||
return GlyphCoordinates(), [], array.array("B")
|
return GlyphCoordinates(), [], bytearray()
|
||||||
|
|
||||||
def getComponentNames(self, glyfTable):
|
def getComponentNames(self, glyfTable):
|
||||||
if not hasattr(self, "data"):
|
if not hasattr(self, "data"):
|
||||||
@ -1101,7 +1090,7 @@ class Glyph(object):
|
|||||||
if not self.data:
|
if not self.data:
|
||||||
return
|
return
|
||||||
numContours = struct.unpack(">h", self.data[:2])[0]
|
numContours = struct.unpack(">h", self.data[:2])[0]
|
||||||
data = array.array("B", self.data)
|
data = bytearray(self.data)
|
||||||
i = 10
|
i = 10
|
||||||
if numContours >= 0:
|
if numContours >= 0:
|
||||||
i += 2 * numContours # endPtsOfContours
|
i += 2 * numContours # endPtsOfContours
|
||||||
@ -1170,7 +1159,7 @@ class Glyph(object):
|
|||||||
# Remove padding
|
# Remove padding
|
||||||
data = data[:i]
|
data = data[:i]
|
||||||
|
|
||||||
self.data = data.tobytes()
|
self.data = data
|
||||||
|
|
||||||
def removeHinting(self):
|
def removeHinting(self):
|
||||||
self.trim (remove_hinting=True)
|
self.trim (remove_hinting=True)
|
||||||
@ -1432,41 +1421,22 @@ class GlyphComponent(object):
|
|||||||
|
|
||||||
class GlyphCoordinates(object):
|
class GlyphCoordinates(object):
|
||||||
|
|
||||||
def __init__(self, iterable=[], typecode="h"):
|
def __init__(self, iterable=[]):
|
||||||
self._a = array.array(typecode)
|
self._a = array.array('d')
|
||||||
self.extend(iterable)
|
self.extend(iterable)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def array(self):
|
def array(self):
|
||||||
return self._a
|
return self._a
|
||||||
|
|
||||||
def isFloat(self):
|
|
||||||
return self._a.typecode == 'd'
|
|
||||||
|
|
||||||
def _ensureFloat(self):
|
|
||||||
if self.isFloat():
|
|
||||||
return
|
|
||||||
# The conversion to list() is to work around Jython bug
|
|
||||||
self._a = array.array("d", list(self._a))
|
|
||||||
|
|
||||||
def _checkFloat(self, p):
|
|
||||||
if self.isFloat():
|
|
||||||
return p
|
|
||||||
if any(v > 0x7FFF or v < -0x8000 for v in p):
|
|
||||||
self._ensureFloat()
|
|
||||||
return p
|
|
||||||
if any(isinstance(v, float) for v in p):
|
|
||||||
p = [int(v) if int(v) == v else v for v in p]
|
|
||||||
if any(isinstance(v, float) for v in p):
|
|
||||||
self._ensureFloat()
|
|
||||||
return p
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def zeros(count):
|
def zeros(count):
|
||||||
return GlyphCoordinates([(0,0)] * count)
|
g = GlyphCoordinates()
|
||||||
|
g._a.frombytes(bytes(count * 2 * g._a.itemsize))
|
||||||
|
return g
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
c = GlyphCoordinates(typecode=self._a.typecode)
|
c = GlyphCoordinates()
|
||||||
c._a.extend(self._a)
|
c._a.extend(self._a)
|
||||||
return c
|
return c
|
||||||
|
|
||||||
@ -1477,7 +1447,11 @@ class GlyphCoordinates(object):
|
|||||||
if isinstance(k, slice):
|
if isinstance(k, slice):
|
||||||
indices = range(*k.indices(len(self)))
|
indices = range(*k.indices(len(self)))
|
||||||
return [self[i] for i in indices]
|
return [self[i] for i in indices]
|
||||||
return self._a[2*k],self._a[2*k+1]
|
a = self._a
|
||||||
|
x = a[2*k]
|
||||||
|
y = a[2*k+1]
|
||||||
|
return (int(x) if x.is_integer() else x,
|
||||||
|
int(y) if y.is_integer() else y)
|
||||||
|
|
||||||
def __setitem__(self, k, v):
|
def __setitem__(self, k, v):
|
||||||
if isinstance(k, slice):
|
if isinstance(k, slice):
|
||||||
@ -1486,7 +1460,6 @@ class GlyphCoordinates(object):
|
|||||||
for j,i in enumerate(indices):
|
for j,i in enumerate(indices):
|
||||||
self[i] = v[j]
|
self[i] = v[j]
|
||||||
return
|
return
|
||||||
v = self._checkFloat(v)
|
|
||||||
self._a[2*k],self._a[2*k+1] = v
|
self._a[2*k],self._a[2*k+1] = v
|
||||||
|
|
||||||
def __delitem__(self, i):
|
def __delitem__(self, i):
|
||||||
@ -1498,69 +1471,71 @@ class GlyphCoordinates(object):
|
|||||||
return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
|
return 'GlyphCoordinates(['+','.join(str(c) for c in self)+'])'
|
||||||
|
|
||||||
def append(self, p):
|
def append(self, p):
|
||||||
p = self._checkFloat(p)
|
|
||||||
self._a.extend(tuple(p))
|
self._a.extend(tuple(p))
|
||||||
|
|
||||||
def extend(self, iterable):
|
def extend(self, iterable):
|
||||||
for p in iterable:
|
for p in iterable:
|
||||||
p = self._checkFloat(p)
|
|
||||||
self._a.extend(p)
|
self._a.extend(p)
|
||||||
|
|
||||||
def toInt(self, *, round=otRound):
|
def toInt(self, *, round=otRound):
|
||||||
if not self.isFloat():
|
a = self._a
|
||||||
return
|
for i in range(len(a)):
|
||||||
a = array.array("h")
|
a[i] = round(a[i])
|
||||||
for n in self._a:
|
|
||||||
a.append(round(n))
|
|
||||||
self._a = a
|
|
||||||
|
|
||||||
def relativeToAbsolute(self):
|
def relativeToAbsolute(self):
|
||||||
a = self._a
|
a = self._a
|
||||||
x,y = 0,0
|
x,y = 0,0
|
||||||
for i in range(len(a) // 2):
|
for i in range(0, len(a), 2):
|
||||||
x = a[2*i ] + x
|
a[i ] = x = a[i ] + x
|
||||||
y = a[2*i+1] + y
|
a[i+1] = y = a[i+1] + y
|
||||||
self[i] = (x, y)
|
|
||||||
|
|
||||||
def absoluteToRelative(self):
|
def absoluteToRelative(self):
|
||||||
a = self._a
|
a = self._a
|
||||||
x,y = 0,0
|
x,y = 0,0
|
||||||
for i in range(len(a) // 2):
|
for i in range(0, len(a), 2):
|
||||||
dx = a[2*i ] - x
|
nx = a[i ]
|
||||||
dy = a[2*i+1] - y
|
ny = a[i+1]
|
||||||
x = a[2*i ]
|
a[i] = nx - x
|
||||||
y = a[2*i+1]
|
a[i+1] = ny - y
|
||||||
self[i] = (dx, dy)
|
x = nx
|
||||||
|
y = ny
|
||||||
|
|
||||||
def translate(self, p):
|
def translate(self, p):
|
||||||
"""
|
"""
|
||||||
>>> GlyphCoordinates([(1,2)]).translate((.5,0))
|
>>> GlyphCoordinates([(1,2)]).translate((.5,0))
|
||||||
"""
|
"""
|
||||||
(x,y) = self._checkFloat(p)
|
x,y = p
|
||||||
|
if x == 0 and y == 0:
|
||||||
|
return
|
||||||
a = self._a
|
a = self._a
|
||||||
for i in range(len(a) // 2):
|
for i in range(0, len(a), 2):
|
||||||
self[i] = (a[2*i] + x, a[2*i+1] + y)
|
a[i] += x
|
||||||
|
a[i+1] += y
|
||||||
|
|
||||||
def scale(self, p):
|
def scale(self, p):
|
||||||
"""
|
"""
|
||||||
>>> GlyphCoordinates([(1,2)]).scale((.5,0))
|
>>> GlyphCoordinates([(1,2)]).scale((.5,0))
|
||||||
"""
|
"""
|
||||||
(x,y) = self._checkFloat(p)
|
x,y = p
|
||||||
|
if x == 1 and y == 1:
|
||||||
|
return
|
||||||
a = self._a
|
a = self._a
|
||||||
for i in range(len(a) // 2):
|
for i in range(0, len(a), 2):
|
||||||
self[i] = (a[2*i] * x, a[2*i+1] * y)
|
a[i] *= x
|
||||||
|
a[i+1] *= y
|
||||||
|
|
||||||
def transform(self, t):
|
def transform(self, t):
|
||||||
"""
|
"""
|
||||||
>>> GlyphCoordinates([(1,2)]).transform(((.5,0),(.2,.5)))
|
>>> GlyphCoordinates([(1,2)]).transform(((.5,0),(.2,.5)))
|
||||||
"""
|
"""
|
||||||
a = self._a
|
a = self._a
|
||||||
for i in range(len(a) // 2):
|
for i in range(0, len(a), 2):
|
||||||
x = a[2*i ]
|
x = a[i ]
|
||||||
y = a[2*i+1]
|
y = a[i+1]
|
||||||
px = x * t[0][0] + y * t[1][0]
|
px = x * t[0][0] + y * t[1][0]
|
||||||
py = x * t[0][1] + y * t[1][1]
|
py = x * t[0][1] + y * t[1][1]
|
||||||
self[i] = (px, py)
|
a[i] = px
|
||||||
|
a[i+1] = py
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""
|
"""
|
||||||
@ -1645,23 +1620,22 @@ class GlyphCoordinates(object):
|
|||||||
>>> g = GlyphCoordinates([(1,2)])
|
>>> g = GlyphCoordinates([(1,2)])
|
||||||
>>> g += (.5,0)
|
>>> g += (.5,0)
|
||||||
>>> g
|
>>> g
|
||||||
GlyphCoordinates([(1.5, 2.0)])
|
GlyphCoordinates([(1.5, 2)])
|
||||||
>>> g2 = GlyphCoordinates([(3,4)])
|
>>> g2 = GlyphCoordinates([(3,4)])
|
||||||
>>> g += g2
|
>>> g += g2
|
||||||
>>> g
|
>>> g
|
||||||
GlyphCoordinates([(4.5, 6.0)])
|
GlyphCoordinates([(4.5, 6)])
|
||||||
"""
|
"""
|
||||||
if isinstance(other, tuple):
|
if isinstance(other, tuple):
|
||||||
assert len(other) == 2
|
assert len(other) == 2
|
||||||
self.translate(other)
|
self.translate(other)
|
||||||
return self
|
return self
|
||||||
if isinstance(other, GlyphCoordinates):
|
if isinstance(other, GlyphCoordinates):
|
||||||
if other.isFloat(): self._ensureFloat()
|
|
||||||
other = other._a
|
other = other._a
|
||||||
a = self._a
|
a = self._a
|
||||||
assert len(a) == len(other)
|
assert len(a) == len(other)
|
||||||
for i in range(len(a) // 2):
|
for i in range(len(a)):
|
||||||
self[i] = (a[2*i] + other[2*i], a[2*i+1] + other[2*i+1])
|
a[i] += other[i]
|
||||||
return self
|
return self
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
@ -1670,23 +1644,22 @@ class GlyphCoordinates(object):
|
|||||||
>>> g = GlyphCoordinates([(1,2)])
|
>>> g = GlyphCoordinates([(1,2)])
|
||||||
>>> g -= (.5,0)
|
>>> g -= (.5,0)
|
||||||
>>> g
|
>>> g
|
||||||
GlyphCoordinates([(0.5, 2.0)])
|
GlyphCoordinates([(0.5, 2)])
|
||||||
>>> g2 = GlyphCoordinates([(3,4)])
|
>>> g2 = GlyphCoordinates([(3,4)])
|
||||||
>>> g -= g2
|
>>> g -= g2
|
||||||
>>> g
|
>>> g
|
||||||
GlyphCoordinates([(-2.5, -2.0)])
|
GlyphCoordinates([(-2.5, -2)])
|
||||||
"""
|
"""
|
||||||
if isinstance(other, tuple):
|
if isinstance(other, tuple):
|
||||||
assert len(other) == 2
|
assert len(other) == 2
|
||||||
self.translate((-other[0],-other[1]))
|
self.translate((-other[0],-other[1]))
|
||||||
return self
|
return self
|
||||||
if isinstance(other, GlyphCoordinates):
|
if isinstance(other, GlyphCoordinates):
|
||||||
if other.isFloat(): self._ensureFloat()
|
|
||||||
other = other._a
|
other = other._a
|
||||||
a = self._a
|
a = self._a
|
||||||
assert len(a) == len(other)
|
assert len(a) == len(other)
|
||||||
for i in range(len(a) // 2):
|
for i in range(len(a)):
|
||||||
self[i] = (a[2*i] - other[2*i], a[2*i+1] - other[2*i+1])
|
a[i] -= other[i]
|
||||||
return self
|
return self
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
@ -1696,20 +1669,23 @@ class GlyphCoordinates(object):
|
|||||||
>>> g *= (2,.5)
|
>>> g *= (2,.5)
|
||||||
>>> g *= 2
|
>>> g *= 2
|
||||||
>>> g
|
>>> g
|
||||||
GlyphCoordinates([(4.0, 2.0)])
|
GlyphCoordinates([(4, 2)])
|
||||||
>>> g = GlyphCoordinates([(1,2)])
|
>>> g = GlyphCoordinates([(1,2)])
|
||||||
>>> g *= 2
|
>>> g *= 2
|
||||||
>>> g
|
>>> g
|
||||||
GlyphCoordinates([(2, 4)])
|
GlyphCoordinates([(2, 4)])
|
||||||
"""
|
"""
|
||||||
if isinstance(other, Number):
|
|
||||||
other = (other, other)
|
|
||||||
if isinstance(other, tuple):
|
if isinstance(other, tuple):
|
||||||
if other == (1,1):
|
|
||||||
return self
|
|
||||||
assert len(other) == 2
|
assert len(other) == 2
|
||||||
self.scale(other)
|
self.scale(other)
|
||||||
return self
|
return self
|
||||||
|
if isinstance(other, Number):
|
||||||
|
if other == 1:
|
||||||
|
return self
|
||||||
|
a = self._a
|
||||||
|
for i in range(len(a)):
|
||||||
|
a[i] *= other
|
||||||
|
return self
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __itruediv__(self, other):
|
def __itruediv__(self, other):
|
||||||
@ -1718,7 +1694,7 @@ class GlyphCoordinates(object):
|
|||||||
>>> g /= (.5,1.5)
|
>>> g /= (.5,1.5)
|
||||||
>>> g /= 2
|
>>> g /= 2
|
||||||
>>> g
|
>>> g
|
||||||
GlyphCoordinates([(1.0, 1.0)])
|
GlyphCoordinates([(1, 1)])
|
||||||
"""
|
"""
|
||||||
if isinstance(other, Number):
|
if isinstance(other, Number):
|
||||||
other = (other, other)
|
other = (other, other)
|
||||||
@ -1750,20 +1726,6 @@ class GlyphCoordinates(object):
|
|||||||
__nonzero__ = __bool__
|
__nonzero__ = __bool__
|
||||||
|
|
||||||
|
|
||||||
def reprflag(flag):
|
|
||||||
bin = ""
|
|
||||||
if isinstance(flag, str):
|
|
||||||
flag = byteord(flag)
|
|
||||||
while flag:
|
|
||||||
if flag & 0x01:
|
|
||||||
bin = "1" + bin
|
|
||||||
else:
|
|
||||||
bin = "0" + bin
|
|
||||||
flag = flag >> 1
|
|
||||||
bin = (14 - len(bin)) * "0" + bin
|
|
||||||
return bin
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import doctest, sys
|
import doctest, sys
|
||||||
sys.exit(doctest.testmod().failed)
|
sys.exit(doctest.testmod().failed)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from fontTools.misc.py23 import bytesjoin
|
|
||||||
from fontTools.misc import sstruct
|
from fontTools.misc import sstruct
|
||||||
from fontTools.misc.textTools import safeEval
|
from fontTools.misc.textTools import safeEval
|
||||||
from . import DefaultTable
|
from . import DefaultTable
|
||||||
@ -76,7 +75,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|||||||
result = [compiledHeader, compiledOffsets]
|
result = [compiledHeader, compiledOffsets]
|
||||||
result.extend(sharedTuples)
|
result.extend(sharedTuples)
|
||||||
result.extend(compiledGlyphs)
|
result.extend(compiledGlyphs)
|
||||||
return bytesjoin(result)
|
return b''.join(result)
|
||||||
|
|
||||||
def compileGlyphs_(self, ttFont, axisTags, sharedCoordIndices):
|
def compileGlyphs_(self, ttFont, axisTags, sharedCoordIndices):
|
||||||
result = []
|
result = []
|
||||||
@ -214,12 +213,14 @@ def compileGlyph_(variations, pointCount, axisTags, sharedCoordIndices):
|
|||||||
variations, pointCount, axisTags, sharedCoordIndices)
|
variations, pointCount, axisTags, sharedCoordIndices)
|
||||||
if tupleVariationCount == 0:
|
if tupleVariationCount == 0:
|
||||||
return b""
|
return b""
|
||||||
result = (
|
result = [
|
||||||
struct.pack(">HH", tupleVariationCount, 4 + len(tuples)) + tuples + data
|
struct.pack(">HH", tupleVariationCount, 4 + len(tuples)),
|
||||||
)
|
tuples,
|
||||||
if len(result) % 2 != 0:
|
data
|
||||||
result = result + b"\0" # padding
|
]
|
||||||
return result
|
if (len(tuples) + len(data)) % 2 != 0:
|
||||||
|
result.append(b"\0") # padding
|
||||||
|
return b''.join(result)
|
||||||
|
|
||||||
|
|
||||||
def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
|
def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
|
||||||
|
@ -284,9 +284,9 @@ def _add_gvar(font, masterModel, master_ttfs, tolerance=0.5, optimize=True):
|
|||||||
var_opt = TupleVariation(support, delta_opt)
|
var_opt = TupleVariation(support, delta_opt)
|
||||||
|
|
||||||
axis_tags = sorted(support.keys()) # Shouldn't matter that this is different from fvar...?
|
axis_tags = sorted(support.keys()) # Shouldn't matter that this is different from fvar...?
|
||||||
tupleData, auxData, _ = var.compile(axis_tags, [], None)
|
tupleData, auxData = var.compile(axis_tags)
|
||||||
unoptimized_len = len(tupleData) + len(auxData)
|
unoptimized_len = len(tupleData) + len(auxData)
|
||||||
tupleData, auxData, _ = var_opt.compile(axis_tags, [], None)
|
tupleData, auxData = var_opt.compile(axis_tags)
|
||||||
optimized_len = len(tupleData) + len(auxData)
|
optimized_len = len(tupleData) + len(auxData)
|
||||||
|
|
||||||
if optimized_len < unoptimized_len:
|
if optimized_len < unoptimized_len:
|
||||||
|
@ -375,6 +375,9 @@ class VariationModel(object):
|
|||||||
for i,weights in enumerate(self.deltaWeights):
|
for i,weights in enumerate(self.deltaWeights):
|
||||||
delta = masterValues[mapping[i]]
|
delta = masterValues[mapping[i]]
|
||||||
for j,weight in weights.items():
|
for j,weight in weights.items():
|
||||||
|
if weight == 1:
|
||||||
|
delta -= out[j]
|
||||||
|
else:
|
||||||
delta -= out[j] * weight
|
delta -= out[j] * weight
|
||||||
out.append(round(delta))
|
out.append(round(delta))
|
||||||
return out
|
return out
|
||||||
|
@ -231,8 +231,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
axisTags = ["wght", "wdth"]
|
axisTags = ["wght", "wdth"]
|
||||||
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
||||||
tup, deltas, _ = var.compile(axisTags, sharedPeakIndices,
|
tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b'')
|
||||||
sharedPoints={0,1,2})
|
|
||||||
# len(deltas)=8; flags=None; tupleIndex=0x77
|
# len(deltas)=8; flags=None; tupleIndex=0x77
|
||||||
# embeddedPeaks=[]; intermediateCoord=[]
|
# embeddedPeaks=[]; intermediateCoord=[]
|
||||||
self.assertEqual("00 08 00 77", hexencode(tup))
|
self.assertEqual("00 08 00 77", hexencode(tup))
|
||||||
@ -246,8 +245,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
axisTags = ["wght", "wdth"]
|
axisTags = ["wght", "wdth"]
|
||||||
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
||||||
tup, deltas, _ = var.compile(axisTags, sharedPeakIndices,
|
tup, deltas = var.compile(axisTags, sharedPeakIndices, pointData=b'')
|
||||||
sharedPoints={0,1,2})
|
|
||||||
# len(deltas)=8; flags=INTERMEDIATE_REGION; tupleIndex=0x77
|
# len(deltas)=8; flags=INTERMEDIATE_REGION; tupleIndex=0x77
|
||||||
# embeddedPeak=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)]
|
# embeddedPeak=[]; intermediateCoord=[(0.3, 0.1), (0.7, 0.9)]
|
||||||
self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tup))
|
self.assertEqual("00 08 40 77 13 33 06 66 2C CD 39 9A", hexencode(tup))
|
||||||
@ -261,8 +259,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
axisTags = ["wght", "wdth"]
|
axisTags = ["wght", "wdth"]
|
||||||
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
||||||
tup, deltas, _ = var.compile(axisTags, sharedPeakIndices,
|
tup, deltas = var.compile(axisTags, sharedPeakIndices)
|
||||||
sharedPoints=None)
|
|
||||||
# len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
|
# len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
|
||||||
# embeddedPeak=[]; intermediateCoord=[]
|
# embeddedPeak=[]; intermediateCoord=[]
|
||||||
self.assertEqual("00 09 20 77", hexencode(tup))
|
self.assertEqual("00 09 20 77", hexencode(tup))
|
||||||
@ -277,8 +274,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
axisTags = ["wght", "wdth"]
|
axisTags = ["wght", "wdth"]
|
||||||
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
sharedPeakIndices = { var.compileCoord(axisTags): 0x77 }
|
||||||
tuple, deltas, _ = var.compile(axisTags,
|
tuple, deltas = var.compile(axisTags, sharedPeakIndices)
|
||||||
sharedPeakIndices, sharedPoints=None)
|
|
||||||
# len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
|
# len(deltas)=9; flags=PRIVATE_POINT_NUMBERS; tupleIndex=0x77
|
||||||
# embeddedPeak=[]; intermediateCoord=[(0.0, 0.0), (1.0, 1.0)]
|
# embeddedPeak=[]; intermediateCoord=[(0.0, 0.0), (1.0, 1.0)]
|
||||||
self.assertEqual("00 09 60 77 00 00 00 00 40 00 40 00",
|
self.assertEqual("00 09 60 77 00 00 00 00 40 00 40 00",
|
||||||
@ -292,8 +288,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
var = TupleVariation(
|
var = TupleVariation(
|
||||||
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
tup, deltas, _ = var.compile(axisTags=["wght", "wdth"],
|
tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'')
|
||||||
sharedCoordIndices={}, sharedPoints={0, 1, 2})
|
|
||||||
# len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
|
# len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
|
||||||
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
||||||
self.assertEqual("00 08 80 00 20 00 33 33", hexencode(tup))
|
self.assertEqual("00 08 80 00 20 00 33 33", hexencode(tup))
|
||||||
@ -305,8 +300,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
var = TupleVariation(
|
var = TupleVariation(
|
||||||
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||||
[3, 1, 4])
|
[3, 1, 4])
|
||||||
tup, deltas, _ = var.compile(axisTags=["wght", "wdth"],
|
tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'')
|
||||||
sharedCoordIndices={}, sharedPoints={0, 1, 2})
|
|
||||||
# len(deltas)=4; flags=EMBEDDED_PEAK_TUPLE
|
# len(deltas)=4; flags=EMBEDDED_PEAK_TUPLE
|
||||||
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
||||||
self.assertEqual("00 04 80 00 20 00 33 33", hexencode(tup))
|
self.assertEqual("00 04 80 00 20 00 33 33", hexencode(tup))
|
||||||
@ -317,9 +311,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
var = TupleVariation(
|
var = TupleVariation(
|
||||||
{"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 0.8)},
|
{"wght": (0.0, 0.5, 1.0), "wdth": (0.0, 0.8, 0.8)},
|
||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
tup, deltas, _ = var.compile(axisTags=["wght", "wdth"],
|
tup, deltas = var.compile(axisTags=["wght", "wdth"], pointData=b'')
|
||||||
sharedCoordIndices={},
|
|
||||||
sharedPoints={0, 1, 2})
|
|
||||||
# len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
|
# len(deltas)=8; flags=EMBEDDED_PEAK_TUPLE
|
||||||
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[(0.0, 0.0), (1.0, 0.8)]
|
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[(0.0, 0.0), (1.0, 0.8)]
|
||||||
self.assertEqual("00 08 C0 00 20 00 33 33 00 00 00 00 40 00 33 33",
|
self.assertEqual("00 08 C0 00 20 00 33 33 00 00 00 00 40 00 33 33",
|
||||||
@ -332,8 +324,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
var = TupleVariation(
|
var = TupleVariation(
|
||||||
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
tup, deltas, _ = var.compile(
|
tup, deltas = var.compile(axisTags=["wght", "wdth"])
|
||||||
axisTags=["wght", "wdth"], sharedCoordIndices={}, sharedPoints=None)
|
|
||||||
# len(deltas)=9; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
|
# len(deltas)=9; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
|
||||||
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
||||||
self.assertEqual("00 09 A0 00 20 00 33 33", hexencode(tup))
|
self.assertEqual("00 09 A0 00 20 00 33 33", hexencode(tup))
|
||||||
@ -346,8 +337,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
var = TupleVariation(
|
var = TupleVariation(
|
||||||
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
{"wght": (0.0, 0.5, 0.5), "wdth": (0.0, 0.8, 0.8)},
|
||||||
[7, 8, 9])
|
[7, 8, 9])
|
||||||
tup, deltas, _ = var.compile(
|
tup, deltas = var.compile(axisTags=["wght", "wdth"])
|
||||||
axisTags=["wght", "wdth"], sharedCoordIndices={}, sharedPoints=None)
|
|
||||||
# len(deltas)=5; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
|
# len(deltas)=5; flags=PRIVATE_POINT_NUMBERS|EMBEDDED_PEAK_TUPLE
|
||||||
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
# embeddedPeak=[(0.5, 0.8)]; intermediateCoord=[]
|
||||||
self.assertEqual("00 05 A0 00 20 00 33 33", hexencode(tup))
|
self.assertEqual("00 05 A0 00 20 00 33 33", hexencode(tup))
|
||||||
@ -359,9 +349,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
var = TupleVariation(
|
var = TupleVariation(
|
||||||
{"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)},
|
{"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)},
|
||||||
[(7,4), (8,5), (9,6)])
|
[(7,4), (8,5), (9,6)])
|
||||||
tup, deltas, _ = var.compile(
|
tup, deltas = var.compile(axisTags = ["wght", "wdth"])
|
||||||
axisTags = ["wght", "wdth"],
|
|
||||||
sharedCoordIndices={}, sharedPoints=None)
|
|
||||||
# len(deltas)=9;
|
# len(deltas)=9;
|
||||||
# flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
|
# flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
|
||||||
# embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
|
# embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
|
||||||
@ -376,9 +364,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
var = TupleVariation(
|
var = TupleVariation(
|
||||||
{"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)},
|
{"wght": (0.4, 0.5, 0.6), "wdth": (0.7, 0.8, 0.9)},
|
||||||
[7, 8, 9])
|
[7, 8, 9])
|
||||||
tup, deltas, _ = var.compile(
|
tup, deltas = var.compile(axisTags = ["wght", "wdth"])
|
||||||
axisTags = ["wght", "wdth"],
|
|
||||||
sharedCoordIndices={}, sharedPoints=None)
|
|
||||||
# len(deltas)=5;
|
# len(deltas)=5;
|
||||||
# flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
|
# flags=PRIVATE_POINT_NUMBERS|INTERMEDIATE_REGION|EMBEDDED_PEAK_TUPLE
|
||||||
# embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
|
# embeddedPeak=(0.5, 0.8); intermediateCoord=[(0.4, 0.7), (0.6, 0.9)]
|
||||||
@ -415,8 +401,8 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
self.assertEqual("7F B9 80 35", hexencode(var.compileCoord(["wght", "wdth"])))
|
self.assertEqual("7F B9 80 35", hexencode(var.compileCoord(["wght", "wdth"])))
|
||||||
|
|
||||||
def test_compilePoints(self):
|
def test_compilePoints(self):
|
||||||
compilePoints = lambda p: TupleVariation.compilePoints(set(p), numPointsInGlyph=999)
|
compilePoints = lambda p: TupleVariation.compilePoints(set(p))
|
||||||
self.assertEqual("00", hexencode(compilePoints(range(999)))) # all points in glyph
|
self.assertEqual("00", hexencode(compilePoints(set()))) # all points in glyph
|
||||||
self.assertEqual("01 00 07", hexencode(compilePoints([7])))
|
self.assertEqual("01 00 07", hexencode(compilePoints([7])))
|
||||||
self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535])))
|
self.assertEqual("01 80 FF FF", hexencode(compilePoints([65535])))
|
||||||
self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15])))
|
self.assertEqual("02 01 09 06", hexencode(compilePoints([9, 15])))
|
||||||
@ -488,7 +474,7 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_decompilePoints_roundTrip(self):
|
def test_decompilePoints_roundTrip(self):
|
||||||
numPointsInGlyph = 500 # greater than 255, so we also exercise code path for 16-bit encoding
|
numPointsInGlyph = 500 # greater than 255, so we also exercise code path for 16-bit encoding
|
||||||
compile = lambda points: TupleVariation.compilePoints(points, numPointsInGlyph)
|
compile = lambda points: TupleVariation.compilePoints(points)
|
||||||
decompile = lambda data: set(TupleVariation.decompilePoints_(numPointsInGlyph, data, 0, "gvar")[0])
|
decompile = lambda data: set(TupleVariation.decompilePoints_(numPointsInGlyph, data, 0, "gvar")[0])
|
||||||
for i in range(50):
|
for i in range(50):
|
||||||
points = set(random.sample(range(numPointsInGlyph), 30))
|
points = set(random.sample(range(numPointsInGlyph), 30))
|
||||||
@ -496,18 +482,17 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
"failed round-trip decompile/compilePoints; points=%s" % points)
|
"failed round-trip decompile/compilePoints; points=%s" % points)
|
||||||
allPoints = set(range(numPointsInGlyph))
|
allPoints = set(range(numPointsInGlyph))
|
||||||
self.assertSetEqual(allPoints, decompile(compile(allPoints)))
|
self.assertSetEqual(allPoints, decompile(compile(allPoints)))
|
||||||
|
self.assertSetEqual(allPoints, decompile(compile(set())))
|
||||||
|
|
||||||
def test_compileDeltas_points(self):
|
def test_compileDeltas_points(self):
|
||||||
var = TupleVariation({}, [(0,0), (1, 0), (2, 0), None, (4, 0), (5, 0)])
|
var = TupleVariation({}, [None, (1, 0), (2, 0), None, (4, 0), None])
|
||||||
points = {1, 2, 3, 4}
|
|
||||||
# deltaX for points: [1, 2, 4]; deltaY for points: [0, 0, 0]
|
# deltaX for points: [1, 2, 4]; deltaY for points: [0, 0, 0]
|
||||||
self.assertEqual("02 01 02 04 82", hexencode(var.compileDeltas(points)))
|
self.assertEqual("02 01 02 04 82", hexencode(var.compileDeltas()))
|
||||||
|
|
||||||
def test_compileDeltas_constants(self):
|
def test_compileDeltas_constants(self):
|
||||||
var = TupleVariation({}, [0, 1, 2, None, 4, 5])
|
var = TupleVariation({}, [None, 1, 2, None, 4, None])
|
||||||
cvts = {1, 2, 3, 4}
|
|
||||||
# delta for cvts: [1, 2, 4]
|
# delta for cvts: [1, 2, 4]
|
||||||
self.assertEqual("02 01 02 04", hexencode(var.compileDeltas(cvts)))
|
self.assertEqual("02 01 02 04", hexencode(var.compileDeltas()))
|
||||||
|
|
||||||
def test_compileDeltaValues(self):
|
def test_compileDeltaValues(self):
|
||||||
compileDeltaValues = lambda values: hexencode(TupleVariation.compileDeltaValues_(values))
|
compileDeltaValues = lambda values: hexencode(TupleVariation.compileDeltaValues_(values))
|
||||||
@ -549,11 +534,6 @@ class TupleVariationTest(unittest.TestCase):
|
|||||||
# words, zeroes
|
# words, zeroes
|
||||||
self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0]))
|
self.assertEqual("40 66 66 80", compileDeltaValues([0x6666, 0]))
|
||||||
self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0]))
|
self.assertEqual("40 66 66 81", compileDeltaValues([0x6666, 0, 0]))
|
||||||
# bytes or words from floats
|
|
||||||
self.assertEqual("00 01", compileDeltaValues([1.1]))
|
|
||||||
self.assertEqual("00 02", compileDeltaValues([1.9]))
|
|
||||||
self.assertEqual("40 66 66", compileDeltaValues([0x6666 + 0.1]))
|
|
||||||
self.assertEqual("40 66 66", compileDeltaValues([0x6665 + 0.9]))
|
|
||||||
|
|
||||||
def test_decompileDeltas(self):
|
def test_decompileDeltas(self):
|
||||||
decompileDeltas = TupleVariation.decompileDeltas_
|
decompileDeltas = TupleVariation.decompileDeltas_
|
||||||
|
@ -173,10 +173,9 @@ class GlyphCoordinatesTest(object):
|
|||||||
assert g[0][0] == otRound(afloat)
|
assert g[0][0] == otRound(afloat)
|
||||||
|
|
||||||
def test__checkFloat_overflow(self):
|
def test__checkFloat_overflow(self):
|
||||||
g = GlyphCoordinates([(1, 1)], typecode="h")
|
g = GlyphCoordinates([(1, 1)])
|
||||||
g.append((0x8000, 0))
|
g.append((0x8000, 0))
|
||||||
assert g.array.typecode == "d"
|
assert list(g.array) == [1.0, 1.0, 32768.0, 0.0]
|
||||||
assert g.array == array.array("d", [1.0, 1.0, 32768.0, 0.0])
|
|
||||||
|
|
||||||
|
|
||||||
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
|
CURR_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user