diff --git a/Lib/fontTools/pens/svgPen.py b/Lib/fontTools/pens/svgPen.py new file mode 100644 index 000000000..839d14c86 --- /dev/null +++ b/Lib/fontTools/pens/svgPen.py @@ -0,0 +1,215 @@ +from __future__ import print_function, division, absolute_import +from fontTools.misc.py23 import * +from fontTools.pens.basePen import BasePen + +# Number to String Conversion + +def valueToString(v): + """ + >>> valueToString(0) + '0' + >>> valueToString(10) + '10' + >>> valueToString(-10) + '-10' + >>> valueToString(0.1) + '0.1' + >>> valueToString(0.0001) + '0.0001' + >>> valueToString(0.00001) + '0' + >>> valueToString(10.0001) + '10.0001' + >>> valueToString(10.00001) + '10' + """ + if int(v) == v: + v = "%d" % (int(v)) + else: + v = "%.4f" % v + # strip unnecessary zeros + # there is probably an easier way to do this + compiled = [] + for c in reversed(v): + if not compiled and c == "0": + continue + compiled.append(c) + v = "".join(reversed(compiled)) + if v.endswith("."): + v = v[:-1] + if not v: + v = "0" + return v + +def pointToString(pt): + return " ".join([valueToString(i) for i in pt]) + +class SVGPen(BasePen): + + def __init__(self, glyphSet): + BasePen.__init__(self, glyphSet) + self._commands = [] + self._lastCommand = None + self._lastX = None + self._lastY = None + + def _handleAnchor(self): + """ + >>> pen = SVGPen(None) + >>> pen.moveTo((0, 0)) + >>> pen.moveTo((10, 10)) + >>> pen._commands + ['M10 10'] + """ + if self._lastCommand == "M": + self._commands.pop(-1) + + def _moveTo(self, pt): + """ + >>> pen = SVGPen(None) + >>> pen.moveTo((0, 0)) + >>> pen._commands + ['M0 0'] + + >>> pen = SVGPen(None) + >>> pen.moveTo((10, 0)) + >>> pen._commands + ['M10 0'] + + >>> pen = SVGPen(None) + >>> pen.moveTo((0, 10)) + >>> pen._commands + ['M0 10'] + """ + self._handleAnchor() + t = "M%s" % (pointToString(pt)) + self._commands.append(t) + self._lastCommand = "M" + self._lastX, self._lastY = pt + + def _lineTo(self, pt): + """ + # duplicate point + >>> pen = SVGPen(None) + >>> pen.moveTo((10, 10)) + >>> pen.lineTo((10, 10)) + >>> pen._commands + ['M10 10'] + + # vertical line + >>> pen = SVGPen(None) + >>> pen.moveTo((10, 10)) + >>> pen.lineTo((10, 0)) + >>> pen._commands + ['M10 10', 'V0'] + + # horizontal line + >>> pen = SVGPen(None) + >>> pen.moveTo((10, 10)) + >>> pen.lineTo((0, 10)) + >>> pen._commands + ['M10 10', 'H0'] + + # basic + >>> pen = SVGPen(None) + >>> pen.lineTo((70, 80)) + >>> pen._commands + ['L70 80'] + + # basic following a moveto + >>> pen = SVGPen(None) + >>> pen.moveTo((0, 0)) + >>> pen.lineTo((10, 10)) + >>> pen._commands + ['M0 0', ' 10 10'] + """ + x, y = pt + # duplicate point + if x == self._lastX and y == self._lastY: + return + # vertical line + elif x == self._lastX: + cmd = "V" + pts = valueToString(y) + # horizontal line + elif y == self._lastY: + cmd = "H" + pts = valueToString(x) + # previous was a moveto + elif self._lastCommand == "M": + cmd = None + pts = " " + pointToString(pt) + # basic + else: + cmd = "L" + pts = pointToString(pt) + # write the string + t = "" + if cmd: + t += cmd + self._lastCommand = cmd + t += pts + self._commands.append(t) + # store for future reference + self._lastX, self._lastY = pt + + def _curveToOne(self, pt1, pt2, pt3): + """ + >>> pen = SVGPen(None) + >>> pen.curveTo((10, 20), (30, 40), (50, 60)) + >>> pen._commands + ['C10 20 30 40 50 60'] + """ + t = "C" + t += pointToString(pt1) + " " + t += pointToString(pt2) + " " + t += pointToString(pt3) + self._commands.append(t) + self._lastCommand = "C" + self._lastX, self._lastY = pt3 + + def _qCurveToOne(self, pt1, pt2): + """ + >>> pen = SVGPen(None) + >>> pen.qCurveTo((10, 20), (30, 40)) + >>> pen._commands + ['Q10 20 30 40'] + """ + assert pt2 is not None + t = "Q" + t += pointToString(pt1) + " " + t += pointToString(pt2) + self._commands.append(t) + self._lastCommand = "Q" + self._lastX, self._lastY = pt2 + + def _closePath(self): + """ + >>> pen = SVGPen(None) + >>> pen.closePath() + >>> pen._commands + ['Z'] + """ + self._commands.append("Z") + self._lastCommand = "Z" + self._lastX = self._lastY = None + + def _endPath(self): + """ + >>> pen = SVGPen(None) + >>> pen.endPath() + >>> pen._commands + ['Z'] + """ + self._closePath() + self._lastCommand = None + self._lastX = self._lastY = None + + def getCommands(self): + return "".join(self._commands) + + +if __name__ == "__main__": + import sys + import doctest + sys.exit(doctest.testmod().failed)