Rip out glyf1 VarComposites
In favor of separate VARC table. ttGlyphSet does NOT yet know how to draw VARC table though. The 9 failing tests are all VarComposite-related and need to be updated with VARC equivalents eventually when we add VARC support to subsetter and instancer.
This commit is contained in:
parent
ec78b572c9
commit
bcd5e4c216
@ -656,11 +656,7 @@ class FontBuilder(object):
|
|||||||
|
|
||||||
if validateGlyphFormat and self.font["head"].glyphDataFormat == 0:
|
if validateGlyphFormat and self.font["head"].glyphDataFormat == 0:
|
||||||
for name, g in glyphs.items():
|
for name, g in glyphs.items():
|
||||||
if g.isVarComposite():
|
if g.numberOfContours > 0 and any(f & flagCubic for f in g.flags):
|
||||||
raise ValueError(
|
|
||||||
f"Glyph {name!r} is a variable composite, but glyphDataFormat=0"
|
|
||||||
)
|
|
||||||
elif g.numberOfContours > 0 and any(f & flagCubic for f in g.flags):
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Glyph {name!r} has cubic Bezier outlines, but glyphDataFormat=0; "
|
f"Glyph {name!r} has cubic Bezier outlines, but glyphDataFormat=0; "
|
||||||
"either convert to quadratics with cu2qu or set glyphDataFormat=1."
|
"either convert to quadratics with cu2qu or set glyphDataFormat=1."
|
||||||
|
@ -225,7 +225,7 @@ def merge(self, m, tables):
|
|||||||
g.removeHinting()
|
g.removeHinting()
|
||||||
# Expand composite glyphs to load their
|
# Expand composite glyphs to load their
|
||||||
# composite glyph names.
|
# composite glyph names.
|
||||||
if g.isComposite() or g.isVarComposite():
|
if g.isComposite():
|
||||||
g.expand(table)
|
g.expand(table)
|
||||||
return DefaultTable.merge(self, m, tables)
|
return DefaultTable.merge(self, m, tables)
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ from fontTools.cffLib import VarStoreData
|
|||||||
import fontTools.cffLib.specializer as cffSpecializer
|
import fontTools.cffLib.specializer as cffSpecializer
|
||||||
from fontTools.varLib import builder # for VarData.calculateNumShorts
|
from fontTools.varLib import builder # for VarData.calculateNumShorts
|
||||||
from fontTools.misc.fixedTools import otRound
|
from fontTools.misc.fixedTools import otRound
|
||||||
from fontTools.ttLib.tables._g_l_y_f import VarComponentFlags
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["scale_upem", "ScalerVisitor"]
|
__all__ = ["scale_upem", "ScalerVisitor"]
|
||||||
@ -123,13 +122,6 @@ def visit(visitor, obj, attr, glyphs):
|
|||||||
component.y = visitor.scale(component.y)
|
component.y = visitor.scale(component.y)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if g.isVarComposite():
|
|
||||||
for component in g.components:
|
|
||||||
for attr in ("translateX", "translateY", "tCenterX", "tCenterY"):
|
|
||||||
v = getattr(component.transform, attr)
|
|
||||||
setattr(component.transform, attr, visitor.scale(v))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if hasattr(g, "coordinates"):
|
if hasattr(g, "coordinates"):
|
||||||
coordinates = g.coordinates
|
coordinates = g.coordinates
|
||||||
for i, (x, y) in enumerate(coordinates):
|
for i, (x, y) in enumerate(coordinates):
|
||||||
@ -138,56 +130,15 @@ def visit(visitor, obj, attr, glyphs):
|
|||||||
|
|
||||||
@ScalerVisitor.register_attr(ttLib.getTableClass("gvar"), "variations")
|
@ScalerVisitor.register_attr(ttLib.getTableClass("gvar"), "variations")
|
||||||
def visit(visitor, obj, attr, variations):
|
def visit(visitor, obj, attr, variations):
|
||||||
# VarComposites are a pain to handle :-(
|
|
||||||
glyfTable = visitor.font["glyf"]
|
glyfTable = visitor.font["glyf"]
|
||||||
|
|
||||||
for glyphName, varlist in variations.items():
|
for glyphName, varlist in variations.items():
|
||||||
glyph = glyfTable[glyphName]
|
glyph = glyfTable[glyphName]
|
||||||
isVarComposite = glyph.isVarComposite()
|
|
||||||
for var in varlist:
|
for var in varlist:
|
||||||
coordinates = var.coordinates
|
coordinates = var.coordinates
|
||||||
|
for i, xy in enumerate(coordinates):
|
||||||
if not isVarComposite:
|
if xy is None:
|
||||||
for i, xy in enumerate(coordinates):
|
continue
|
||||||
if xy is None:
|
|
||||||
continue
|
|
||||||
coordinates[i] = visitor.scale(xy[0]), visitor.scale(xy[1])
|
|
||||||
continue
|
|
||||||
|
|
||||||
# VarComposite glyph
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for component in glyph.components:
|
|
||||||
if component.flags & VarComponentFlags.AXES_HAVE_VARIATION:
|
|
||||||
i += len(component.location)
|
|
||||||
if component.flags & (
|
|
||||||
VarComponentFlags.HAVE_TRANSLATE_X
|
|
||||||
| VarComponentFlags.HAVE_TRANSLATE_Y
|
|
||||||
):
|
|
||||||
xy = coordinates[i]
|
|
||||||
coordinates[i] = visitor.scale(xy[0]), visitor.scale(xy[1])
|
|
||||||
i += 1
|
|
||||||
if component.flags & VarComponentFlags.HAVE_ROTATION:
|
|
||||||
i += 1
|
|
||||||
if component.flags & (
|
|
||||||
VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
):
|
|
||||||
i += 1
|
|
||||||
if component.flags & (
|
|
||||||
VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y
|
|
||||||
):
|
|
||||||
i += 1
|
|
||||||
if component.flags & (
|
|
||||||
VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y
|
|
||||||
):
|
|
||||||
xy = coordinates[i]
|
|
||||||
coordinates[i] = visitor.scale(xy[0]), visitor.scale(xy[1])
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# Phantom points
|
|
||||||
assert i + 4 == len(coordinates)
|
|
||||||
for i in range(i, len(coordinates)):
|
|
||||||
xy = coordinates[i]
|
|
||||||
coordinates[i] = visitor.scale(xy[0]), visitor.scale(xy[1])
|
coordinates[i] = visitor.scale(xy[0]), visitor.scale(xy[1])
|
||||||
|
|
||||||
|
|
||||||
|
@ -424,29 +424,6 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
|||||||
for c in glyph.components
|
for c in glyph.components
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
elif glyph.isVarComposite():
|
|
||||||
coords = []
|
|
||||||
controls = []
|
|
||||||
|
|
||||||
for component in glyph.components:
|
|
||||||
(
|
|
||||||
componentCoords,
|
|
||||||
componentControls,
|
|
||||||
) = component.getCoordinatesAndControls()
|
|
||||||
coords.extend(componentCoords)
|
|
||||||
controls.extend(componentControls)
|
|
||||||
|
|
||||||
coords = GlyphCoordinates(coords)
|
|
||||||
|
|
||||||
controls = _GlyphControls(
|
|
||||||
numberOfContours=glyph.numberOfContours,
|
|
||||||
endPts=list(range(len(coords))),
|
|
||||||
flags=None,
|
|
||||||
components=[
|
|
||||||
(c.glyphName, getattr(c, "flags", None)) for c in glyph.components
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
coords, endPts, flags = glyph.getCoordinates(self)
|
coords, endPts, flags = glyph.getCoordinates(self)
|
||||||
coords = coords.copy()
|
coords = coords.copy()
|
||||||
@ -492,10 +469,6 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
|
|||||||
for p, comp in zip(coord, glyph.components):
|
for p, comp in zip(coord, glyph.components):
|
||||||
if hasattr(comp, "x"):
|
if hasattr(comp, "x"):
|
||||||
comp.x, comp.y = p
|
comp.x, comp.y = p
|
||||||
elif glyph.isVarComposite():
|
|
||||||
for comp in glyph.components:
|
|
||||||
coord = comp.setCoordinates(coord)
|
|
||||||
assert not coord
|
|
||||||
elif glyph.numberOfContours == 0:
|
elif glyph.numberOfContours == 0:
|
||||||
assert len(coord) == 0
|
assert len(coord) == 0
|
||||||
else:
|
else:
|
||||||
@ -737,8 +710,6 @@ class Glyph(object):
|
|||||||
return
|
return
|
||||||
if self.isComposite():
|
if self.isComposite():
|
||||||
self.decompileComponents(data, glyfTable)
|
self.decompileComponents(data, glyfTable)
|
||||||
elif self.isVarComposite():
|
|
||||||
self.decompileVarComponents(data, glyfTable)
|
|
||||||
else:
|
else:
|
||||||
self.decompileCoordinates(data)
|
self.decompileCoordinates(data)
|
||||||
|
|
||||||
@ -758,8 +729,6 @@ class Glyph(object):
|
|||||||
data = sstruct.pack(glyphHeaderFormat, self)
|
data = sstruct.pack(glyphHeaderFormat, self)
|
||||||
if self.isComposite():
|
if self.isComposite():
|
||||||
data = data + self.compileComponents(glyfTable)
|
data = data + self.compileComponents(glyfTable)
|
||||||
elif self.isVarComposite():
|
|
||||||
data = data + self.compileVarComponents(glyfTable)
|
|
||||||
else:
|
else:
|
||||||
data = data + self.compileCoordinates()
|
data = data + self.compileCoordinates()
|
||||||
return data
|
return data
|
||||||
@ -769,10 +738,6 @@ class Glyph(object):
|
|||||||
for compo in self.components:
|
for compo in self.components:
|
||||||
compo.toXML(writer, ttFont)
|
compo.toXML(writer, ttFont)
|
||||||
haveInstructions = hasattr(self, "program")
|
haveInstructions = hasattr(self, "program")
|
||||||
elif self.isVarComposite():
|
|
||||||
for compo in self.components:
|
|
||||||
compo.toXML(writer, ttFont)
|
|
||||||
haveInstructions = False
|
|
||||||
else:
|
else:
|
||||||
last = 0
|
last = 0
|
||||||
for i in range(self.numberOfContours):
|
for i in range(self.numberOfContours):
|
||||||
@ -842,15 +807,6 @@ class Glyph(object):
|
|||||||
component = GlyphComponent()
|
component = GlyphComponent()
|
||||||
self.components.append(component)
|
self.components.append(component)
|
||||||
component.fromXML(name, attrs, content, ttFont)
|
component.fromXML(name, attrs, content, ttFont)
|
||||||
elif name == "varComponent":
|
|
||||||
if self.numberOfContours > 0:
|
|
||||||
raise ttLib.TTLibError("can't mix composites and contours in glyph")
|
|
||||||
self.numberOfContours = -2
|
|
||||||
if not hasattr(self, "components"):
|
|
||||||
self.components = []
|
|
||||||
component = GlyphVarComponent()
|
|
||||||
self.components.append(component)
|
|
||||||
component.fromXML(name, attrs, content, ttFont)
|
|
||||||
elif name == "instructions":
|
elif name == "instructions":
|
||||||
self.program = ttProgram.Program()
|
self.program = ttProgram.Program()
|
||||||
for element in content:
|
for element in content:
|
||||||
@ -860,7 +816,7 @@ class Glyph(object):
|
|||||||
self.program.fromXML(name, attrs, content, ttFont)
|
self.program.fromXML(name, attrs, content, ttFont)
|
||||||
|
|
||||||
def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
|
def getCompositeMaxpValues(self, glyfTable, maxComponentDepth=1):
|
||||||
assert self.isComposite() or self.isVarComposite()
|
assert self.isComposite()
|
||||||
nContours = 0
|
nContours = 0
|
||||||
nPoints = 0
|
nPoints = 0
|
||||||
initialMaxComponentDepth = maxComponentDepth
|
initialMaxComponentDepth = maxComponentDepth
|
||||||
@ -904,13 +860,6 @@ class Glyph(object):
|
|||||||
len(data),
|
len(data),
|
||||||
)
|
)
|
||||||
|
|
||||||
def decompileVarComponents(self, data, glyfTable):
|
|
||||||
self.components = []
|
|
||||||
while len(data) >= GlyphVarComponent.MIN_SIZE:
|
|
||||||
component = GlyphVarComponent()
|
|
||||||
data = component.decompile(data, glyfTable)
|
|
||||||
self.components.append(component)
|
|
||||||
|
|
||||||
def decompileCoordinates(self, data):
|
def decompileCoordinates(self, data):
|
||||||
endPtsOfContours = array.array("H")
|
endPtsOfContours = array.array("H")
|
||||||
endPtsOfContours.frombytes(data[: 2 * self.numberOfContours])
|
endPtsOfContours.frombytes(data[: 2 * self.numberOfContours])
|
||||||
@ -1027,9 +976,6 @@ class Glyph(object):
|
|||||||
data = data + struct.pack(">h", len(instructions)) + instructions
|
data = data + struct.pack(">h", len(instructions)) + instructions
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def compileVarComponents(self, glyfTable):
|
|
||||||
return b"".join(c.compile(glyfTable) for c in self.components)
|
|
||||||
|
|
||||||
def compileCoordinates(self):
|
def compileCoordinates(self):
|
||||||
assert len(self.coordinates) == len(self.flags)
|
assert len(self.coordinates) == len(self.flags)
|
||||||
data = []
|
data = []
|
||||||
@ -1231,13 +1177,6 @@ class Glyph(object):
|
|||||||
else:
|
else:
|
||||||
return self.numberOfContours == -1
|
return self.numberOfContours == -1
|
||||||
|
|
||||||
def isVarComposite(self):
|
|
||||||
"""Test whether a glyph has variable components"""
|
|
||||||
if hasattr(self, "data"):
|
|
||||||
return struct.unpack(">h", self.data[:2])[0] == -2 if self.data else False
|
|
||||||
else:
|
|
||||||
return self.numberOfContours == -2
|
|
||||||
|
|
||||||
def getCoordinates(self, glyfTable):
|
def getCoordinates(self, glyfTable):
|
||||||
"""Return the coordinates, end points and flags
|
"""Return the coordinates, end points and flags
|
||||||
|
|
||||||
@ -1308,8 +1247,6 @@ class Glyph(object):
|
|||||||
allCoords.extend(coordinates)
|
allCoords.extend(coordinates)
|
||||||
allFlags.extend(flags)
|
allFlags.extend(flags)
|
||||||
return allCoords, allEndPts, allFlags
|
return allCoords, allEndPts, allFlags
|
||||||
elif self.isVarComposite():
|
|
||||||
raise NotImplementedError("use TTGlyphSet to draw VarComposite glyphs")
|
|
||||||
else:
|
else:
|
||||||
return GlyphCoordinates(), [], bytearray()
|
return GlyphCoordinates(), [], bytearray()
|
||||||
|
|
||||||
@ -1319,12 +1256,8 @@ class Glyph(object):
|
|||||||
This method can be used on simple glyphs (in which case it returns an
|
This method can be used on simple glyphs (in which case it returns an
|
||||||
empty list) or composite glyphs.
|
empty list) or composite glyphs.
|
||||||
"""
|
"""
|
||||||
if hasattr(self, "data") and self.isVarComposite():
|
|
||||||
# TODO(VarComposite) Add implementation without expanding glyph
|
|
||||||
self.expand(glyfTable)
|
|
||||||
|
|
||||||
if not hasattr(self, "data"):
|
if not hasattr(self, "data"):
|
||||||
if self.isComposite() or self.isVarComposite():
|
if self.isComposite():
|
||||||
return [c.glyphName for c in self.components]
|
return [c.glyphName for c in self.components]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
@ -1367,8 +1300,6 @@ class Glyph(object):
|
|||||||
if self.isComposite():
|
if self.isComposite():
|
||||||
if hasattr(self, "program"):
|
if hasattr(self, "program"):
|
||||||
del self.program
|
del self.program
|
||||||
elif self.isVarComposite():
|
|
||||||
pass # Doesn't have hinting
|
|
||||||
else:
|
else:
|
||||||
self.program = ttProgram.Program()
|
self.program = ttProgram.Program()
|
||||||
self.program.fromBytecode([])
|
self.program.fromBytecode([])
|
||||||
@ -1450,13 +1381,6 @@ class Glyph(object):
|
|||||||
i += 2 + instructionLen
|
i += 2 + instructionLen
|
||||||
# Remove padding
|
# Remove padding
|
||||||
data = data[:i]
|
data = data[:i]
|
||||||
elif self.isVarComposite():
|
|
||||||
i = 0
|
|
||||||
MIN_SIZE = GlyphVarComponent.MIN_SIZE
|
|
||||||
while len(data[i : i + MIN_SIZE]) >= MIN_SIZE:
|
|
||||||
size = GlyphVarComponent.getSize(data[i : i + MIN_SIZE])
|
|
||||||
i += size
|
|
||||||
data = data[:i]
|
|
||||||
|
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
@ -1942,391 +1866,6 @@ class GlyphComponent(object):
|
|||||||
return result if result is NotImplemented else not result
|
return result if result is NotImplemented else not result
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Variable Composite glyphs
|
|
||||||
# https://github.com/harfbuzz/boring-expansion-spec/blob/main/glyf1.md
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
class VarComponentFlags(IntFlag):
|
|
||||||
USE_MY_METRICS = 0x0001
|
|
||||||
AXIS_INDICES_ARE_SHORT = 0x0002
|
|
||||||
UNIFORM_SCALE = 0x0004
|
|
||||||
HAVE_TRANSLATE_X = 0x0008
|
|
||||||
HAVE_TRANSLATE_Y = 0x0010
|
|
||||||
HAVE_ROTATION = 0x0020
|
|
||||||
HAVE_SCALE_X = 0x0040
|
|
||||||
HAVE_SCALE_Y = 0x0080
|
|
||||||
HAVE_SKEW_X = 0x0100
|
|
||||||
HAVE_SKEW_Y = 0x0200
|
|
||||||
HAVE_TCENTER_X = 0x0400
|
|
||||||
HAVE_TCENTER_Y = 0x0800
|
|
||||||
GID_IS_24BIT = 0x1000
|
|
||||||
AXES_HAVE_VARIATION = 0x2000
|
|
||||||
RESET_UNSPECIFIED_AXES = 0x4000
|
|
||||||
|
|
||||||
|
|
||||||
VarComponentTransformMappingValues = namedtuple(
|
|
||||||
"VarComponentTransformMappingValues",
|
|
||||||
["flag", "fractionalBits", "scale", "defaultValue"],
|
|
||||||
)
|
|
||||||
|
|
||||||
VAR_COMPONENT_TRANSFORM_MAPPING = {
|
|
||||||
"translateX": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_TRANSLATE_X, 0, 1, 0
|
|
||||||
),
|
|
||||||
"translateY": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_TRANSLATE_Y, 0, 1, 0
|
|
||||||
),
|
|
||||||
"rotation": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_ROTATION, 12, 180, 0
|
|
||||||
),
|
|
||||||
"scaleX": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_SCALE_X, 10, 1, 1
|
|
||||||
),
|
|
||||||
"scaleY": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_SCALE_Y, 10, 1, 1
|
|
||||||
),
|
|
||||||
"skewX": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_SKEW_X, 12, -180, 0
|
|
||||||
),
|
|
||||||
"skewY": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_SKEW_Y, 12, 180, 0
|
|
||||||
),
|
|
||||||
"tCenterX": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_TCENTER_X, 0, 1, 0
|
|
||||||
),
|
|
||||||
"tCenterY": VarComponentTransformMappingValues(
|
|
||||||
VarComponentFlags.HAVE_TCENTER_Y, 0, 1, 0
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GlyphVarComponent(object):
|
|
||||||
MIN_SIZE = 5
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.location = {}
|
|
||||||
self.transform = DecomposedTransform()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getSize(data):
|
|
||||||
size = 5
|
|
||||||
flags = struct.unpack(">H", data[:2])[0]
|
|
||||||
numAxes = int(data[2])
|
|
||||||
|
|
||||||
if flags & VarComponentFlags.GID_IS_24BIT:
|
|
||||||
size += 1
|
|
||||||
|
|
||||||
size += numAxes
|
|
||||||
if flags & VarComponentFlags.AXIS_INDICES_ARE_SHORT:
|
|
||||||
size += 2 * numAxes
|
|
||||||
else:
|
|
||||||
axisIndices = array.array("B", data[:numAxes])
|
|
||||||
size += numAxes
|
|
||||||
|
|
||||||
for attr_name, mapping_values in VAR_COMPONENT_TRANSFORM_MAPPING.items():
|
|
||||||
if flags & mapping_values.flag:
|
|
||||||
size += 2
|
|
||||||
|
|
||||||
return size
|
|
||||||
|
|
||||||
def decompile(self, data, glyfTable):
|
|
||||||
flags = struct.unpack(">H", data[:2])[0]
|
|
||||||
self.flags = int(flags)
|
|
||||||
data = data[2:]
|
|
||||||
|
|
||||||
numAxes = int(data[0])
|
|
||||||
data = data[1:]
|
|
||||||
|
|
||||||
if flags & VarComponentFlags.GID_IS_24BIT:
|
|
||||||
glyphID = int(struct.unpack(">L", b"\0" + data[:3])[0])
|
|
||||||
data = data[3:]
|
|
||||||
flags ^= VarComponentFlags.GID_IS_24BIT
|
|
||||||
else:
|
|
||||||
glyphID = int(struct.unpack(">H", data[:2])[0])
|
|
||||||
data = data[2:]
|
|
||||||
self.glyphName = glyfTable.getGlyphName(int(glyphID))
|
|
||||||
|
|
||||||
if flags & VarComponentFlags.AXIS_INDICES_ARE_SHORT:
|
|
||||||
axisIndices = array.array("H", data[: 2 * numAxes])
|
|
||||||
if sys.byteorder != "big":
|
|
||||||
axisIndices.byteswap()
|
|
||||||
data = data[2 * numAxes :]
|
|
||||||
flags ^= VarComponentFlags.AXIS_INDICES_ARE_SHORT
|
|
||||||
else:
|
|
||||||
axisIndices = array.array("B", data[:numAxes])
|
|
||||||
data = data[numAxes:]
|
|
||||||
assert len(axisIndices) == numAxes
|
|
||||||
axisIndices = list(axisIndices)
|
|
||||||
|
|
||||||
axisValues = array.array("h", data[: 2 * numAxes])
|
|
||||||
if sys.byteorder != "big":
|
|
||||||
axisValues.byteswap()
|
|
||||||
data = data[2 * numAxes :]
|
|
||||||
assert len(axisValues) == numAxes
|
|
||||||
axisValues = [fi2fl(v, 14) for v in axisValues]
|
|
||||||
|
|
||||||
self.location = {
|
|
||||||
glyfTable.axisTags[i]: v for i, v in zip(axisIndices, axisValues)
|
|
||||||
}
|
|
||||||
|
|
||||||
def read_transform_component(data, values):
|
|
||||||
if flags & values.flag:
|
|
||||||
return (
|
|
||||||
data[2:],
|
|
||||||
fi2fl(struct.unpack(">h", data[:2])[0], values.fractionalBits)
|
|
||||||
* values.scale,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return data, values.defaultValue
|
|
||||||
|
|
||||||
for attr_name, mapping_values in VAR_COMPONENT_TRANSFORM_MAPPING.items():
|
|
||||||
data, value = read_transform_component(data, mapping_values)
|
|
||||||
setattr(self.transform, attr_name, value)
|
|
||||||
|
|
||||||
if flags & VarComponentFlags.UNIFORM_SCALE:
|
|
||||||
if flags & VarComponentFlags.HAVE_SCALE_X and not (
|
|
||||||
flags & VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
):
|
|
||||||
self.transform.scaleY = self.transform.scaleX
|
|
||||||
flags |= VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
flags ^= VarComponentFlags.UNIFORM_SCALE
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def compile(self, glyfTable):
|
|
||||||
data = b""
|
|
||||||
|
|
||||||
if not hasattr(self, "flags"):
|
|
||||||
flags = 0
|
|
||||||
# Calculate optimal transform component flags
|
|
||||||
for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items():
|
|
||||||
value = getattr(self.transform, attr_name)
|
|
||||||
if fl2fi(value / mapping.scale, mapping.fractionalBits) != fl2fi(
|
|
||||||
mapping.defaultValue / mapping.scale, mapping.fractionalBits
|
|
||||||
):
|
|
||||||
flags |= mapping.flag
|
|
||||||
else:
|
|
||||||
flags = self.flags
|
|
||||||
|
|
||||||
if (
|
|
||||||
flags & VarComponentFlags.HAVE_SCALE_X
|
|
||||||
and flags & VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
and fl2fi(self.transform.scaleX, 10) == fl2fi(self.transform.scaleY, 10)
|
|
||||||
):
|
|
||||||
flags |= VarComponentFlags.UNIFORM_SCALE
|
|
||||||
flags ^= VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
|
|
||||||
numAxes = len(self.location)
|
|
||||||
|
|
||||||
data = data + struct.pack(">B", numAxes)
|
|
||||||
|
|
||||||
glyphID = glyfTable.getGlyphID(self.glyphName)
|
|
||||||
if glyphID > 65535:
|
|
||||||
flags |= VarComponentFlags.GID_IS_24BIT
|
|
||||||
data = data + struct.pack(">L", glyphID)[1:]
|
|
||||||
else:
|
|
||||||
data = data + struct.pack(">H", glyphID)
|
|
||||||
|
|
||||||
axisIndices = [glyfTable.axisTags.index(tag) for tag in self.location.keys()]
|
|
||||||
if all(a <= 255 for a in axisIndices):
|
|
||||||
axisIndices = array.array("B", axisIndices)
|
|
||||||
else:
|
|
||||||
axisIndices = array.array("H", axisIndices)
|
|
||||||
if sys.byteorder != "big":
|
|
||||||
axisIndices.byteswap()
|
|
||||||
flags |= VarComponentFlags.AXIS_INDICES_ARE_SHORT
|
|
||||||
data = data + bytes(axisIndices)
|
|
||||||
|
|
||||||
axisValues = self.location.values()
|
|
||||||
axisValues = array.array("h", (fl2fi(v, 14) for v in axisValues))
|
|
||||||
if sys.byteorder != "big":
|
|
||||||
axisValues.byteswap()
|
|
||||||
data = data + bytes(axisValues)
|
|
||||||
|
|
||||||
def write_transform_component(data, value, values):
|
|
||||||
if flags & values.flag:
|
|
||||||
return data + struct.pack(
|
|
||||||
">h", fl2fi(value / values.scale, values.fractionalBits)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return data
|
|
||||||
|
|
||||||
for attr_name, mapping_values in VAR_COMPONENT_TRANSFORM_MAPPING.items():
|
|
||||||
value = getattr(self.transform, attr_name)
|
|
||||||
data = write_transform_component(data, value, mapping_values)
|
|
||||||
|
|
||||||
return struct.pack(">H", flags) + data
|
|
||||||
|
|
||||||
def toXML(self, writer, ttFont):
|
|
||||||
attrs = [("glyphName", self.glyphName)]
|
|
||||||
|
|
||||||
if hasattr(self, "flags"):
|
|
||||||
attrs = attrs + [("flags", hex(self.flags))]
|
|
||||||
|
|
||||||
for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items():
|
|
||||||
v = getattr(self.transform, attr_name)
|
|
||||||
if v != mapping.defaultValue:
|
|
||||||
attrs.append((attr_name, fl2str(v, mapping.fractionalBits)))
|
|
||||||
|
|
||||||
writer.begintag("varComponent", attrs)
|
|
||||||
writer.newline()
|
|
||||||
|
|
||||||
writer.begintag("location")
|
|
||||||
writer.newline()
|
|
||||||
for tag, v in self.location.items():
|
|
||||||
writer.simpletag("axis", [("tag", tag), ("value", fl2str(v, 14))])
|
|
||||||
writer.newline()
|
|
||||||
writer.endtag("location")
|
|
||||||
writer.newline()
|
|
||||||
|
|
||||||
writer.endtag("varComponent")
|
|
||||||
writer.newline()
|
|
||||||
|
|
||||||
def fromXML(self, name, attrs, content, ttFont):
|
|
||||||
self.glyphName = attrs["glyphName"]
|
|
||||||
|
|
||||||
if "flags" in attrs:
|
|
||||||
self.flags = safeEval(attrs["flags"])
|
|
||||||
|
|
||||||
for attr_name, mapping in VAR_COMPONENT_TRANSFORM_MAPPING.items():
|
|
||||||
if attr_name not in attrs:
|
|
||||||
continue
|
|
||||||
v = str2fl(safeEval(attrs[attr_name]), mapping.fractionalBits)
|
|
||||||
setattr(self.transform, attr_name, v)
|
|
||||||
|
|
||||||
for c in content:
|
|
||||||
if not isinstance(c, tuple):
|
|
||||||
continue
|
|
||||||
name, attrs, content = c
|
|
||||||
if name != "location":
|
|
||||||
continue
|
|
||||||
for c in content:
|
|
||||||
if not isinstance(c, tuple):
|
|
||||||
continue
|
|
||||||
name, attrs, content = c
|
|
||||||
assert name == "axis"
|
|
||||||
assert not content
|
|
||||||
self.location[attrs["tag"]] = str2fl(safeEval(attrs["value"]), 14)
|
|
||||||
|
|
||||||
def getPointCount(self):
|
|
||||||
assert hasattr(self, "flags"), "VarComponent with variations must have flags"
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
if self.flags & VarComponentFlags.AXES_HAVE_VARIATION:
|
|
||||||
count += len(self.location)
|
|
||||||
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y
|
|
||||||
):
|
|
||||||
count += 1
|
|
||||||
if self.flags & VarComponentFlags.HAVE_ROTATION:
|
|
||||||
count += 1
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
):
|
|
||||||
count += 1
|
|
||||||
if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y):
|
|
||||||
count += 1
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y
|
|
||||||
):
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
return count
|
|
||||||
|
|
||||||
def getCoordinatesAndControls(self):
|
|
||||||
coords = []
|
|
||||||
controls = []
|
|
||||||
|
|
||||||
if self.flags & VarComponentFlags.AXES_HAVE_VARIATION:
|
|
||||||
for tag, v in self.location.items():
|
|
||||||
controls.append(tag)
|
|
||||||
coords.append((fl2fi(v, 14), 0))
|
|
||||||
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y
|
|
||||||
):
|
|
||||||
controls.append("translate")
|
|
||||||
coords.append((self.transform.translateX, self.transform.translateY))
|
|
||||||
if self.flags & VarComponentFlags.HAVE_ROTATION:
|
|
||||||
controls.append("rotation")
|
|
||||||
coords.append((fl2fi(self.transform.rotation / 180, 12), 0))
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
):
|
|
||||||
controls.append("scale")
|
|
||||||
coords.append(
|
|
||||||
(fl2fi(self.transform.scaleX, 10), fl2fi(self.transform.scaleY, 10))
|
|
||||||
)
|
|
||||||
if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y):
|
|
||||||
controls.append("skew")
|
|
||||||
coords.append(
|
|
||||||
(
|
|
||||||
fl2fi(self.transform.skewX / -180, 12),
|
|
||||||
fl2fi(self.transform.skewY / 180, 12),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y
|
|
||||||
):
|
|
||||||
controls.append("tCenter")
|
|
||||||
coords.append((self.transform.tCenterX, self.transform.tCenterY))
|
|
||||||
|
|
||||||
return coords, controls
|
|
||||||
|
|
||||||
def setCoordinates(self, coords):
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
if self.flags & VarComponentFlags.AXES_HAVE_VARIATION:
|
|
||||||
newLocation = {}
|
|
||||||
for tag in self.location:
|
|
||||||
newLocation[tag] = fi2fl(coords[i][0], 14)
|
|
||||||
i += 1
|
|
||||||
self.location = newLocation
|
|
||||||
|
|
||||||
self.transform = DecomposedTransform()
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_TRANSLATE_X | VarComponentFlags.HAVE_TRANSLATE_Y
|
|
||||||
):
|
|
||||||
self.transform.translateX, self.transform.translateY = coords[i]
|
|
||||||
i += 1
|
|
||||||
if self.flags & VarComponentFlags.HAVE_ROTATION:
|
|
||||||
self.transform.rotation = fi2fl(coords[i][0], 12) * 180
|
|
||||||
i += 1
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_SCALE_X | VarComponentFlags.HAVE_SCALE_Y
|
|
||||||
):
|
|
||||||
self.transform.scaleX, self.transform.scaleY = fi2fl(
|
|
||||||
coords[i][0], 10
|
|
||||||
), fi2fl(coords[i][1], 10)
|
|
||||||
i += 1
|
|
||||||
if self.flags & (VarComponentFlags.HAVE_SKEW_X | VarComponentFlags.HAVE_SKEW_Y):
|
|
||||||
self.transform.skewX, self.transform.skewY = (
|
|
||||||
fi2fl(coords[i][0], 12) * -180,
|
|
||||||
fi2fl(coords[i][1], 12) * 180,
|
|
||||||
)
|
|
||||||
i += 1
|
|
||||||
if self.flags & (
|
|
||||||
VarComponentFlags.HAVE_TCENTER_X | VarComponentFlags.HAVE_TCENTER_Y
|
|
||||||
):
|
|
||||||
self.transform.tCenterX, self.transform.tCenterY = coords[i]
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
return coords[i:]
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if type(self) != type(other):
|
|
||||||
return NotImplemented
|
|
||||||
return self.__dict__ == other.__dict__
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
result = self.__eq__(other)
|
|
||||||
return result if result is NotImplemented else not result
|
|
||||||
|
|
||||||
|
|
||||||
class GlyphCoordinates(object):
|
class GlyphCoordinates(object):
|
||||||
"""A list of glyph coordinates.
|
"""A list of glyph coordinates.
|
||||||
|
|
||||||
|
@ -245,11 +245,6 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
|
|||||||
|
|
||||||
if glyph.isComposite():
|
if glyph.isComposite():
|
||||||
return len(glyph.components) + NUM_PHANTOM_POINTS
|
return len(glyph.components) + NUM_PHANTOM_POINTS
|
||||||
elif glyph.isVarComposite():
|
|
||||||
count = 0
|
|
||||||
for component in glyph.components:
|
|
||||||
count += component.getPointCount()
|
|
||||||
return count + NUM_PHANTOM_POINTS
|
|
||||||
else:
|
else:
|
||||||
# Empty glyphs (eg. space, nonmarkingreturn) have no "coordinates" attribute.
|
# Empty glyphs (eg. space, nonmarkingreturn) have no "coordinates" attribute.
|
||||||
return len(getattr(glyph, "coordinates", [])) + NUM_PHANTOM_POINTS
|
return len(getattr(glyph, "coordinates", [])) + NUM_PHANTOM_POINTS
|
||||||
|
@ -178,10 +178,6 @@ class _TTGlyphGlyf(_TTGlyph):
|
|||||||
if depth:
|
if depth:
|
||||||
offset = 0 # Offset should only apply at top-level
|
offset = 0 # Offset should only apply at top-level
|
||||||
|
|
||||||
if glyph.isVarComposite():
|
|
||||||
self._drawVarComposite(glyph, pen, False)
|
|
||||||
return
|
|
||||||
|
|
||||||
glyph.draw(pen, self.glyphSet.glyfTable, offset)
|
glyph.draw(pen, self.glyphSet.glyfTable, offset)
|
||||||
|
|
||||||
def drawPoints(self, pen):
|
def drawPoints(self, pen):
|
||||||
@ -194,35 +190,8 @@ class _TTGlyphGlyf(_TTGlyph):
|
|||||||
if depth:
|
if depth:
|
||||||
offset = 0 # Offset should only apply at top-level
|
offset = 0 # Offset should only apply at top-level
|
||||||
|
|
||||||
if glyph.isVarComposite():
|
|
||||||
self._drawVarComposite(glyph, pen, True)
|
|
||||||
return
|
|
||||||
|
|
||||||
glyph.drawPoints(pen, self.glyphSet.glyfTable, offset)
|
glyph.drawPoints(pen, self.glyphSet.glyfTable, offset)
|
||||||
|
|
||||||
def _drawVarComposite(self, glyph, pen, isPointPen):
|
|
||||||
from fontTools.ttLib.tables._g_l_y_f import (
|
|
||||||
VarComponentFlags,
|
|
||||||
VAR_COMPONENT_TRANSFORM_MAPPING,
|
|
||||||
)
|
|
||||||
|
|
||||||
for comp in glyph.components:
|
|
||||||
with self.glyphSet.pushLocation(
|
|
||||||
comp.location, comp.flags & VarComponentFlags.RESET_UNSPECIFIED_AXES
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
pen.addVarComponent(
|
|
||||||
comp.glyphName, comp.transform, self.glyphSet.rawLocation
|
|
||||||
)
|
|
||||||
except AttributeError:
|
|
||||||
t = comp.transform.toTransform()
|
|
||||||
if isPointPen:
|
|
||||||
tPen = TransformPointPen(pen, t)
|
|
||||||
self.glyphSet[comp.glyphName].drawPoints(tPen)
|
|
||||||
else:
|
|
||||||
tPen = TransformPen(pen, t)
|
|
||||||
self.glyphSet[comp.glyphName].draw(tPen)
|
|
||||||
|
|
||||||
def _getGlyphAndOffset(self):
|
def _getGlyphAndOffset(self):
|
||||||
if self.glyphSet.location and self.glyphSet.gvarTable is not None:
|
if self.glyphSet.location and self.glyphSet.gvarTable is not None:
|
||||||
glyph = self._getGlyphInstance()
|
glyph = self._getGlyphInstance()
|
||||||
@ -300,11 +269,6 @@ def _setCoordinates(glyph, coord, glyfTable, *, recalcBounds=True):
|
|||||||
for p, comp in zip(coord, glyph.components):
|
for p, comp in zip(coord, glyph.components):
|
||||||
if hasattr(comp, "x"):
|
if hasattr(comp, "x"):
|
||||||
comp.x, comp.y = p
|
comp.x, comp.y = p
|
||||||
elif glyph.isVarComposite():
|
|
||||||
glyph.components = [copy(comp) for comp in glyph.components] # Shallow copy
|
|
||||||
for comp in glyph.components:
|
|
||||||
coord = comp.setCoordinates(coord)
|
|
||||||
assert not coord
|
|
||||||
elif glyph.numberOfContours == 0:
|
elif glyph.numberOfContours == 0:
|
||||||
assert len(coord) == 0
|
assert len(coord) == 0
|
||||||
else:
|
else:
|
||||||
|
@ -1017,8 +1017,6 @@ class WOFF2GlyfTable(getTableClass("glyf")):
|
|||||||
return
|
return
|
||||||
elif glyph.isComposite():
|
elif glyph.isComposite():
|
||||||
self._encodeComponents(glyph)
|
self._encodeComponents(glyph)
|
||||||
elif glyph.isVarComposite():
|
|
||||||
raise NotImplementedError
|
|
||||||
else:
|
else:
|
||||||
self._encodeCoordinates(glyph)
|
self._encodeCoordinates(glyph)
|
||||||
self._encodeOverlapSimpleFlag(glyph, glyphID)
|
self._encodeOverlapSimpleFlag(glyph, glyphID)
|
||||||
|
@ -843,23 +843,6 @@ def _instantiateGvarGlyph(
|
|||||||
if defaultDeltas:
|
if defaultDeltas:
|
||||||
coordinates += _g_l_y_f.GlyphCoordinates(defaultDeltas)
|
coordinates += _g_l_y_f.GlyphCoordinates(defaultDeltas)
|
||||||
|
|
||||||
glyph = glyf[glyphname]
|
|
||||||
if glyph.isVarComposite():
|
|
||||||
for component in glyph.components:
|
|
||||||
newLocation = {}
|
|
||||||
for tag, loc in component.location.items():
|
|
||||||
if tag not in axisLimits:
|
|
||||||
newLocation[tag] = loc
|
|
||||||
continue
|
|
||||||
if component.flags & _g_l_y_f.VarComponentFlags.AXES_HAVE_VARIATION:
|
|
||||||
raise NotImplementedError(
|
|
||||||
"Instancing accross VarComposite axes with variation is not supported."
|
|
||||||
)
|
|
||||||
limits = axisLimits[tag]
|
|
||||||
loc = limits.renormalizeValue(loc, extrapolate=False)
|
|
||||||
newLocation[tag] = loc
|
|
||||||
component.location = newLocation
|
|
||||||
|
|
||||||
# _setCoordinates also sets the hmtx/vmtx advance widths and sidebearings from
|
# _setCoordinates also sets the hmtx/vmtx advance widths and sidebearings from
|
||||||
# the four phantom points and glyph bounding boxes.
|
# the four phantom points and glyph bounding boxes.
|
||||||
# We call it unconditionally even if a glyph has no variations or no deltas are
|
# We call it unconditionally even if a glyph has no variations or no deltas are
|
||||||
@ -908,11 +891,9 @@ def instantiateGvar(varfont, axisLimits, optimize=True):
|
|||||||
glyphnames = sorted(
|
glyphnames = sorted(
|
||||||
glyf.glyphOrder,
|
glyf.glyphOrder,
|
||||||
key=lambda name: (
|
key=lambda name: (
|
||||||
(
|
glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth
|
||||||
glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth
|
if glyf[name].isComposite()
|
||||||
if glyf[name].isComposite() or glyf[name].isVarComposite()
|
else 0,
|
||||||
else 0
|
|
||||||
),
|
|
||||||
name,
|
name,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -199,11 +199,9 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
|
|||||||
glyphnames = sorted(
|
glyphnames = sorted(
|
||||||
gvar.variations.keys(),
|
gvar.variations.keys(),
|
||||||
key=lambda name: (
|
key=lambda name: (
|
||||||
(
|
glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth
|
||||||
glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth
|
if glyf[name].isComposite()
|
||||||
if glyf[name].isComposite() or glyf[name].isVarComposite()
|
else 0,
|
||||||
else 0
|
|
||||||
),
|
|
||||||
name,
|
name,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -307,9 +305,9 @@ def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
|
|||||||
if applies:
|
if applies:
|
||||||
assert record.FeatureTableSubstitution.Version == 0x00010000
|
assert record.FeatureTableSubstitution.Version == 0x00010000
|
||||||
for rec in record.FeatureTableSubstitution.SubstitutionRecord:
|
for rec in record.FeatureTableSubstitution.SubstitutionRecord:
|
||||||
table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature = (
|
table.FeatureList.FeatureRecord[
|
||||||
rec.Feature
|
rec.FeatureIndex
|
||||||
)
|
].Feature = rec.Feature
|
||||||
break
|
break
|
||||||
del table.FeatureVariations
|
del table.FeatureVariations
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user