Home | History | Annotate | Download | only in pens
      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