svgPathPen: add option to customize number formatting (e.g. rounding)

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.
This commit is contained in:
Cosimo Lupo 2021-12-14 12:42:37 +00:00
parent d190a7c206
commit fce1fa2da8

View File

@ -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):
"""
@ -43,7 +61,7 @@ class SVGPathPen(BasePen):
['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
@ -99,11 +117,11 @@ class SVGPathPen(BasePen):
# 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:
@ -122,9 +140,9 @@ class SVGPathPen(BasePen):
['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
@ -135,11 +153,16 @@ class SVGPathPen(BasePen):
>>> 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