Merge pull request #2788 from fonttools/varglyphset-spaces

[ttVarGlyphSet] Convert tabs to spaces
This commit is contained in:
Just van Rossum 2022-08-29 19:33:23 +02:00 committed by GitHub
commit 4abf2ee8c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -6,273 +6,273 @@ from copy import copy
class _TTGlyphSet(object): class _TTGlyphSet(object):
"""Generic dict-like GlyphSet class that pulls metrics from hmtx and """Generic dict-like GlyphSet class that pulls metrics from hmtx and
glyph shape from TrueType or CFF. glyph shape from TrueType or CFF.
""" """
def __init__(self, ttFont, glyphs, glyphType): def __init__(self, ttFont, glyphs, glyphType):
"""Construct a new glyphset. """Construct a new glyphset.
Args: Args:
font (TTFont): The font object (used to get metrics). font (TTFont): The font object (used to get metrics).
glyphs (dict): A dictionary mapping glyph names to ``_TTGlyph`` objects. glyphs (dict): A dictionary mapping glyph names to ``_TTGlyph`` objects.
glyphType (class): Either ``_TTGlyphCFF`` or ``_TTGlyphGlyf``. glyphType (class): Either ``_TTGlyphCFF`` or ``_TTGlyphGlyf``.
""" """
self._glyphs = glyphs self._glyphs = glyphs
self._hmtx = ttFont["hmtx"] self._hmtx = ttFont["hmtx"]
self._vmtx = ttFont["vmtx"] if "vmtx" in ttFont else None self._vmtx = ttFont["vmtx"] if "vmtx" in ttFont else None
self._glyphType = glyphType self._glyphType = glyphType
def keys(self): def keys(self):
return list(self._glyphs.keys()) return list(self._glyphs.keys())
def has_key(self, glyphName): def has_key(self, glyphName):
return glyphName in self._glyphs return glyphName in self._glyphs
__contains__ = has_key __contains__ = has_key
def __getitem__(self, glyphName): def __getitem__(self, glyphName):
horizontalMetrics = self._hmtx[glyphName] horizontalMetrics = self._hmtx[glyphName]
verticalMetrics = self._vmtx[glyphName] if self._vmtx else None verticalMetrics = self._vmtx[glyphName] if self._vmtx else None
return self._glyphType( return self._glyphType(
self, self._glyphs[glyphName], horizontalMetrics, verticalMetrics self, self._glyphs[glyphName], horizontalMetrics, verticalMetrics
) )
def __len__(self): def __len__(self):
return len(self._glyphs) return len(self._glyphs)
def get(self, glyphName, default=None): def get(self, glyphName, default=None):
try: try:
return self[glyphName] return self[glyphName]
except KeyError: except KeyError:
return default return default
class _TTGlyph(object): class _TTGlyph(object):
"""Wrapper for a TrueType glyph that supports the Pen protocol, meaning """Wrapper for a TrueType glyph that supports the Pen protocol, meaning
that it has .draw() and .drawPoints() methods that take a pen object as that it has .draw() and .drawPoints() methods that take a pen object as
their only argument. Additionally there are 'width' and 'lsb' attributes, their only argument. Additionally there are 'width' and 'lsb' attributes,
read from the 'hmtx' table. read from the 'hmtx' table.
If the font contains a 'vmtx' table, there will also be 'height' and 'tsb' If the font contains a 'vmtx' table, there will also be 'height' and 'tsb'
attributes. attributes.
""" """
def __init__(self, glyphset, glyph, horizontalMetrics=None, verticalMetrics=None): def __init__(self, glyphset, glyph, horizontalMetrics=None, verticalMetrics=None):
"""Construct a new _TTGlyph. """Construct a new _TTGlyph.
Args: Args:
glyphset (_TTGlyphSet): A glyphset object used to resolve components. glyphset (_TTGlyphSet): A glyphset object used to resolve components.
glyph (ttLib.tables._g_l_y_f.Glyph): The glyph object. glyph (ttLib.tables._g_l_y_f.Glyph): The glyph object.
horizontalMetrics (int, int): The glyph's width and left sidebearing. horizontalMetrics (int, int): The glyph's width and left sidebearing.
""" """
self._glyphset = glyphset self._glyphset = glyphset
self._glyph = glyph self._glyph = glyph
if horizontalMetrics: if horizontalMetrics:
self.width, self.lsb = horizontalMetrics self.width, self.lsb = horizontalMetrics
else: else:
self.width, self.lsb = None, None self.width, self.lsb = None, None
if verticalMetrics: if verticalMetrics:
self.height, self.tsb = verticalMetrics self.height, self.tsb = verticalMetrics
else: else:
self.height, self.tsb = None, None self.height, self.tsb = None, None
def draw(self, pen): def draw(self, pen):
"""Draw the glyph onto ``pen``. See fontTools.pens.basePen for details """Draw the glyph onto ``pen``. See fontTools.pens.basePen for details
how that works. how that works.
""" """
self._glyph.draw(pen) self._glyph.draw(pen)
def drawPoints(self, pen): def drawPoints(self, pen):
from fontTools.pens.pointPen import SegmentToPointPen from fontTools.pens.pointPen import SegmentToPointPen
self.draw(SegmentToPointPen(pen)) self.draw(SegmentToPointPen(pen))
class _TTGlyphCFF(_TTGlyph): class _TTGlyphCFF(_TTGlyph):
pass pass
class _TTGlyphGlyf(_TTGlyph): class _TTGlyphGlyf(_TTGlyph):
def draw(self, pen): def draw(self, pen):
"""Draw the glyph onto Pen. See fontTools.pens.basePen for details """Draw the glyph onto Pen. See fontTools.pens.basePen for details
how that works. how that works.
""" """
glyfTable = self._glyphset._glyphs glyfTable = self._glyphset._glyphs
glyph = self._glyph glyph = self._glyph
offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0 offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
glyph.draw(pen, glyfTable, offset) glyph.draw(pen, glyfTable, offset)
def drawPoints(self, pen): def drawPoints(self, pen):
"""Draw the glyph onto PointPen. See fontTools.pens.pointPen """Draw the glyph onto PointPen. See fontTools.pens.pointPen
for details how that works. for details how that works.
""" """
glyfTable = self._glyphset._glyphs glyfTable = self._glyphset._glyphs
glyph = self._glyph glyph = self._glyph
offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0 offset = self.lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
glyph.drawPoints(pen, glyfTable, offset) glyph.drawPoints(pen, glyfTable, offset)
class _TTVarGlyphSet(_TTGlyphSet): class _TTVarGlyphSet(_TTGlyphSet):
def __init__(self, font, glyphs, glyphType, location, normalized): def __init__(self, font, glyphs, glyphType, location, normalized):
self._ttFont = font self._ttFont = font
self._glyphs = glyphs self._glyphs = glyphs
self._glyphType = glyphType self._glyphType = glyphType
if not normalized: if not normalized:
from fontTools.varLib.models import normalizeLocation, piecewiseLinearMap from fontTools.varLib.models import normalizeLocation, piecewiseLinearMap
axes = { axes = {
a.axisTag: (a.minValue, a.defaultValue, a.maxValue) a.axisTag: (a.minValue, a.defaultValue, a.maxValue)
for a in font["fvar"].axes for a in font["fvar"].axes
} }
location = normalizeLocation(location, axes) location = normalizeLocation(location, axes)
if "avar" in font: if "avar" in font:
avar = font["avar"] avar = font["avar"]
avarSegments = avar.segments avarSegments = avar.segments
new_location = {} new_location = {}
for axis_tag, value in location.items(): for axis_tag, value in location.items():
avarMapping = avarSegments.get(axis_tag, None) avarMapping = avarSegments.get(axis_tag, None)
if avarMapping is not None: if avarMapping is not None:
value = piecewiseLinearMap(value, avarMapping) value = piecewiseLinearMap(value, avarMapping)
new_location[axis_tag] = value new_location[axis_tag] = value
location = new_location location = new_location
del new_location del new_location
self.location = location self.location = location
def __getitem__(self, glyphName): def __getitem__(self, glyphName):
if glyphName not in self._glyphs: if glyphName not in self._glyphs:
raise KeyError(glyphName) raise KeyError(glyphName)
return self._glyphType(self, glyphName, self.location) return self._glyphType(self, glyphName, self.location)
def _setCoordinates(glyph, coord, glyfTable): def _setCoordinates(glyph, coord, glyfTable):
# Handle phantom points for (left, right, top, bottom) positions. # Handle phantom points for (left, right, top, bottom) positions.
assert len(coord) >= 4 assert len(coord) >= 4
if not hasattr(glyph, "xMin"): if not hasattr(glyph, "xMin"):
glyph.recalcBounds(glyfTable) glyph.recalcBounds(glyfTable)
leftSideX = coord[-4][0] leftSideX = coord[-4][0]
rightSideX = coord[-3][0] rightSideX = coord[-3][0]
topSideY = coord[-2][1] topSideY = coord[-2][1]
bottomSideY = coord[-1][1] bottomSideY = coord[-1][1]
for _ in range(4): for _ in range(4):
del coord[-1] del coord[-1]
if glyph.isComposite(): if glyph.isComposite():
assert len(coord) == len(glyph.components) assert len(coord) == len(glyph.components)
glyph.components = [copy(comp) for comp in glyph.components] glyph.components = [copy(comp) for comp in glyph.components]
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.numberOfContours == 0: elif glyph.numberOfContours == 0:
assert len(coord) == 0 assert len(coord) == 0
else: else:
assert len(coord) == len(glyph.coordinates) assert len(coord) == len(glyph.coordinates)
glyph.coordinates = coord glyph.coordinates = coord
glyph.recalcBounds(glyfTable) glyph.recalcBounds(glyfTable)
horizontalAdvanceWidth = otRound(rightSideX - leftSideX) horizontalAdvanceWidth = otRound(rightSideX - leftSideX)
verticalAdvanceWidth = otRound(topSideY - bottomSideY) verticalAdvanceWidth = otRound(topSideY - bottomSideY)
leftSideBearing = otRound(glyph.xMin - leftSideX) leftSideBearing = otRound(glyph.xMin - leftSideX)
topSideBearing = otRound(topSideY - glyph.yMax) topSideBearing = otRound(topSideY - glyph.yMax)
return ( return (
horizontalAdvanceWidth, horizontalAdvanceWidth,
leftSideBearing, leftSideBearing,
verticalAdvanceWidth, verticalAdvanceWidth,
topSideBearing, topSideBearing,
) )
class _TTVarGlyph(_TTGlyph): class _TTVarGlyph(_TTGlyph):
def __init__(self, glyphSet, glyphName, location): def __init__(self, glyphSet, glyphName, location):
super().__init__(glyphSet._glyphs, glyphSet._glyphs[glyphName]) super().__init__(glyphSet._glyphs, glyphSet._glyphs[glyphName])
self._glyphSet = glyphSet self._glyphSet = glyphSet
self._ttFont = glyphSet._ttFont self._ttFont = glyphSet._ttFont
self._glyphs = glyphSet._glyphs self._glyphs = glyphSet._glyphs
self._glyphName = glyphName self._glyphName = glyphName
self._location = location self._location = location
class _TTVarGlyphCFF(_TTVarGlyph): class _TTVarGlyphCFF(_TTVarGlyph):
def draw(self, pen): def draw(self, pen):
varStore = self._glyphs.varStore varStore = self._glyphs.varStore
if varStore is None: if varStore is None:
blender = None blender = None
else: else:
from fontTools.varLib.varStore import VarStoreInstancer from fontTools.varLib.varStore import VarStoreInstancer
vsInstancer = getattr(self._glyphSet, "vsInstancer", None) vsInstancer = getattr(self._glyphSet, "vsInstancer", None)
if vsInstancer is None: if vsInstancer is None:
self._glyphSet.vsInstancer = vsInstancer = VarStoreInstancer( self._glyphSet.vsInstancer = vsInstancer = VarStoreInstancer(
varStore.otVarStore, self._ttFont["fvar"].axes, self._location varStore.otVarStore, self._ttFont["fvar"].axes, self._location
) )
blender = vsInstancer.interpolateFromDeltas blender = vsInstancer.interpolateFromDeltas
self._glyph.draw(pen, blender) self._glyph.draw(pen, blender)
self.width = self._ttFont["hmtx"][self._glyphName][0] self.width = self._ttFont["hmtx"][self._glyphName][0]
if "HVAR" in self._ttFont: if "HVAR" in self._ttFont:
hvar = self._ttFont["HVAR"].table hvar = self._ttFont["HVAR"].table
varidx = self._ttFont.getGlyphID(self._glyphName) varidx = self._ttFont.getGlyphID(self._glyphName)
if hvar.AdvWidthMap is not None: if hvar.AdvWidthMap is not None:
varidx = hvar.AdvWidthMap.mapping[self._glyphName] varidx = hvar.AdvWidthMap.mapping[self._glyphName]
vsInstancer = VarStoreInstancer( vsInstancer = VarStoreInstancer(
hvar.VarStore, self._ttFont["fvar"].axes, self._location hvar.VarStore, self._ttFont["fvar"].axes, self._location
) )
delta = vsInstancer[varidx] delta = vsInstancer[varidx]
self.width += delta self.width += delta
class _TTVarGlyphGlyf(_TTVarGlyph): class _TTVarGlyphGlyf(_TTVarGlyph):
def draw(self, pen): def draw(self, pen):
self._drawWithPen(pen, isPointPen=False) self._drawWithPen(pen, isPointPen=False)
def drawPoints(self, pen): def drawPoints(self, pen):
self._drawWithPen(pen, isPointPen=True) self._drawWithPen(pen, isPointPen=True)
def _drawWithPen(self, pen, isPointPen): def _drawWithPen(self, pen, isPointPen):
from fontTools.varLib.iup import iup_delta from fontTools.varLib.iup import iup_delta
from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates
from fontTools.varLib.models import supportScalar from fontTools.varLib.models import supportScalar
glyf = self._ttFont["glyf"] glyf = self._ttFont["glyf"]
hMetrics = self._ttFont["hmtx"].metrics hMetrics = self._ttFont["hmtx"].metrics
vMetrics = getattr(self._ttFont.get("vmtx"), "metrics", None) vMetrics = getattr(self._ttFont.get("vmtx"), "metrics", None)
variations = self._ttFont["gvar"].variations[self._glyphName] variations = self._ttFont["gvar"].variations[self._glyphName]
coordinates, _ = glyf._getCoordinatesAndControls( coordinates, _ = glyf._getCoordinatesAndControls(
self._glyphName, hMetrics, vMetrics self._glyphName, hMetrics, vMetrics
) )
origCoords, endPts = None, None origCoords, endPts = None, None
for var in variations: for var in variations:
scalar = supportScalar(self._location, var.axes) scalar = supportScalar(self._location, var.axes)
if not scalar: if not scalar:
continue continue
delta = var.coordinates delta = var.coordinates
if None in delta: if None in delta:
if origCoords is None: if origCoords is None:
origCoords, control = glyf._getCoordinatesAndControls( origCoords, control = glyf._getCoordinatesAndControls(
self._glyphName, hMetrics, vMetrics self._glyphName, hMetrics, vMetrics
) )
endPts = ( endPts = (
control[1] if control[0] >= 1 else list(range(len(control[1]))) control[1] if control[0] >= 1 else list(range(len(control[1])))
) )
delta = iup_delta(delta, origCoords, endPts) delta = iup_delta(delta, origCoords, endPts)
coordinates += GlyphCoordinates(delta) * scalar coordinates += GlyphCoordinates(delta) * scalar
glyph = copy(glyf[self._glyphName]) # Shallow copy glyph = copy(glyf[self._glyphName]) # Shallow copy
width, lsb, height, tsb = _setCoordinates(glyph, coordinates, glyf) width, lsb, height, tsb = _setCoordinates(glyph, coordinates, glyf)
self.width = width self.width = width
self.lsb = lsb self.lsb = lsb
self.height = height self.height = height
self.tsb = tsb self.tsb = tsb
offset = lsb - glyph.xMin if hasattr(glyph, "xMin") else 0 offset = lsb - glyph.xMin if hasattr(glyph, "xMin") else 0
if isPointPen: if isPointPen:
glyph.drawPoints(pen, glyf, offset) glyph.drawPoints(pen, glyf, offset)
else: else:
glyph.draw(pen, glyf, offset) glyph.draw(pen, glyf, offset)