"""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): # drawPoints is only implemented for _TTGlyphGlyf at this time. raise NotImplementedError() 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(object): def __init__(self, font, location, normalized=False): from fontTools.varLib.models import normalizeLocation, piecewiseLinearMap self._ttFont = font if not normalized: 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 keys(self): return list(self._ttFont['glyf'].keys()) def has_key(self, glyphName): return glyphName in self._ttFont['glyf'] __contains__ = has_key def __getitem__(self, glyphName): return _TTVarGlyphGlyf(self._ttFont, glyphName, self.location) def get(self, glyphName, default=None): try: return self[glyphName] except KeyError: return default 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) return horizontalAdvanceWidth, leftSideBearing, verticalAdvanceWidth class _TTVarGlyphGlyf(object): def __init__(self, ttFont, glyphName, location): self._ttFont = ttFont self._glyphName = glyphName self._location = location self.width = None # draw fills it in 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 horizontalAdvanceWidth, leftSideBearing, verticalAdvanceWidth = _setCoordinates(glyph, coordinates, glyf) self.width = horizontalAdvanceWidth self.height = verticalAdvanceWidth offset = leftSideBearing - glyph.xMin if hasattr(glyph, "xMin") else 0 glyph.draw(pen, glyf, offset)