Skip to content

Instantly share code, notes, and snippets.

@PaperclipBadger
Created May 30, 2018 15:12
Show Gist options
  • Save PaperclipBadger/289855081986a16fd40bd974fd10d7c8 to your computer and use it in GitHub Desktop.
Save PaperclipBadger/289855081986a16fd40bd974fd10d7c8 to your computer and use it in GitHub Desktop.
"""
svg_path.py
-----------
Provides utility functions for manipulating svgs.
"""
import logging
from parser_combinators import char, white, integer
from parser_combinators import seq, any_, many, one_or_more, change_result
logger = logging.getLogger(__name__)
class ParseError(Exception):
"""Raised when parsing fails."""
pass
def draw_path(svg_path):
"""Converts a path specification into a list of points.
Args:
svg_path: A path specification. A comma seperated non-empty
sequence of:
- ``z``, representing the initial point of the path.
- ``m X Y``, where ``X`` and ``Y`` are sequences of digits.
Represents a move to the coordinate point (X, Y) relative
to the origin.
- ``l X Y``, where ``X`` and ``Y`` are sequences of digits.
Represents a move to the coordinate point (X, Y) relative
to the current position.
The first element of the sequence must be of the form
``m X Y``.
Returns: A list of points, which are pairs of floats.
Raises:
ParseError: If ``svg_path`` does not conform to the
specification.
"""
def new_Z(result):
"""Creates a Z command from a parse result."""
return 'Z', None
Z = change_result(char('z'), new_Z)
def new_M(result):
"""Creates an M command from a parse result."""
[_, ws_and_coords] = result
_, coords = zip(*ws_and_coords)
return 'M', coords
M = change_result(seq(char('m'), many(seq(many(white), integer))), new_M)
def new_L(result):
"""Creates an L command from a parse result."""
[_, ws_and_coords] = result
_, coords = zip(*ws_and_coords)
return 'M', coords
L = change_result(seq(char('l'), many(seq(many(white), integer))), new_L)
def new_path(result):
"""Flattens the list of commands, discarding commas."""
[first, rest] = result
# rest is a sequence of pairs of (commas + whitespace, command)
_, rest_ = zip(*rest)
return rest_.insert(0, first)
svg_path_parser = change_result(
seq(M, many(seq(char(,), many(white)), any_(Z, L, M))),
new_path
)
result, remaining = svg_path_parser(svg_path.lower())
if result is None or remaining != '':
raise ParseError('svg path "' + svg_path + '" is not valid')
else:
points = []
position = (0, 0)
for command in result:
kind, coords = command
if kind == 'Z':
position = points[0]
points.append(position)
if kind == 'M':
[X, Y] = coords
position = (X, Y)
points.append(position)
if kind == 'L':
[X, Y] = coords
X_, Y_ = position
position = (X_ + X, Y_ + Y)
points.append(position)
logger.info('drew path "{}", got points {}'.format(svg_path, points))
return points
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment