Blob Blame History Raw
# Copyright (c) 2009 Type Supply LLC
# Author: Tal Leming

from __future__ import print_function, division, absolute_import
from fontTools.misc.py23 import *
from fontTools.misc.fixedTools import otRound
from fontTools.misc.psCharStrings import T2CharString
from fontTools.pens.basePen import BasePen
from fontTools.cffLib.specializer import specializeCommands, commandsToProgram


def makeRoundFunc(tolerance):
    if tolerance < 0:
        raise ValueError("Rounding tolerance must be positive")

    def _round(number):
        if tolerance == 0:
            return number  # no-op
        rounded = otRound(number)
        # return rounded integer if the tolerance >= 0.5, or if the absolute
        # difference between the original float and the rounded integer is
        # within the tolerance
        if tolerance >= .5 or abs(rounded - number) <= tolerance:
            return rounded
        else:
            # else return the value un-rounded
            return number

    def roundPoint(point):
        x, y = point
        return _round(x), _round(y)

    return roundPoint


class T2CharStringPen(BasePen):
    """Pen to draw Type 2 CharStrings.

    The 'roundTolerance' argument controls the rounding of point coordinates.
    It is defined as the maximum absolute difference between the original
    float and the rounded integer value.
    The default tolerance of 0.5 means that all floats are rounded to integer;
    a value of 0 disables rounding; values in between will only round floats
    which are close to their integral part within the tolerated range.
    """

    def __init__(self, width, glyphSet, roundTolerance=0.5, CFF2=False):
        super(T2CharStringPen, self).__init__(glyphSet)
        self.roundPoint = makeRoundFunc(roundTolerance)
        self._CFF2 = CFF2
        self._width = width
        self._commands = []
        self._p0 = (0,0)

    def _p(self, pt):
        p0 = self._p0
        pt = self._p0 = self.roundPoint(pt)
        return [pt[0]-p0[0], pt[1]-p0[1]]

    def _moveTo(self, pt):
        self._commands.append(('rmoveto', self._p(pt)))

    def _lineTo(self, pt):
        self._commands.append(('rlineto', self._p(pt)))

    def _curveToOne(self, pt1, pt2, pt3):
        _p = self._p
        self._commands.append(('rrcurveto', _p(pt1)+_p(pt2)+_p(pt3)))

    def _closePath(self):
        pass

    def _endPath(self):
        pass

    def getCharString(self, private=None, globalSubrs=None, optimize=True):
        commands = self._commands
        if optimize:
            maxstack = 48 if not self._CFF2 else 513
            commands = specializeCommands(commands,
                                          generalizeFirst=False,
                                          maxstack=maxstack)
        program = commandsToProgram(commands)
        if self._width is not None:
            assert not self._CFF2, "CFF2 does not allow encoding glyph width in CharString."
            program.insert(0, otRound(self._width))
        if not self._CFF2:
            program.append('endchar')
        charString = T2CharString(
            program=program, private=private, globalSubrs=globalSubrs)
        return charString