Move svg path parser to own module
This commit is contained in:
263
src/drawing_controller/drawing_controller/svg_path_parser.py
Normal file
263
src/drawing_controller/drawing_controller/svg_path_parser.py
Normal file
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import splipy.curve_factory as cf
|
||||
import numpy as np
|
||||
import math
|
||||
|
||||
class SVGPathParser():
|
||||
|
||||
def __init__(self, logger, map_point):
|
||||
self.logger = logger
|
||||
self.map_point = map_point
|
||||
|
||||
def tokenize(self, pathstr):
|
||||
self.logger.info("Tokenizing path :'{}...' with {} characters".format(pathstr[:40], len(pathstr)))
|
||||
path = []
|
||||
i = 0
|
||||
while i < len(pathstr):
|
||||
c = pathstr[i]
|
||||
i += 1
|
||||
# Single letter commands
|
||||
if c.isalpha():
|
||||
path.append(c)
|
||||
# Numbers
|
||||
if c == '+' or c == '-' or c.isdecimal():
|
||||
s = c
|
||||
isdelim = lambda x: x.isspace() or (x.isalpha() and c != 'e') or x in [',', '+']
|
||||
while i < len(pathstr) and not isdelim(c):
|
||||
c = pathstr[i]
|
||||
if not isdelim(c):
|
||||
s = s + c
|
||||
if c.isalpha() and c != 'e':
|
||||
break
|
||||
i += 1
|
||||
path.append(s)
|
||||
|
||||
#print(path)
|
||||
#input()
|
||||
return path
|
||||
|
||||
def parse(self, path):
|
||||
'''
|
||||
path documentation: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
||||
|
||||
MoveTo: M, m
|
||||
LineTo: L, l, H, h, V, v
|
||||
Cubic Bézier Curve: C, c, S, s
|
||||
Quadratic Bézier Curve: Q, q, T, t
|
||||
Elliptical Arc Curve: A, a
|
||||
ClosePath: Z, z
|
||||
|
||||
Parameters:
|
||||
primitive (lxml child): the primitive from the svg file
|
||||
Returns:
|
||||
primitive_fn ():
|
||||
'''
|
||||
self.logger.info("Parsing path :'{}...' with {} tokens".format(path[:20], len(path)))
|
||||
x = 0.0
|
||||
y = 0.0
|
||||
i = 0
|
||||
output = []
|
||||
def getnum():
|
||||
nonlocal i
|
||||
i += 1
|
||||
return float(path[i])
|
||||
|
||||
def isfloat(element):
|
||||
if element is None:
|
||||
return False
|
||||
try:
|
||||
float(element)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def nextisnum():
|
||||
return i + 1 < len(path) and isfloat(path[i + 1])
|
||||
def setpointup():
|
||||
nonlocal output
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
p = self.map_point(x,y)
|
||||
output.append((p[0],p[1],1.0))
|
||||
def setpointdown():
|
||||
nonlocal output
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
p = self.map_point(x,y)
|
||||
output.append((p[0],p[1],0.0))
|
||||
def dropzeros(points):
|
||||
out = []
|
||||
for x,y in points:
|
||||
if x <= 0.0 or y <= 0.0:
|
||||
continue
|
||||
out.append([x,y])
|
||||
return out
|
||||
def appendpoints(points):
|
||||
nonlocal output
|
||||
for x,y in points:
|
||||
p = self.map_point(x,y)
|
||||
output.append((p[0],p[1],0.0))
|
||||
def lineto(xn,yn):
|
||||
nonlocal output
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
setpointdown()
|
||||
x = xn
|
||||
y = yn
|
||||
setpointdown()
|
||||
def bezier(control_points):
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
control_points = np.array(control_points)
|
||||
#maxval = np.amax(np.absolute(control_points))
|
||||
#print('inpput', control_points)
|
||||
maxval = np.amax(np.absolute(control_points))
|
||||
#print('maxxv', maxval)
|
||||
control_points = control_points / maxval #normalize values
|
||||
n = 10
|
||||
curve = cf.cubic_curve(control_points)
|
||||
lin = np.linspace(curve.start(0), curve.end(0), n)
|
||||
coordinates = curve(lin)
|
||||
coordinates = np.nan_to_num(coordinates)
|
||||
coordinates = coordinates * maxval #denormalize values
|
||||
coordinates = dropzeros(coordinates)
|
||||
#self.logger.info("Appending curve points: {}".format(coordinates))
|
||||
#print(coordinates)
|
||||
#input()
|
||||
|
||||
#print('start', curve.start(0))
|
||||
#print('end', curve.end(0))
|
||||
#print('curve', curve)
|
||||
#print('lin', lin)
|
||||
#print('curvelin', curve(lin))
|
||||
#input()
|
||||
if len(coordinates) > 0:
|
||||
x = coordinates[-1][0]
|
||||
y = coordinates[-1][1]
|
||||
return coordinates
|
||||
|
||||
while i < len(path):
|
||||
w = path[i]
|
||||
# MoveTo commands
|
||||
if (w == "M"):
|
||||
setpointup()
|
||||
x = getnum()
|
||||
y = getnum()
|
||||
setpointup()
|
||||
i += 1
|
||||
continue
|
||||
if (w == "m"):
|
||||
setpointup()
|
||||
x += getnum()
|
||||
y += getnum()
|
||||
setpointup()
|
||||
i += 1
|
||||
continue
|
||||
# LineTo commands
|
||||
if (w == "L"):
|
||||
while True:
|
||||
xn = getnum()
|
||||
yn = getnum()
|
||||
lineto(xn, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "l"):
|
||||
while True:
|
||||
xn = x + getnum()
|
||||
yn = y + getnum()
|
||||
lineto(xn, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "H"):
|
||||
while True:
|
||||
xn = getnum()
|
||||
lineto(xn, y)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "h"):
|
||||
while True:
|
||||
xn = x + getnum()
|
||||
lineto(xn, y)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "V"):
|
||||
while True:
|
||||
yn = getnum()
|
||||
lineto(x, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "v"):
|
||||
while True:
|
||||
yn = y + getnum()
|
||||
lineto(x, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
# Cubic Bézier Curve commands
|
||||
if (w == "C"):
|
||||
while True:
|
||||
# https://github.com/sintef/Splipy/tree/master/examples
|
||||
control_points = [(x,y),
|
||||
(getnum(),getnum()),
|
||||
(getnum(),getnum()),
|
||||
(getnum(),getnum())]
|
||||
coordinates = bezier(control_points)
|
||||
appendpoints(coordinates)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "c"):
|
||||
while True:
|
||||
# https://github.com/sintef/Splipy/tree/master/examples
|
||||
control_points = [(x,y),
|
||||
(x + getnum(), y + getnum()),
|
||||
(x + getnum(), y + getnum()),
|
||||
(x + getnum(), y + getnum())]
|
||||
coordinates = bezier(control_points)
|
||||
appendpoints(coordinates)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "S"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "s"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
# Quadratic Bézier Curve commands
|
||||
if (w == "Q"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "q"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "T"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "t"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
# Elliptical arc commands
|
||||
if (w == "A"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "a"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
# ClosePath commands
|
||||
if (w == "Z" or w == "z"):
|
||||
#TODO draw line if start and end point not are the same
|
||||
i += 1
|
||||
continue
|
||||
|
||||
self.logger.error("SVG path parser panic mode at '{}'".format(w))
|
||||
|
||||
i += 1
|
||||
self.logger.info("Finished parsing path :'{}...' with {} points".format(output[:3], len(output)))
|
||||
return output
|
||||
@@ -5,6 +5,7 @@ import splipy.curve_factory as cf
|
||||
import numpy as np
|
||||
import math
|
||||
import simplification.cutil
|
||||
from drawing_controller.svg_path_parser import SVGPathParser
|
||||
|
||||
class SVGProcessor():
|
||||
"""
|
||||
@@ -86,259 +87,10 @@ class SVGProcessor():
|
||||
output.append((output[0][0],output[0][1],0))
|
||||
return output
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
||||
def path_parser(self, child):
|
||||
'''
|
||||
|
||||
MoveTo: M, m
|
||||
LineTo: L, l, H, h, V, v
|
||||
Cubic Bézier Curve: C, c, S, s
|
||||
Quadratic Bézier Curve: Q, q, T, t
|
||||
Elliptical Arc Curve: A, a
|
||||
ClosePath: Z, z
|
||||
|
||||
Parameters:
|
||||
primitive (lxml child): the primitive from the svg file
|
||||
Returns:
|
||||
primitive_fn ():
|
||||
'''
|
||||
pathstr = child.get('d')
|
||||
|
||||
# Tokenizer
|
||||
self.logger.info("Tokenizing path :'{}...' with {} characters".format(pathstr[:40], len(pathstr)))
|
||||
path = []
|
||||
i = 0
|
||||
while i < len(pathstr):
|
||||
c = pathstr[i]
|
||||
i += 1
|
||||
# Single letter commands
|
||||
if c.isalpha():
|
||||
path.append(c)
|
||||
|
||||
# Numbers
|
||||
if c == '+' or c == '-' or c.isdecimal():
|
||||
s = c
|
||||
isdelim = lambda x: x.isspace() or (x.isalpha() and c != 'e') or x in [',', '+']
|
||||
while i < len(pathstr) and not isdelim(c):
|
||||
c = pathstr[i]
|
||||
if not isdelim(c):
|
||||
s = s + c
|
||||
if c.isalpha() and c != 'e':
|
||||
break
|
||||
i += 1
|
||||
path.append(s)
|
||||
|
||||
#print(path)
|
||||
#input()
|
||||
|
||||
# Parser
|
||||
self.logger.info("Parsing path :'{}...' with {} tokens".format(path[:20], len(path)))
|
||||
x = 0.0
|
||||
y = 0.0
|
||||
i = 0
|
||||
output = []
|
||||
def getnum():
|
||||
nonlocal i
|
||||
i += 1
|
||||
return float(path[i])
|
||||
|
||||
def isfloat(element):
|
||||
if element is None:
|
||||
return False
|
||||
try:
|
||||
float(element)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def nextisnum():
|
||||
return i + 1 < len(path) and isfloat(path[i + 1])
|
||||
def setpointup():
|
||||
nonlocal output
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
p = self.map_point(x,y)
|
||||
output.append((p[0],p[1],1.0))
|
||||
def setpointdown():
|
||||
nonlocal output
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
p = self.map_point(x,y)
|
||||
output.append((p[0],p[1],0.0))
|
||||
def dropzeros(points):
|
||||
out = []
|
||||
for x,y in points:
|
||||
if x <= 0.0 or y <= 0.0:
|
||||
continue
|
||||
out.append([x,y])
|
||||
return out
|
||||
def appendpoints(points):
|
||||
nonlocal output
|
||||
for x,y in points:
|
||||
p = self.map_point(x,y)
|
||||
output.append((p[0],p[1],0.0))
|
||||
def lineto(xn,yn):
|
||||
nonlocal output
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
setpointdown()
|
||||
x = xn
|
||||
y = yn
|
||||
setpointdown()
|
||||
def bezier(control_points):
|
||||
nonlocal x
|
||||
nonlocal y
|
||||
control_points = np.array(control_points)
|
||||
#maxval = np.amax(np.absolute(control_points))
|
||||
#print('inpput', control_points)
|
||||
maxval = np.amax(np.absolute(control_points))
|
||||
#print('maxxv', maxval)
|
||||
control_points = control_points / maxval #normalize values
|
||||
n = 10
|
||||
curve = cf.cubic_curve(control_points)
|
||||
lin = np.linspace(curve.start(0), curve.end(0), n)
|
||||
coordinates = curve(lin)
|
||||
coordinates = np.nan_to_num(coordinates)
|
||||
coordinates = coordinates * maxval #denormalize values
|
||||
coordinates = dropzeros(coordinates)
|
||||
#self.logger.info("Appending curve points: {}".format(coordinates))
|
||||
#print(coordinates)
|
||||
#input()
|
||||
|
||||
#print('start', curve.start(0))
|
||||
#print('end', curve.end(0))
|
||||
#print('curve', curve)
|
||||
#print('lin', lin)
|
||||
#print('curvelin', curve(lin))
|
||||
#input()
|
||||
if len(coordinates) > 0:
|
||||
x = coordinates[-1][0]
|
||||
y = coordinates[-1][1]
|
||||
return coordinates
|
||||
|
||||
while i < len(path):
|
||||
w = path[i]
|
||||
# MoveTo commands
|
||||
if (w == "M"):
|
||||
setpointup()
|
||||
x = getnum()
|
||||
y = getnum()
|
||||
setpointup()
|
||||
i += 1
|
||||
continue
|
||||
if (w == "m"):
|
||||
setpointup()
|
||||
x += getnum()
|
||||
y += getnum()
|
||||
setpointup()
|
||||
i += 1
|
||||
continue
|
||||
# LineTo commands
|
||||
if (w == "L"):
|
||||
while True:
|
||||
xn = getnum()
|
||||
yn = getnum()
|
||||
lineto(xn, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "l"):
|
||||
while True:
|
||||
xn = x + getnum()
|
||||
yn = y + getnum()
|
||||
lineto(xn, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "H"):
|
||||
while True:
|
||||
xn = getnum()
|
||||
lineto(xn, y)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "h"):
|
||||
while True:
|
||||
xn = x + getnum()
|
||||
lineto(xn, y)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "V"):
|
||||
while True:
|
||||
yn = getnum()
|
||||
lineto(x, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "v"):
|
||||
while True:
|
||||
yn = y + getnum()
|
||||
lineto(x, yn)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
# Cubic Bézier Curve commands
|
||||
if (w == "C"):
|
||||
while True:
|
||||
# https://github.com/sintef/Splipy/tree/master/examples
|
||||
control_points = [(x,y),
|
||||
(getnum(),getnum()),
|
||||
(getnum(),getnum()),
|
||||
(getnum(),getnum())]
|
||||
coordinates = bezier(control_points)
|
||||
appendpoints(coordinates)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "c"):
|
||||
while True:
|
||||
# https://github.com/sintef/Splipy/tree/master/examples
|
||||
control_points = [(x,y),
|
||||
(x + getnum(), y + getnum()),
|
||||
(x + getnum(), y + getnum()),
|
||||
(x + getnum(), y + getnum())]
|
||||
coordinates = bezier(control_points)
|
||||
appendpoints(coordinates)
|
||||
if not nextisnum():
|
||||
break
|
||||
i += 1
|
||||
continue
|
||||
if (w == "S"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "s"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
# Quadratic Bézier Curve commands
|
||||
if (w == "Q"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "q"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "T"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "t"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
# Elliptical arc commands
|
||||
if (w == "A"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
if (w == "a"):
|
||||
self.logger.error("SVG path parser '{}' not implemented".format(w))
|
||||
# ClosePath commands
|
||||
if (w == "Z" or w == "z"):
|
||||
#TODO draw line if start and end point not are the same
|
||||
i += 1
|
||||
continue
|
||||
|
||||
self.logger.error("SVG path parser panic mode at '{}'".format(w))
|
||||
|
||||
i += 1
|
||||
self.logger.info("Finished parsing path :'{}...' with {} points".format(output[:3], len(output)))
|
||||
path_parser = SVGPathParser(self.logger, self.map_point)
|
||||
output = path_parser.parse(path_parser.tokenize(pathstr))
|
||||
return output
|
||||
|
||||
# https://stackoverflow.com/questions/30232031/how-can-i-strip-namespaces-out-of-an-lxml-tree
|
||||
|
||||
Reference in New Issue
Block a user