ParseSVG.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://flatcam.org #
  4. # Author: Juan Pablo Caram (c) #
  5. # Date: 12/18/2015 #
  6. # MIT Licence #
  7. # #
  8. # SVG Features supported: #
  9. # * Groups #
  10. # * Rectangles (w/ rounded corners) #
  11. # * Circles #
  12. # * Ellipses #
  13. # * Polygons #
  14. # * Polylines #
  15. # * Lines #
  16. # * Paths #
  17. # * All transformations #
  18. # #
  19. # Reference: www.w3.org/TR/SVG/Overview.html #
  20. # ##########################################################
  21. # import xml.etree.ElementTree as ET
  22. from svg.path import Line, Arc, CubicBezier, QuadraticBezier, parse_path
  23. # from svg.path.path import Move
  24. # from svg.path.path import Close
  25. import svg.path
  26. from shapely.geometry import LineString, MultiLineString
  27. from shapely.affinity import skew, affine_transform, rotate
  28. import numpy as np
  29. from appParsers.ParseFont import *
  30. log = logging.getLogger('base2')
  31. def svgparselength(lengthstr):
  32. """
  33. Parse an SVG length string into a float and a units
  34. string, if any.
  35. :param lengthstr: SVG length string.
  36. :return: Number and units pair.
  37. :rtype: tuple(float, str|None)
  38. """
  39. integer_re_str = r'[+-]?[0-9]+'
  40. number_re_str = r'(?:[+-]?[0-9]*\.[0-9]+(?:[Ee]' + integer_re_str + ')?' + r')|' + \
  41. r'(?:' + integer_re_str + r'(?:[Ee]' + integer_re_str + r')?)'
  42. length_re_str = r'(' + number_re_str + r')(em|ex|px|in|cm|mm|pt|pc|%)?'
  43. if lengthstr:
  44. match = re.search(length_re_str, lengthstr)
  45. if match:
  46. return float(match.group(1)), match.group(2)
  47. else:
  48. return 0, 0
  49. return
  50. def path2shapely(path, object_type, res=1.0):
  51. """
  52. Converts an svg.path.Path into a Shapely
  53. Polygon or LinearString.
  54. :param path: svg.path.Path instance
  55. :param object_type:
  56. :param res: Resolution (minimum step along path)
  57. :return: Shapely geometry object
  58. :rtype : Polygon
  59. :rtype : LineString
  60. """
  61. points = []
  62. geometry = []
  63. rings = []
  64. closed = False
  65. for component in path:
  66. # Line
  67. if isinstance(component, Line):
  68. start = component.start
  69. x, y = start.real, start.imag
  70. if len(points) == 0 or points[-1] != (x, y):
  71. points.append((x, y))
  72. end = component.end
  73. points.append((end.real, end.imag))
  74. continue
  75. # Arc, CubicBezier or QuadraticBezier
  76. if isinstance(component, Arc) or \
  77. isinstance(component, CubicBezier) or \
  78. isinstance(component, QuadraticBezier):
  79. # How many points to use in the discrete representation.
  80. length = component.length(res / 10.0)
  81. # steps = int(length / res + 0.5)
  82. steps = int(length) * 2
  83. # solve error when step is below 1,
  84. # it may cause other problems, but LineString needs at least two points
  85. if steps == 0:
  86. steps = 1
  87. frac = 1.0 / steps
  88. # print length, steps, frac
  89. for i in range(steps):
  90. point = component.point(i * frac)
  91. x, y = point.real, point.imag
  92. if len(points) == 0 or points[-1] != (x, y):
  93. points.append((x, y))
  94. end = component.point(1.0)
  95. points.append((end.real, end.imag))
  96. continue
  97. # Move
  98. if isinstance(component, svg.path.Move):
  99. if not points:
  100. continue
  101. else:
  102. rings.append(points)
  103. if closed is False:
  104. points = []
  105. else:
  106. closed = False
  107. start = component.start
  108. x, y = start.real, start.imag
  109. points = [(x, y)]
  110. continue
  111. closed = False
  112. # Close
  113. if isinstance(component, svg.path.Close):
  114. if not points:
  115. continue
  116. else:
  117. rings.append(points)
  118. points = []
  119. closed = True
  120. continue
  121. log.warning("I don't know what this is: %s" % str(component))
  122. continue
  123. # if there are still points in points then add them to the last ring
  124. if points:
  125. rings.append(points)
  126. try:
  127. rings = MultiLineString(rings)
  128. except Exception as e:
  129. log.debug("ParseSVG.path2shapely() MString --> %s" % str(e))
  130. return None
  131. if len(rings) > 0:
  132. if len(rings) == 1 and not isinstance(rings, MultiLineString):
  133. # Polygons are closed and require more than 2 points
  134. if Point(rings[0][0]).almost_equals(Point(rings[0][-1])) and len(rings[0]) > 2:
  135. geo_element = Polygon(rings[0])
  136. else:
  137. geo_element = LineString(rings[0])
  138. else:
  139. try:
  140. geo_element = Polygon(rings[0], rings[1:])
  141. except Exception:
  142. coords = []
  143. for line in rings:
  144. coords.append(line.coords[0])
  145. coords.append(line.coords[1])
  146. try:
  147. geo_element = Polygon(coords)
  148. except Exception:
  149. geo_element = LineString(coords)
  150. geometry.append(geo_element)
  151. return geometry
  152. def svgrect2shapely(rect, n_points=32):
  153. """
  154. Converts an SVG rect into Shapely geometry.
  155. :param rect: Rect Element
  156. :type rect: xml.etree.ElementTree.Element
  157. :param n_points: number of points to approximate circles
  158. :type n_points: int
  159. :return: shapely.geometry.polygon.LinearRing
  160. """
  161. w = svgparselength(rect.get('width'))[0]
  162. h = svgparselength(rect.get('height'))[0]
  163. x_obj = rect.get('x')
  164. if x_obj is not None:
  165. x = svgparselength(x_obj)[0]
  166. else:
  167. x = 0
  168. y_obj = rect.get('y')
  169. if y_obj is not None:
  170. y = svgparselength(y_obj)[0]
  171. else:
  172. y = 0
  173. rxstr = rect.get('rx')
  174. rystr = rect.get('ry')
  175. if rxstr is None and rystr is None: # Sharp corners
  176. pts = [
  177. (x, y), (x + w, y), (x + w, y + h), (x, y + h), (x, y)
  178. ]
  179. else: # Rounded corners
  180. rx = 0.0 if rxstr is None else svgparselength(rxstr)[0]
  181. ry = 0.0 if rystr is None else svgparselength(rystr)[0]
  182. n_points = int(n_points / 4 + 0.5)
  183. t = np.arange(n_points, dtype=float) / n_points / 4
  184. x_ = (x + w - rx) + rx * np.cos(2 * np.pi * (t + 0.75))
  185. y_ = (y + ry) + ry * np.sin(2 * np.pi * (t + 0.75))
  186. lower_right = [(x_[i], y_[i]) for i in range(n_points)]
  187. x_ = (x + w - rx) + rx * np.cos(2 * np.pi * t)
  188. y_ = (y + h - ry) + ry * np.sin(2 * np.pi * t)
  189. upper_right = [(x_[i], y_[i]) for i in range(n_points)]
  190. x_ = (x + rx) + rx * np.cos(2 * np.pi * (t + 0.25))
  191. y_ = (y + h - ry) + ry * np.sin(2 * np.pi * (t + 0.25))
  192. upper_left = [(x_[i], y_[i]) for i in range(n_points)]
  193. x_ = (x + rx) + rx * np.cos(2 * np.pi * (t + 0.5))
  194. y_ = (y + ry) + ry * np.sin(2 * np.pi * (t + 0.5))
  195. lower_left = [(x_[i], y_[i]) for i in range(n_points)]
  196. pts = [(x + rx, y), (x - rx + w, y)] + \
  197. lower_right + \
  198. [(x + w, y + ry), (x + w, y + h - ry)] + \
  199. upper_right + \
  200. [(x + w - rx, y + h), (x + rx, y + h)] + \
  201. upper_left + \
  202. [(x, y + h - ry), (x, y + ry)] + \
  203. lower_left
  204. return Polygon(pts).buffer(0)
  205. # return LinearRing(pts)
  206. def svgcircle2shapely(circle):
  207. """
  208. Converts an SVG circle into Shapely geometry.
  209. :param circle: Circle Element
  210. :type circle: xml.etree.ElementTree.Element
  211. :return: Shapely representation of the circle.
  212. :rtype: shapely.geometry.polygon.LinearRing
  213. """
  214. # cx = float(circle.get('cx'))
  215. # cy = float(circle.get('cy'))
  216. # r = float(circle.get('r'))
  217. cx = svgparselength(circle.get('cx'))[0] # TODO: No units support yet
  218. cy = svgparselength(circle.get('cy'))[0] # TODO: No units support yet
  219. r = svgparselength(circle.get('r'))[0] # TODO: No units support yet
  220. # TODO: No resolution specified.
  221. return Point(cx, cy).buffer(r)
  222. def svgellipse2shapely(ellipse, n_points=64):
  223. """
  224. Converts an SVG ellipse into Shapely geometry
  225. :param ellipse: Ellipse Element
  226. :type ellipse: xml.etree.ElementTree.Element
  227. :param n_points: Number of discrete points in output.
  228. :return: Shapely representation of the ellipse.
  229. :rtype: shapely.geometry.polygon.LinearRing
  230. """
  231. cx = svgparselength(ellipse.get('cx'))[0] # TODO: No units support yet
  232. cy = svgparselength(ellipse.get('cy'))[0] # TODO: No units support yet
  233. rx = svgparselength(ellipse.get('rx'))[0] # TODO: No units support yet
  234. ry = svgparselength(ellipse.get('ry'))[0] # TODO: No units support yet
  235. t = np.arange(n_points, dtype=float) / n_points
  236. x = cx + rx * np.cos(2 * np.pi * t)
  237. y = cy + ry * np.sin(2 * np.pi * t)
  238. pts = [(x[i], y[i]) for i in range(n_points)]
  239. return Polygon(pts).buffer(0)
  240. # return LinearRing(pts)
  241. def svgline2shapely(line):
  242. """
  243. :param line: Line element
  244. :type line: xml.etree.ElementTree.Element
  245. :return: Shapely representation on the line.
  246. :rtype: shapely.geometry.polygon.LinearRing
  247. """
  248. x1 = svgparselength(line.get('x1'))[0]
  249. y1 = svgparselength(line.get('y1'))[0]
  250. x2 = svgparselength(line.get('x2'))[0]
  251. y2 = svgparselength(line.get('y2'))[0]
  252. return LineString([(x1, y1), (x2, y2)])
  253. def svgpolyline2shapely(polyline):
  254. ptliststr = polyline.get('points')
  255. points = parse_svg_point_list(ptliststr)
  256. return LineString(points)
  257. def svgpolygon2shapely(polygon):
  258. ptliststr = polygon.get('points')
  259. points = parse_svg_point_list(ptliststr)
  260. return Polygon(points).buffer(0)
  261. # return LinearRing(points)
  262. def getsvggeo(node, object_type, root=None):
  263. """
  264. Extracts and flattens all geometry from an SVG node
  265. into a list of Shapely geometry.
  266. :param node: xml.etree.ElementTree.Element
  267. :param object_type:
  268. :return: List of Shapely geometry
  269. :rtype: list
  270. """
  271. if root is None:
  272. root = node
  273. kind = re.search('(?:\{.*\})?(.*)$', node.tag).group(1)
  274. geo = []
  275. # Recurse
  276. if len(node) > 0:
  277. for child in node:
  278. subgeo = getsvggeo(child, object_type, root)
  279. if subgeo is not None:
  280. geo += subgeo
  281. # Parse
  282. elif kind == 'path':
  283. log.debug("***PATH***")
  284. P = parse_path(node.get('d'))
  285. P = path2shapely(P, object_type)
  286. # for path, the resulting geometry is already a list so no need to create a new one
  287. geo = P
  288. elif kind == 'rect':
  289. log.debug("***RECT***")
  290. R = svgrect2shapely(node)
  291. geo = [R]
  292. elif kind == 'circle':
  293. log.debug("***CIRCLE***")
  294. C = svgcircle2shapely(node)
  295. geo = [C]
  296. elif kind == 'ellipse':
  297. log.debug("***ELLIPSE***")
  298. E = svgellipse2shapely(node)
  299. geo = [E]
  300. elif kind == 'polygon':
  301. log.debug("***POLYGON***")
  302. poly = svgpolygon2shapely(node)
  303. geo = [poly]
  304. elif kind == 'line':
  305. log.debug("***LINE***")
  306. line = svgline2shapely(node)
  307. geo = [line]
  308. elif kind == 'polyline':
  309. log.debug("***POLYLINE***")
  310. pline = svgpolyline2shapely(node)
  311. geo = [pline]
  312. elif kind == 'use':
  313. log.debug('***USE***')
  314. # href= is the preferred name for this[1], but inkscape still generates xlink:href=.
  315. # [1] https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use#Attributes
  316. href = node.attrib['href'] if 'href' in node.attrib else node.attrib['{http://www.w3.org/1999/xlink}href']
  317. ref = root.find(".//*[@id='%s']" % href.replace('#', ''))
  318. if ref is not None:
  319. geo = getsvggeo(ref, object_type, root)
  320. else:
  321. log.warning("Unknown kind: " + kind)
  322. geo = None
  323. # ignore transformation for unknown kind
  324. if geo is not None:
  325. # Transformations
  326. if 'transform' in node.attrib:
  327. trstr = node.get('transform')
  328. trlist = parse_svg_transform(trstr)
  329. # log.debug(trlist)
  330. # Transformations are applied in reverse order
  331. for tr in trlist[::-1]:
  332. if tr[0] == 'translate':
  333. geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
  334. elif tr[0] == 'scale':
  335. geo = [scale(geoi, tr[1], tr[2], origin=(0, 0))
  336. for geoi in geo]
  337. elif tr[0] == 'rotate':
  338. geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
  339. for geoi in geo]
  340. elif tr[0] == 'skew':
  341. geo = [skew(geoi, tr[1], tr[2], origin=(0, 0))
  342. for geoi in geo]
  343. elif tr[0] == 'matrix':
  344. geo = [affine_transform(geoi, tr[1:]) for geoi in geo]
  345. else:
  346. raise Exception('Unknown transformation: %s', tr)
  347. return geo
  348. def getsvgtext(node, object_type, units='MM'):
  349. """
  350. Extracts and flattens all geometry from an SVG node
  351. into a list of Shapely geometry.
  352. :param node: xml.etree.ElementTree.Element
  353. :param object_type:
  354. :return: List of Shapely geometry
  355. :rtype: list
  356. """
  357. kind = re.search('(?:\{.*\})?(.*)$', node.tag).group(1)
  358. geo = []
  359. # Recurse
  360. if len(node) > 0:
  361. for child in node:
  362. subgeo = getsvgtext(child, object_type, units=units)
  363. if subgeo is not None:
  364. geo += subgeo
  365. # Parse
  366. elif kind == 'tspan':
  367. current_attrib = node.attrib
  368. txt = node.text
  369. style_dict = {}
  370. parrent_attrib = node.getparent().attrib
  371. style = parrent_attrib['style']
  372. try:
  373. style_list = style.split(';')
  374. for css in style_list:
  375. style_dict[css.rpartition(':')[0]] = css.rpartition(':')[-1]
  376. pos_x = float(current_attrib['x'])
  377. pos_y = float(current_attrib['y'])
  378. # should have used the instance from FlatCAMApp.App but how? without reworking everything ...
  379. pf = ParseFont()
  380. pf.get_fonts_by_types()
  381. font_name = style_dict['font-family'].replace("'", '')
  382. if style_dict['font-style'] == 'italic' and style_dict['font-weight'] == 'bold':
  383. font_type = 'bi'
  384. elif style_dict['font-weight'] == 'bold':
  385. font_type = 'bold'
  386. elif style_dict['font-style'] == 'italic':
  387. font_type = 'italic'
  388. else:
  389. font_type = 'regular'
  390. # value of 2.2 should have been 2.83 (conversion value from pixels to points)
  391. # but the dimensions from Inkscape did not corelate with the ones after importing in FlatCAM
  392. # so I adjusted this
  393. font_size = svgparselength(style_dict['font-size'])[0] * 2.2
  394. geo = [pf.font_to_geometry(txt,
  395. font_name=font_name,
  396. font_size=font_size,
  397. font_type=font_type,
  398. units=units,
  399. coordx=pos_x,
  400. coordy=pos_y)
  401. ]
  402. geo = [(scale(g, 1.0, -1.0)) for g in geo]
  403. except Exception as e:
  404. log.debug(str(e))
  405. else:
  406. geo = None
  407. # ignore transformation for unknown kind
  408. if geo is not None:
  409. # Transformations
  410. if 'transform' in node.attrib:
  411. trstr = node.get('transform')
  412. trlist = parse_svg_transform(trstr)
  413. # log.debug(trlist)
  414. # Transformations are applied in reverse order
  415. for tr in trlist[::-1]:
  416. if tr[0] == 'translate':
  417. geo = [translate(geoi, tr[1], tr[2]) for geoi in geo]
  418. elif tr[0] == 'scale':
  419. geo = [scale(geoi, tr[1], tr[2], origin=(0, 0))
  420. for geoi in geo]
  421. elif tr[0] == 'rotate':
  422. geo = [rotate(geoi, tr[1], origin=(tr[2], tr[3]))
  423. for geoi in geo]
  424. elif tr[0] == 'skew':
  425. geo = [skew(geoi, tr[1], tr[2], origin=(0, 0))
  426. for geoi in geo]
  427. elif tr[0] == 'matrix':
  428. geo = [affine_transform(geoi, tr[1:]) for geoi in geo]
  429. else:
  430. raise Exception('Unknown transformation: %s', tr)
  431. return geo
  432. def parse_svg_point_list(ptliststr):
  433. """
  434. Returns a list of coordinate pairs extracted from the "points"
  435. attribute in SVG polygons and polyline's.
  436. :param ptliststr: "points" attribute string in polygon or polyline.
  437. :return: List of tuples with coordinates.
  438. """
  439. pairs = []
  440. last = None
  441. pos = 0
  442. i = 0
  443. for match in re.finditer(r'(\s*,\s*)|(\s+)', ptliststr.strip(' ')):
  444. val = float(ptliststr[pos:match.start()])
  445. if i % 2 == 1:
  446. pairs.append((last, val))
  447. else:
  448. last = val
  449. pos = match.end()
  450. i += 1
  451. # Check for last element
  452. val = float(ptliststr[pos:])
  453. if i % 2 == 1:
  454. pairs.append((last, val))
  455. else:
  456. log.warning("Incomplete coordinates.")
  457. return pairs
  458. def parse_svg_transform(trstr):
  459. """
  460. Parses an SVG transform string into a list
  461. of transform names and their parameters.
  462. Possible transformations are:
  463. * Translate: translate(<tx> [<ty>]), which specifies
  464. a translation by tx and ty. If <ty> is not provided,
  465. it is assumed to be zero. Result is
  466. ['translate', tx, ty]
  467. * Scale: scale(<sx> [<sy>]), which specifies a scale operation
  468. by sx and sy. If <sy> is not provided, it is assumed to be
  469. equal to <sx>. Result is: ['scale', sx, sy]
  470. * Rotate: rotate(<rotate-angle> [<cx> <cy>]), which specifies
  471. a rotation by <rotate-angle> degrees about a given point.
  472. If optional parameters <cx> and <cy> are not supplied,
  473. the rotate is about the origin of the current user coordinate
  474. system. Result is: ['rotate', rotate-angle, cx, cy]
  475. * Skew: skewX(<skew-angle>), which specifies a skew
  476. transformation along the x-axis. skewY(<skew-angle>), which
  477. specifies a skew transformation along the y-axis.
  478. Result is ['skew', angle-x, angle-y]
  479. * Matrix: matrix(<a> <b> <c> <d> <e> <f>), which specifies a
  480. transformation in the form of a transformation matrix of six
  481. values. matrix(a,b,c,d,e,f) is equivalent to applying the
  482. transformation matrix [a b c d e f]. Result is
  483. ['matrix', a, b, c, d, e, f]
  484. Note: All parameters to the transformations are "numbers",
  485. i.e. no units present.
  486. :param trstr: SVG transform string.
  487. :type trstr: str
  488. :return: List of transforms.
  489. :rtype: list
  490. """
  491. trlist = []
  492. assert isinstance(trstr, str)
  493. trstr = trstr.strip(' ')
  494. integer_re_str = r'[+-]?[0-9]+'
  495. number_re_str = r'(?:[+-]?[0-9]*\.[0-9]+(?:[Ee]' + integer_re_str + ')?' + r')|' + \
  496. r'(?:' + integer_re_str + r'(?:[Ee]' + integer_re_str + r')?)'
  497. # num_re_str = r'[\+\-]?[0-9\.e]+' # TODO: Negative exponents missing
  498. comma_or_space_re_str = r'(?:(?:\s+)|(?:\s*,\s*))'
  499. translate_re_str = r'translate\s*\(\s*(' + \
  500. number_re_str + r')(?:' + \
  501. comma_or_space_re_str + \
  502. r'(' + number_re_str + r'))?\s*\)'
  503. scale_re_str = r'scale\s*\(\s*(' + \
  504. number_re_str + r')' + \
  505. r'(?:' + comma_or_space_re_str + \
  506. r'(' + number_re_str + r'))?\s*\)'
  507. skew_re_str = r'skew([XY])\s*\(\s*(' + \
  508. number_re_str + r')\s*\)'
  509. rotate_re_str = r'rotate\s*\(\s*(' + \
  510. number_re_str + r')' + \
  511. r'(?:' + comma_or_space_re_str + \
  512. r'(' + number_re_str + r')' + \
  513. comma_or_space_re_str + \
  514. r'(' + number_re_str + r'))?\s*\)'
  515. matrix_re_str = r'matrix\s*\(\s*' + \
  516. r'(' + number_re_str + r')' + comma_or_space_re_str + \
  517. r'(' + number_re_str + r')' + comma_or_space_re_str + \
  518. r'(' + number_re_str + r')' + comma_or_space_re_str + \
  519. r'(' + number_re_str + r')' + comma_or_space_re_str + \
  520. r'(' + number_re_str + r')' + comma_or_space_re_str + \
  521. r'(' + number_re_str + r')\s*\)'
  522. while len(trstr) > 0:
  523. match = re.search(r'^' + translate_re_str, trstr)
  524. if match:
  525. trlist.append([
  526. 'translate',
  527. float(match.group(1)),
  528. float(match.group(2)) if (match.group(2) is not None) else 0.0
  529. ])
  530. trstr = trstr[len(match.group(0)):].strip(' ')
  531. continue
  532. match = re.search(r'^' + scale_re_str, trstr)
  533. if match:
  534. trlist.append([
  535. 'scale',
  536. float(match.group(1)),
  537. float(match.group(2)) if (match.group(2) is not None) else float(match.group(1))
  538. ])
  539. trstr = trstr[len(match.group(0)):].strip(' ')
  540. continue
  541. match = re.search(r'^' + skew_re_str, trstr)
  542. if match:
  543. trlist.append([
  544. 'skew',
  545. float(match.group(2)) if match.group(1) == 'X' else 0.0,
  546. float(match.group(2)) if match.group(1) == 'Y' else 0.0
  547. ])
  548. trstr = trstr[len(match.group(0)):].strip(' ')
  549. continue
  550. match = re.search(r'^' + rotate_re_str, trstr)
  551. if match:
  552. trlist.append([
  553. 'rotate',
  554. float(match.group(1)),
  555. float(match.group(2)) if match.group(2) else 0.0,
  556. float(match.group(3)) if match.group(3) else 0.0
  557. ])
  558. trstr = trstr[len(match.group(0)):].strip(' ')
  559. continue
  560. match = re.search(r'^' + matrix_re_str, trstr)
  561. if match:
  562. trlist.append(['matrix'] + [float(x) for x in match.groups()])
  563. trstr = trstr[len(match.group(0)):].strip(' ')
  564. continue
  565. # raise Exception("Don't know how to parse: %s" % trstr)
  566. log.error("[ERROR] Don't know how to parse: %s" % trstr)
  567. return trlist
  568. # if __name__ == "__main__":
  569. # tree = ET.parse('tests/svg/drawing.svg')
  570. # root = tree.getroot()
  571. # ns = re.search(r'\{(.*)\}', root.tag).group(1)
  572. # print(ns)
  573. # for geo in getsvggeo(root):
  574. # print(geo)