Created
May 30, 2018 15:12
-
-
Save PaperclipBadger/289855081986a16fd40bd974fd10d7c8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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