Skip to content

Commit

Permalink
svgPathPen: add option to customize number formatting (e.g. rounding)
Browse files Browse the repository at this point in the history
Using a RoundingPen as a filter doesn't work for TrueType quadratic splines (qCurveTo), because the rounding would occur too early, before these get split into atomic quadratic Bezier segments (by the superclass BasePen.qCurveTo method), thus leaving unwanted floating-point coordinates in the SVG output.
So here we add a 'ntos' parameter (by default simpli calls str()) that takes a Callable[[float], str] and can be used to customize the formatting of the numbers in SVG path coordinates.
  • Loading branch information
anthrotype committed Dec 14, 2021
1 parent d190a7c commit fce1fa2
Showing 1 changed file with 34 additions and 11 deletions.
45 changes: 34 additions & 11 deletions Lib/fontTools/pens/svgPathPen.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
from typing import Callable
from fontTools.pens.basePen import BasePen


def pointToString(pt):
return " ".join([str(i) for i in pt])
def pointToString(pt, ntos=str):
return " ".join(ntos(i) for i in pt)


class SVGPathPen(BasePen):
""" Pen to draw SVG path d commands.
def __init__(self, glyphSet):
Example::
>>> pen = SVGPathPen(None)
>>> pen.moveTo((0, 0))
>>> pen.lineTo((1, 1))
>>> pen.curveTo((2, 2), (3, 3), (4, 4))
>>> pen.closePath()
>>> pen.getCommands()
'M0 0 1 1C2 2 3 3 4 4Z'
Args:
glyphSet: a dictionary of drawable glyph objects keyed by name
used to resolve component references in composite glyphs.
ntos: a callable that takes a number and returns a string, to
customize how numbers are formatted (default: str).
"""
def __init__(self, glyphSet, ntos: Callable[[float], str] = str):
BasePen.__init__(self, glyphSet)
self._commands = []
self._lastCommand = None
self._lastX = None
self._lastY = None
self._ntos = ntos

def _handleAnchor(self):
"""
Expand Down Expand Up @@ -43,7 +61,7 @@ def _moveTo(self, pt):
['M0 10']
"""
self._handleAnchor()
t = "M%s" % (pointToString(pt))
t = "M%s" % (pointToString(pt, self._ntos))
self._commands.append(t)
self._lastCommand = "M"
self._lastX, self._lastY = pt
Expand Down Expand Up @@ -99,11 +117,11 @@ def _lineTo(self, pt):
# previous was a moveto
elif self._lastCommand == "M":
cmd = None
pts = " " + pointToString(pt)
pts = " " + pointToString(pt, self._ntos)
# basic
else:
cmd = "L"
pts = pointToString(pt)
pts = pointToString(pt, self._ntos)
# write the string
t = ""
if cmd:
Expand All @@ -122,9 +140,9 @@ def _curveToOne(self, pt1, pt2, pt3):
['C10 20 30 40 50 60']
"""
t = "C"
t += pointToString(pt1) + " "
t += pointToString(pt2) + " "
t += pointToString(pt3)
t += pointToString(pt1, self._ntos) + " "
t += pointToString(pt2, self._ntos) + " "
t += pointToString(pt3, self._ntos)
self._commands.append(t)
self._lastCommand = "C"
self._lastX, self._lastY = pt3
Expand All @@ -135,11 +153,16 @@ def _qCurveToOne(self, pt1, pt2):
>>> pen.qCurveTo((10, 20), (30, 40))
>>> pen._commands
['Q10 20 30 40']
>>> from fontTools.misc.roundTools import otRound
>>> pen = SVGPathPen(None, ntos=lambda v: str(otRound(v)))
>>> pen.qCurveTo((3, 3), (7, 5), (11, 4))
>>> pen._commands
['Q3 3 5 4', 'Q7 5 11 4']
"""
assert pt2 is not None
t = "Q"
t += pointToString(pt1) + " "
t += pointToString(pt2)
t += pointToString(pt1, self._ntos) + " "
t += pointToString(pt2, self._ntos)
self._commands.append(t)
self._lastCommand = "Q"
self._lastX, self._lastY = pt2
Expand Down

0 comments on commit fce1fa2

Please sign in to comment.