1 from __future__ import print_function, division, absolute_import 2 from fontTools.misc.py23 import * 3 from array import array 4 from fontTools.pens.basePen import LoggingPen 5 from fontTools.pens.transformPen import TransformPen 6 from fontTools.ttLib.tables import ttProgram 7 from fontTools.ttLib.tables._g_l_y_f import Glyph 8 from fontTools.ttLib.tables._g_l_y_f import GlyphComponent 9 from fontTools.ttLib.tables._g_l_y_f import GlyphCoordinates 10 11 12 __all__ = ["TTGlyphPen"] 13 14 15 # the max value that can still fit in an F2Dot14: 16 # 1.99993896484375 17 MAX_F2DOT14 = 0x7FFF / (1 << 14) 18 19 20 class TTGlyphPen(LoggingPen): 21 """Pen used for drawing to a TrueType glyph. 22 23 If `handleOverflowingTransforms` is True, the components' transform values 24 are checked that they don't overflow the limits of a F2Dot14 number: 25 -2.0 <= v < +2.0. If any transform value exceeds these, the composite 26 glyph is decomposed. 27 An exception to this rule is done for values that are very close to +2.0 28 (both for consistency with the -2.0 case, and for the relative frequency 29 these occur in real fonts). When almost +2.0 values occur (and all other 30 values are within the range -2.0 <= x <= +2.0), they are clamped to the 31 maximum positive value that can still be encoded as an F2Dot14: i.e. 32 1.99993896484375. 33 If False, no check is done and all components are translated unmodified 34 into the glyf table, followed by an inevitable `struct.error` once an 35 attempt is made to compile them. 36 """ 37 38 def __init__(self, glyphSet, handleOverflowingTransforms=True): 39 self.glyphSet = glyphSet 40 self.handleOverflowingTransforms = handleOverflowingTransforms 41 self.init() 42 43 def init(self): 44 self.points = [] 45 self.endPts = [] 46 self.types = [] 47 self.components = [] 48 49 def _addPoint(self, pt, onCurve): 50 self.points.append(pt) 51 self.types.append(onCurve) 52 53 def _popPoint(self): 54 self.points.pop() 55 self.types.pop() 56 57 def _isClosed(self): 58 return ( 59 (not self.points) or 60 (self.endPts and self.endPts[-1] == len(self.points) - 1)) 61 62 def lineTo(self, pt): 63 self._addPoint(pt, 1) 64 65 def moveTo(self, pt): 66 assert self._isClosed(), '"move"-type point must begin a new contour.' 67 self._addPoint(pt, 1) 68 69 def qCurveTo(self, *points): 70 assert len(points) >= 1 71 for pt in points[:-1]: 72 self._addPoint(pt, 0) 73 74 # last point is None if there are no on-curve points 75 if points[-1] is not None: 76 self._addPoint(points[-1], 1) 77 78 def closePath(self): 79 endPt = len(self.points) - 1 80 81 # ignore anchors (one-point paths) 82 if endPt == 0 or (self.endPts and endPt == self.endPts[-1] + 1): 83 self._popPoint() 84 return 85 86 # if first and last point on this path are the same, remove last 87 startPt = 0 88 if self.endPts: 89 startPt = self.endPts[-1] + 1 90 if self.points[startPt] == self.points[endPt]: 91 self._popPoint() 92 endPt -= 1 93 94 self.endPts.append(endPt) 95 96 def endPath(self): 97 # TrueType contours are always "closed" 98 self.closePath() 99 100 def addComponent(self, glyphName, transformation): 101 self.components.append((glyphName, transformation)) 102 103 def _buildComponents(self, componentFlags): 104 if self.handleOverflowingTransforms: 105 # we can't encode transform values > 2 or < -2 in F2Dot14, 106 # so we must decompose the glyph if any transform exceeds these 107 overflowing = any(s > 2 or s < -2 108 for (glyphName, transformation) in self.components 109 for s in transformation[:4]) 110 components = [] 111 for glyphName, transformation in self.components: 112 if glyphName not in self.glyphSet: 113 self.log.warning( 114 "skipped non-existing component '%s'", glyphName 115 ) 116 continue 117 if (self.points or 118 (self.handleOverflowingTransforms and overflowing)): 119 # can't have both coordinates and components, so decompose 120 tpen = TransformPen(self, transformation) 121 self.glyphSet[glyphName].draw(tpen) 122 continue 123 124 component = GlyphComponent() 125 component.glyphName = glyphName 126 component.x, component.y = transformation[4:] 127 transformation = transformation[:4] 128 if transformation != (1, 0, 0, 1): 129 if (self.handleOverflowingTransforms and 130 any(MAX_F2DOT14 < s <= 2 for s in transformation)): 131 # clamp values ~= +2.0 so we can keep the component 132 transformation = tuple(MAX_F2DOT14 if MAX_F2DOT14 < s <= 2 133 else s for s in transformation) 134 component.transform = (transformation[:2], transformation[2:]) 135 component.flags = componentFlags 136 components.append(component) 137 return components 138 139 def glyph(self, componentFlags=0x4): 140 assert self._isClosed(), "Didn't close last contour." 141 142 components = self._buildComponents(componentFlags) 143 144 glyph = Glyph() 145 glyph.coordinates = GlyphCoordinates(self.points) 146 glyph.endPtsOfContours = self.endPts 147 glyph.flags = array("B", self.types) 148 self.init() 149 150 if components: 151 glyph.components = components 152 glyph.numberOfContours = -1 153 else: 154 glyph.numberOfContours = len(glyph.endPtsOfContours) 155 glyph.program = ttProgram.Program() 156 glyph.program.fromBytecode(b"") 157 158 return glyph 159