upgrade misc/tools/gen-metrics-and-svgs.py to new toolchain
This commit is contained in:
parent
11435926ba
commit
3099bc6495
2 changed files with 300 additions and 140 deletions
|
|
@ -4,67 +4,32 @@
|
||||||
# Sync glyph shapes between SVG and UFO, creating a bridge between UFO and Figma.
|
# Sync glyph shapes between SVG and UFO, creating a bridge between UFO and Figma.
|
||||||
#
|
#
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import os, sys, argparse, re, json, plistlib
|
|
||||||
from math import ceil, floor
|
import os, sys
|
||||||
from robofab.objects.objectsRF import OpenFont
|
from os.path import dirname, basename, abspath, relpath, join as pjoin
|
||||||
|
sys.path.append(abspath(pjoin(dirname(__file__), 'tools')))
|
||||||
|
from common import BASEDIR
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import plistlib
|
||||||
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from fontbuild.generateGlyph import generateGlyph
|
from math import ceil, floor
|
||||||
from ConfigParser import RawConfigParser
|
from defcon import Font
|
||||||
|
from svg import SVGPathPen
|
||||||
|
|
||||||
|
|
||||||
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
||||||
|
|
||||||
font = None # RFont
|
font = None # RFont
|
||||||
ufopath = ''
|
ufopath = ''
|
||||||
effectiveAscender = 0
|
effectiveAscender = 0
|
||||||
scale = 0.1
|
scale = 0.1
|
||||||
agl = None
|
|
||||||
|
|
||||||
|
|
||||||
def num(s):
|
def num(s):
|
||||||
return int(s) if s.find('.') == -1 else float(s)
|
return int(s) if s.find('.') == -1 else float(s)
|
||||||
|
|
||||||
|
|
||||||
def parseGlyphComposition(composite):
|
|
||||||
c = composite.split("=")
|
|
||||||
d = c[1].split("/")
|
|
||||||
glyphName = d[0]
|
|
||||||
if len(d) == 1:
|
|
||||||
offset = [0, 0]
|
|
||||||
else:
|
|
||||||
offset = [int(i) for i in d[1].split(",")]
|
|
||||||
accentString = c[0]
|
|
||||||
accents = accentString.split("+")
|
|
||||||
baseName = accents.pop(0)
|
|
||||||
accentNames = [i.split(":") for i in accents]
|
|
||||||
return (glyphName, baseName, accentNames, offset)
|
|
||||||
|
|
||||||
|
|
||||||
def loadGlyphCompositions(filename): # { glyphName => (baseName, accentNames, offset, rawline) }
|
|
||||||
compositions = OrderedDict()
|
|
||||||
with open(filename, 'r') as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.strip()
|
|
||||||
if len(line) > 0 and line[0] != '#':
|
|
||||||
glyphName, baseName, accentNames, offset = parseGlyphComposition(line)
|
|
||||||
compositions[glyphName] = (baseName, accentNames, offset, line)
|
|
||||||
return compositions
|
|
||||||
|
|
||||||
|
|
||||||
def loadAGL(filename): # -> { 2126: 'Omega', ... }
|
|
||||||
m = {}
|
|
||||||
with open(filename, 'r') as f:
|
|
||||||
for line in f:
|
|
||||||
# Omega;2126
|
|
||||||
# dalethatafpatah;05D3 05B2 # higher-level combinations; ignored
|
|
||||||
line = line.strip()
|
|
||||||
if len(line) > 0 and line[0] != '#':
|
|
||||||
name, uc = tuple([c.strip() for c in line.split(';')])
|
|
||||||
if uc.find(' ') == -1:
|
|
||||||
# it's a 1:1 mapping
|
|
||||||
m[int(uc, 16)] = name
|
|
||||||
return m
|
|
||||||
|
|
||||||
def decomposeGlyph(font, glyph):
|
def decomposeGlyph(font, glyph):
|
||||||
"""Moves the components of a glyph to its outline."""
|
"""Moves the components of a glyph to its outline."""
|
||||||
if len(glyph.components):
|
if len(glyph.components):
|
||||||
|
|
@ -92,55 +57,19 @@ def deepCopyContours(font, parent, component, offset, scale):
|
||||||
|
|
||||||
|
|
||||||
def glyphToSVGPath(g, yMul):
|
def glyphToSVGPath(g, yMul):
|
||||||
commands = {'move':'M','line':'L','curve':'Y','offcurve':'X','offCurve':'X'}
|
pen = SVGPathPen(g.getParent(), yMul)
|
||||||
svg = ''
|
g.draw(pen)
|
||||||
contours = []
|
return pen.getCommands()
|
||||||
|
|
||||||
if len(g.components):
|
|
||||||
decomposeGlyph(g.getParent(), g) # mutates g
|
|
||||||
|
|
||||||
if len(g):
|
|
||||||
for c in range(len(g)):
|
|
||||||
contours.append(g[c])
|
|
||||||
|
|
||||||
for i in range(len(contours)):
|
|
||||||
c = contours[i]
|
|
||||||
contour = end = ''
|
|
||||||
curve = False
|
|
||||||
points = c.points
|
|
||||||
if points[0].type == 'offCurve':
|
|
||||||
points.append(points.pop(0))
|
|
||||||
if points[0].type == 'offCurve':
|
|
||||||
points.append(points.pop(0))
|
|
||||||
for x in range(len(points)):
|
|
||||||
p = points[x]
|
|
||||||
command = commands[str(p.type)]
|
|
||||||
if command == 'X':
|
|
||||||
if curve == True:
|
|
||||||
command = ''
|
|
||||||
else:
|
|
||||||
command = 'C'
|
|
||||||
curve = True
|
|
||||||
if command == 'Y':
|
|
||||||
command = ''
|
|
||||||
curve = False
|
|
||||||
if x == 0:
|
|
||||||
command = 'M'
|
|
||||||
if p.type == 'curve':
|
|
||||||
end = ' %g %g' % (p.x * scale, (p.y * yMul) * scale)
|
|
||||||
contour += ' %s%g %g' % (command, p.x * scale, (p.y * yMul) * scale)
|
|
||||||
svg += ' ' + contour + end + 'z'
|
|
||||||
|
|
||||||
if font.has_key('__svgsync'):
|
|
||||||
font.removeGlyph('__svgsync')
|
|
||||||
return svg.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def svgWidth(g):
|
def svgWidth(g):
|
||||||
box = g.box
|
bounds = g.bounds # (xMin, yMin, xMax, yMax)
|
||||||
xoffs = box[0]
|
if bounds is None:
|
||||||
width = box[2] - box[0]
|
return 0, 0
|
||||||
return width, xoffs
|
xMin = bounds[0]
|
||||||
|
xMax = bounds[2]
|
||||||
|
width = xMax - xMin
|
||||||
|
return width, xMin
|
||||||
|
|
||||||
|
|
||||||
def glyphToSVG(g):
|
def glyphToSVG(g):
|
||||||
|
|
@ -148,7 +77,7 @@ def glyphToSVG(g):
|
||||||
|
|
||||||
svg = '''
|
svg = '''
|
||||||
<svg id="svg-%(name)s" xmlns="http://www.w3.org/2000/svg" width="%(width)d" height="%(height)d">
|
<svg id="svg-%(name)s" xmlns="http://www.w3.org/2000/svg" width="%(width)d" height="%(height)d">
|
||||||
<path d="%(glyphSVGPath)s" transform="translate(%(xoffs)g %(yoffs)g)"/>
|
<path d="%(glyphSVGPath)s" transform="translate(%(xoffs)g %(yoffs)g) scale(%(scale)g)"/>
|
||||||
</svg>
|
</svg>
|
||||||
''' % {
|
''' % {
|
||||||
'name': g.name,
|
'name': g.name,
|
||||||
|
|
@ -163,6 +92,7 @@ def glyphToSVG(g):
|
||||||
# 'descender': font.info.descender * scale,
|
# 'descender': font.info.descender * scale,
|
||||||
# 'baselineOffset': (font.info.unitsPerEm + font.info.descender) * scale,
|
# 'baselineOffset': (font.info.unitsPerEm + font.info.descender) * scale,
|
||||||
# 'unitsPerEm': font.info.unitsPerEm,
|
# 'unitsPerEm': font.info.unitsPerEm,
|
||||||
|
'scale': scale,
|
||||||
|
|
||||||
# 'margin': [g.leftMargin * scale, g.rightMargin * scale],
|
# 'margin': [g.leftMargin * scale, g.rightMargin * scale],
|
||||||
}
|
}
|
||||||
|
|
@ -242,13 +172,8 @@ def findGlifFile(glyphname):
|
||||||
|
|
||||||
usedSVGNames = set()
|
usedSVGNames = set()
|
||||||
|
|
||||||
def genGlyph(glyphName, generateFrom, force):
|
def genGlyph(glyphName):
|
||||||
# generateFrom = (baseName, accentNames, offset, rawline)
|
g = font[glyphName]
|
||||||
if generateFrom is not None:
|
|
||||||
generateGlyph(font, generateFrom[3], agl)
|
|
||||||
|
|
||||||
g = font.getGlyph(glyphName)
|
|
||||||
|
|
||||||
return glyphToSVG(g)
|
return glyphToSVG(g)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -328,10 +253,6 @@ argparser.add_argument('-scale', dest='scale', metavar='<scale>', type=str,
|
||||||
default='',
|
default='',
|
||||||
help='Scale glyph. Should be a number in the range (0-1]. Defaults to %g' % scale)
|
help='Scale glyph. Should be a number in the range (0-1]. Defaults to %g' % scale)
|
||||||
|
|
||||||
argparser.add_argument(
|
|
||||||
'-f', '-force', dest='force', action='store_const', const=True, default=False,
|
|
||||||
help='Generate glyphs even though they appear to be up-to date.')
|
|
||||||
|
|
||||||
argparser.add_argument('ufopath', metavar='<ufopath>', type=str,
|
argparser.add_argument('ufopath', metavar='<ufopath>', type=str,
|
||||||
help='Path to UFO packages')
|
help='Path to UFO packages')
|
||||||
|
|
||||||
|
|
@ -340,44 +261,25 @@ argparser.add_argument('glyphs', metavar='<glyphname>', type=str, nargs='*',
|
||||||
|
|
||||||
|
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
|
|
||||||
srcDir = os.path.join(BASEDIR, 'src')
|
srcDir = os.path.join(BASEDIR, 'src')
|
||||||
|
deleteNames = set(['.notdef', '.null'])
|
||||||
# load fontbuild config
|
|
||||||
config = RawConfigParser(dict_type=OrderedDict)
|
|
||||||
configFilename = os.path.join(srcDir, 'fontbuild.cfg')
|
|
||||||
config.read(configFilename)
|
|
||||||
deleteNames = set()
|
|
||||||
for sectionName, value in config.items('glyphs'):
|
|
||||||
if sectionName == 'delete':
|
|
||||||
deleteNames = set(value.split())
|
|
||||||
|
|
||||||
if len(args.scale):
|
if len(args.scale):
|
||||||
scale = float(args.scale)
|
scale = float(args.scale)
|
||||||
|
|
||||||
ufopath = args.ufopath.rstrip('/')
|
ufopath = args.ufopath.rstrip('/')
|
||||||
|
|
||||||
font = OpenFont(ufopath)
|
font = Font(ufopath)
|
||||||
effectiveAscender = max(font.info.ascender, font.info.unitsPerEm)
|
effectiveAscender = max(font.info.ascender, font.info.unitsPerEm)
|
||||||
|
|
||||||
# print('\n'.join(font.keys()))
|
# print('\n'.join(font.keys()))
|
||||||
# sys.exit(0)
|
# sys.exit(0)
|
||||||
|
|
||||||
agl = loadAGL(os.path.join(srcDir, 'glyphlist.txt')) # { 2126: 'Omega', ... }
|
|
||||||
|
|
||||||
deleteNames.add('.notdef')
|
deleteNames.add('.notdef')
|
||||||
deleteNames.add('.null')
|
deleteNames.add('.null')
|
||||||
|
|
||||||
glyphnames = args.glyphs if len(args.glyphs) else font.keys()
|
glyphnames = args.glyphs if len(args.glyphs) else font.keys()
|
||||||
glyphnameSet = set(glyphnames)
|
glyphnameSet = set(glyphnames)
|
||||||
generatedGlyphNames = set()
|
|
||||||
|
|
||||||
diacriticComps = loadGlyphCompositions(os.path.join(srcDir, 'diacritics.txt'))
|
|
||||||
for glyphName, comp in diacriticComps.iteritems():
|
|
||||||
if glyphName not in glyphnameSet:
|
|
||||||
generatedGlyphNames.add(glyphName)
|
|
||||||
glyphnames.append(glyphName)
|
|
||||||
glyphnameSet.add(glyphName)
|
|
||||||
|
|
||||||
glyphnames = [gn for gn in glyphnames if gn not in deleteNames]
|
glyphnames = [gn for gn in glyphnames if gn not in deleteNames]
|
||||||
glyphnames.sort()
|
glyphnames.sort()
|
||||||
|
|
@ -390,9 +292,7 @@ glyphMetrics = {}
|
||||||
svgLines = []
|
svgLines = []
|
||||||
for glyphname in glyphnames:
|
for glyphname in glyphnames:
|
||||||
generateFrom = None
|
generateFrom = None
|
||||||
if glyphname in generatedGlyphNames:
|
svg, metrics = genGlyph(glyphname)
|
||||||
generateFrom = diacriticComps[glyphname]
|
|
||||||
svg, metrics = genGlyph(glyphname, generateFrom, force=args.force)
|
|
||||||
# metrics: (width, advance, left, right)
|
# metrics: (width, advance, left, right)
|
||||||
glyphMetrics[nameToIdMap[glyphname]] = metrics
|
glyphMetrics[nameToIdMap[glyphname]] = metrics
|
||||||
svgLines.append(svg.replace('\n', ''))
|
svgLines.append(svg.replace('\n', ''))
|
||||||
|
|
@ -404,14 +304,14 @@ svgtext = '\n'.join(svgLines)
|
||||||
|
|
||||||
glyphsHtmlFilename = os.path.join(BASEDIR, 'docs', 'glyphs', 'index.html')
|
glyphsHtmlFilename = os.path.join(BASEDIR, 'docs', 'glyphs', 'index.html')
|
||||||
|
|
||||||
html = ''
|
html = u''
|
||||||
with open(glyphsHtmlFilename, 'r') as f:
|
with open(glyphsHtmlFilename, 'r') as f:
|
||||||
html = f.read()
|
html = f.read().decode('utf8')
|
||||||
|
|
||||||
startMarker = '<div id="svgs">'
|
startMarker = u'<div id="svgs">'
|
||||||
startPos = html.find(startMarker)
|
startPos = html.find(startMarker)
|
||||||
|
|
||||||
endMarker = '</div><!--END-SVGS'
|
endMarker = u'</div><!--END-SVGS'
|
||||||
endPos = html.find(endMarker, startPos + len(startMarker))
|
endPos = html.find(endMarker, startPos + len(startMarker))
|
||||||
|
|
||||||
relfilename = os.path.relpath(glyphsHtmlFilename, os.getcwd())
|
relfilename = os.path.relpath(glyphsHtmlFilename, os.getcwd())
|
||||||
|
|
@ -421,10 +321,6 @@ if startPos == -1 or endPos == -1:
|
||||||
print(msg % relfilename, file=sys.stderr)
|
print(msg % relfilename, file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for name in glyphnames:
|
|
||||||
if name == 'zero.tnum.slash':
|
|
||||||
print('FOUND zero.tnum.slash')
|
|
||||||
|
|
||||||
kerning = genKerningInfo(font, glyphnames, nameToIdMap)
|
kerning = genKerningInfo(font, glyphnames, nameToIdMap)
|
||||||
metaJson = '{\n'
|
metaJson = '{\n'
|
||||||
metaJson += '"nameids":' + fmtJsonDict(idToNameMap) + ',\n'
|
metaJson += '"nameids":' + fmtJsonDict(idToNameMap) + ',\n'
|
||||||
|
|
@ -433,11 +329,11 @@ metaJson += '"kerning":' + fmtJsonList(kerning) + '\n'
|
||||||
metaJson += '}'
|
metaJson += '}'
|
||||||
# metaHtml = '<script>var fontMetaData = ' + metaJson + ';</script>'
|
# metaHtml = '<script>var fontMetaData = ' + metaJson + ';</script>'
|
||||||
|
|
||||||
html = html[:startPos + len(startMarker)] + '\n' + svgtext + '\n' + html[endPos:]
|
html = html[:startPos + len(startMarker)] + '\n' + svgtext.decode('utf8') + '\n' + html[endPos:]
|
||||||
|
|
||||||
print('write', relfilename)
|
print('write', relfilename)
|
||||||
with open(glyphsHtmlFilename, 'w') as f:
|
with open(glyphsHtmlFilename, 'w') as f:
|
||||||
f.write(html)
|
f.write(html.encode('utf8'))
|
||||||
|
|
||||||
# JSON
|
# JSON
|
||||||
jsonFilename = os.path.join(BASEDIR, 'docs', 'glyphs', 'metrics.json')
|
jsonFilename = os.path.join(BASEDIR, 'docs', 'glyphs', 'metrics.json')
|
||||||
|
|
|
||||||
264
misc/tools/svg.py
Normal file
264
misc/tools/svg.py
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
# encoding: utf8
|
||||||
|
#
|
||||||
|
# The MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2010 Type Supply LLC
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# https://github.com/typesupply/ufo2svg
|
||||||
|
#
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from fontTools.pens.basePen import BasePen
|
||||||
|
|
||||||
|
|
||||||
|
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])
|
||||||
|
return valueToString(pt[0]) + " " + valueToString(pt[1])
|
||||||
|
|
||||||
|
|
||||||
|
class SVGPathPen(BasePen):
|
||||||
|
|
||||||
|
def __init__(self, glyphSet, yMul):
|
||||||
|
BasePen.__init__(self, glyphSet)
|
||||||
|
self._commands = []
|
||||||
|
self._lastCommand = None
|
||||||
|
self._lastX = None
|
||||||
|
self._lastY = None
|
||||||
|
self._yMul = yMul
|
||||||
|
|
||||||
|
# def pointToString(self, pt):
|
||||||
|
# pts = []
|
||||||
|
# n = 0
|
||||||
|
# for i in pt:
|
||||||
|
# if n == 1:
|
||||||
|
# i = i * self._yMul
|
||||||
|
# pts.append(valueToString(i))
|
||||||
|
# n = n + 1
|
||||||
|
# return " ".join(pts)
|
||||||
|
|
||||||
|
|
||||||
|
def pt(self, pt1):
|
||||||
|
return (pt1[0], pt1[1] * self._yMul)
|
||||||
|
|
||||||
|
|
||||||
|
def _handleAnchor(self):
|
||||||
|
"""
|
||||||
|
>>> pen = SVGPathPen(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 = SVGPathPen(None)
|
||||||
|
>>> pen.moveTo((0, 0))
|
||||||
|
>>> pen._commands
|
||||||
|
['M0 0']
|
||||||
|
|
||||||
|
>>> pen = SVGPathPen(None)
|
||||||
|
>>> pen.moveTo((10, 0))
|
||||||
|
>>> pen._commands
|
||||||
|
['M10 0']
|
||||||
|
|
||||||
|
>>> pen = SVGPathPen(None)
|
||||||
|
>>> pen.moveTo((0, 10))
|
||||||
|
>>> pen._commands
|
||||||
|
['M0 10']
|
||||||
|
"""
|
||||||
|
pt = self.pt(pt)
|
||||||
|
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 = SVGPathPen(None)
|
||||||
|
>>> pen.moveTo((10, 10))
|
||||||
|
>>> pen.lineTo((10, 10))
|
||||||
|
>>> pen._commands
|
||||||
|
['M10 10']
|
||||||
|
|
||||||
|
# vertical line
|
||||||
|
>>> pen = SVGPathPen(None)
|
||||||
|
>>> pen.moveTo((10, 10))
|
||||||
|
>>> pen.lineTo((10, 0))
|
||||||
|
>>> pen._commands
|
||||||
|
['M10 10', 'V0']
|
||||||
|
|
||||||
|
# horizontal line
|
||||||
|
>>> pen = SVGPathPen(None)
|
||||||
|
>>> pen.moveTo((10, 10))
|
||||||
|
>>> pen.lineTo((0, 10))
|
||||||
|
>>> pen._commands
|
||||||
|
['M10 10', 'H0']
|
||||||
|
|
||||||
|
# basic
|
||||||
|
>>> pen = SVGPathPen(None)
|
||||||
|
>>> pen.lineTo((70, 80))
|
||||||
|
>>> pen._commands
|
||||||
|
['L70 80']
|
||||||
|
|
||||||
|
# basic following a moveto
|
||||||
|
>>> pen = SVGPathPen(None)
|
||||||
|
>>> pen.moveTo((0, 0))
|
||||||
|
>>> pen.lineTo((10, 10))
|
||||||
|
>>> pen._commands
|
||||||
|
['M0 0', ' 10 10']
|
||||||
|
"""
|
||||||
|
pt = self.pt(pt)
|
||||||
|
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 = SVGPathPen(None)
|
||||||
|
>>> pen.curveTo((10, 20), (30, 40), (50, 60))
|
||||||
|
>>> pen._commands
|
||||||
|
['C10 20 30 40 50 60']
|
||||||
|
"""
|
||||||
|
pt1 = self.pt(pt1)
|
||||||
|
pt2 = self.pt(pt2)
|
||||||
|
pt3 = self.pt(pt3)
|
||||||
|
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 = SVGPathPen(None)
|
||||||
|
>>> pen.qCurveTo((10, 20), (30, 40))
|
||||||
|
>>> pen._commands
|
||||||
|
['Q10 20 30 40']
|
||||||
|
"""
|
||||||
|
assert pt2 is not None
|
||||||
|
pt1 = self.pt(pt1)
|
||||||
|
pt2 = self.pt(pt2)
|
||||||
|
t = "Q"
|
||||||
|
t += pointToString(pt1) + " "
|
||||||
|
t += pointToString(pt2)
|
||||||
|
self._commands.append(t)
|
||||||
|
self._lastCommand = "Q"
|
||||||
|
self._lastX, self._lastY = pt2
|
||||||
|
|
||||||
|
def _closePath(self):
|
||||||
|
"""
|
||||||
|
>>> pen = SVGPathPen(None)
|
||||||
|
>>> pen.closePath()
|
||||||
|
>>> pen._commands
|
||||||
|
['Z']
|
||||||
|
"""
|
||||||
|
self._commands.append("Z")
|
||||||
|
self._lastCommand = "Z"
|
||||||
|
self._lastX = self._lastY = None
|
||||||
|
|
||||||
|
def _endPath(self):
|
||||||
|
"""
|
||||||
|
>>> pen = SVGPathPen(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 doctest
|
||||||
|
doctest.testmod()
|
||||||
Reference in a new issue