1# Copyright (c) 2009 Type Supply LLC 2# Author: Tal Leming 3 4from __future__ import print_function, division, absolute_import 5from fontTools.misc.py23 import * 6from fontTools.misc.fixedTools import otRound 7from fontTools.misc.psCharStrings import T2CharString 8from fontTools.pens.basePen import BasePen 9from fontTools.cffLib.specializer import specializeCommands, commandsToProgram 10 11 12def t2c_round(number, tolerance=0.5): 13 if tolerance == 0: 14 return number # no-op 15 rounded = otRound(number) 16 # return rounded integer if the tolerance >= 0.5, or if the absolute 17 # difference between the original float and the rounded integer is 18 # within the tolerance 19 if tolerance >= .5 or abs(rounded - number) <= tolerance: 20 return rounded 21 else: 22 # else return the value un-rounded 23 return number 24 25def makeRoundFunc(tolerance): 26 if tolerance < 0: 27 raise ValueError("Rounding tolerance must be positive") 28 29 def roundPoint(point): 30 x, y = point 31 return t2c_round(x, tolerance), t2c_round(y, tolerance) 32 33 return roundPoint 34 35 36class T2CharStringPen(BasePen): 37 """Pen to draw Type 2 CharStrings. 38 39 The 'roundTolerance' argument controls the rounding of point coordinates. 40 It is defined as the maximum absolute difference between the original 41 float and the rounded integer value. 42 The default tolerance of 0.5 means that all floats are rounded to integer; 43 a value of 0 disables rounding; values in between will only round floats 44 which are close to their integral part within the tolerated range. 45 """ 46 47 def __init__(self, width, glyphSet, roundTolerance=0.5, CFF2=False): 48 super(T2CharStringPen, self).__init__(glyphSet) 49 self.roundPoint = makeRoundFunc(roundTolerance) 50 self._CFF2 = CFF2 51 self._width = width 52 self._commands = [] 53 self._p0 = (0,0) 54 55 def _p(self, pt): 56 p0 = self._p0 57 pt = self._p0 = self.roundPoint(pt) 58 return [pt[0]-p0[0], pt[1]-p0[1]] 59 60 def _moveTo(self, pt): 61 self._commands.append(('rmoveto', self._p(pt))) 62 63 def _lineTo(self, pt): 64 self._commands.append(('rlineto', self._p(pt))) 65 66 def _curveToOne(self, pt1, pt2, pt3): 67 _p = self._p 68 self._commands.append(('rrcurveto', _p(pt1)+_p(pt2)+_p(pt3))) 69 70 def _closePath(self): 71 pass 72 73 def _endPath(self): 74 pass 75 76 def getCharString(self, private=None, globalSubrs=None, optimize=True): 77 commands = self._commands 78 if optimize: 79 maxstack = 48 if not self._CFF2 else 513 80 commands = specializeCommands(commands, 81 generalizeFirst=False, 82 maxstack=maxstack) 83 program = commandsToProgram(commands) 84 if self._width is not None: 85 assert not self._CFF2, "CFF2 does not allow encoding glyph width in CharString." 86 program.insert(0, otRound(self._width)) 87 if not self._CFF2: 88 program.append('endchar') 89 charString = T2CharString( 90 program=program, private=private, globalSubrs=globalSubrs) 91 return charString 92