from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * from array import array from fontTools.pens.basePen import LoggingPen from fontTools.pens.transformPen import TransformPen from fontTools.ttLib.tables import ttProgram from fontTools.ttLib.tables._g_l_y_f import Glyph from fontTools.ttLib.tables._g_l_y_f import GlyphComponent from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates __all__ = ["TTGlyphPen"] # the max value that can still fit in an F2Dot14: # 1.99993896484375 MAX_F2DOT14 = 0x7FFF / (1 << 14) class TTGlyphPen(LoggingPen): """Pen used for drawing to a TrueType glyph. If `handleOverflowingTransforms` is True, the components' transform values are checked that they don't overflow the limits of a F2Dot14 number: -2.0 <= v < +2.0. If any transform value exceeds these, the composite glyph is decomposed. An exception to this rule is done for values that are very close to +2.0 (both for consistency with the -2.0 case, and for the relative frequency these occur in real fonts). When almost +2.0 values occur (and all other values are within the range -2.0 <= x <= +2.0), they are clamped to the maximum positive value that can still be encoded as an F2Dot14: i.e. 1.99993896484375. If False, no check is done and all components are translated unmodified into the glyf table, followed by an inevitable `struct.error` once an attempt is made to compile them. """ def __init__(self, glyphSet, handleOverflowingTransforms=True): self.glyphSet = glyphSet self.handleOverflowingTransforms = handleOverflowingTransforms self.init() def init(self): self.points = [] self.endPts = [] self.types = [] self.components = [] def _addPoint(self, pt, onCurve): self.points.append(pt) self.types.append(onCurve) def _popPoint(self): self.points.pop() self.types.pop() def _isClosed(self): return ( (not self.points) or (self.endPts and self.endPts[-1] == len(self.points) - 1)) def lineTo(self, pt): self._addPoint(pt, 1) def moveTo(self, pt): assert self._isClosed(), '"move"-type point must begin a new contour.' self._addPoint(pt, 1) def qCurveTo(self, *points): assert len(points) >= 1 for pt in points[:-1]: self._addPoint(pt, 0) # last point is None if there are no on-curve points if points[-1] is not None: self._addPoint(points[-1], 1) def closePath(self): endPt = len(self.points) - 1 # ignore anchors (one-point paths) if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1): self._popPoint() return # if first and last point on this path are the same, remove last startPt = 0 if self.endPts: startPt = self.endPts[-1] + 1 if self.points[startPt] == self.points[endPt]: self._popPoint() endPt -= 1 self.endPts.append(endPt) def endPath(self): # TrueType contours are always "closed" self.closePath() def addComponent(self, glyphName, transformation): self.components.append((glyphName, transformation)) def _buildComponents(self, componentFlags): if self.handleOverflowingTransforms: # we can't encode transform values > 2 or < -2 in F2Dot14, # so we must decompose the glyph if any transform exceeds these overflowing = any(s > 2 or s < -2 for (glyphName, transformation) in self.components for s in transformation[:4]) components = [] for glyphName, transformation in self.components: if glyphName not in self.glyphSet: self.log.warning( "skipped non-existing component '%s'", glyphName ) continue if (self.points or (self.handleOverflowingTransforms and overflowing)): # can't have both coordinates and components, so decompose tpen = TransformPen(self, transformation) self.glyphSet[glyphName].draw(tpen) continue component = GlyphComponent() component.glyphName = glyphName component.x, component.y = transformation[4:] transformation = transformation[:4] if transformation != (1, 0, 0, 1): if (self.handleOverflowingTransforms and any(MAX_F2DOT14 < s <= 2 for s in transformation)): # clamp values ~= +2.0 so we can keep the component transformation = tuple(MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 else s for s in transformation) component.transform = (transformation[:2], transformation[2:]) component.flags = componentFlags components.append(component) return components def glyph(self, componentFlags=0x4): assert self._isClosed(), "Didn't close last contour." components = self._buildComponents(componentFlags) glyph = Glyph() glyph.coordinates = GlyphCoordinates(self.points) glyph.endPtsOfContours = self.endPts glyph.flags = array("B", self.types) self.init() if components: glyph.components = components glyph.numberOfContours = -1 else: glyph.numberOfContours = len(glyph.endPtsOfContours) glyph.program = ttProgram.Program() glyph.program.fromBytecode(b"") return glyph