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