diff options
Diffstat (limited to 'Lib/fontTools/svgLib/path/parser.py')
-rw-r--r-- | Lib/fontTools/svgLib/path/parser.py | 72 |
1 files changed, 66 insertions, 6 deletions
diff --git a/Lib/fontTools/svgLib/path/parser.py b/Lib/fontTools/svgLib/path/parser.py index bdf3de0c..1fcf8998 100644 --- a/Lib/fontTools/svgLib/path/parser.py +++ b/Lib/fontTools/svgLib/path/parser.py @@ -7,26 +7,86 @@ # Copyright (c) 2013-2014 Lennart Regebro # License: MIT -from __future__ import ( - print_function, division, absolute_import, unicode_literals) -from fontTools.misc.py23 import * from .arc import EllipticalArc import re COMMANDS = set('MmZzLlHhVvCcSsQqTtAa') +ARC_COMMANDS = set("Aa") UPPERCASE = set('MZLHVCSQTA') COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])") -FLOAT_RE = re.compile(r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?") +FLOAT_RE = re.compile( + r"[-+]?" # optional sign + r"(?:" + r"(?:0|[1-9][0-9]*)(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)?" # int/float + r"|" + r"(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)" # float with leading dot (e.g. '.42') + r")" +) +BOOL_RE = re.compile("^[01]") +SEPARATOR_RE = re.compile(f"[, \t]") def _tokenize_path(pathdef): + arc_cmd = None for x in COMMAND_RE.split(pathdef): if x in COMMANDS: + arc_cmd = x if x in ARC_COMMANDS else None yield x - for token in FLOAT_RE.findall(x): - yield token + continue + + if arc_cmd: + try: + yield from _tokenize_arc_arguments(x) + except ValueError as e: + raise ValueError(f"Invalid arc command: '{arc_cmd}{x}'") from e + else: + for token in FLOAT_RE.findall(x): + yield token + + +ARC_ARGUMENT_TYPES = ( + ("rx", FLOAT_RE), + ("ry", FLOAT_RE), + ("x-axis-rotation", FLOAT_RE), + ("large-arc-flag", BOOL_RE), + ("sweep-flag", BOOL_RE), + ("x", FLOAT_RE), + ("y", FLOAT_RE), +) + + +def _tokenize_arc_arguments(arcdef): + raw_args = [s for s in SEPARATOR_RE.split(arcdef) if s] + if not raw_args: + raise ValueError(f"Not enough arguments: '{arcdef}'") + raw_args.reverse() + + i = 0 + while raw_args: + arg = raw_args.pop() + + name, pattern = ARC_ARGUMENT_TYPES[i] + match = pattern.search(arg) + if not match: + raise ValueError(f"Invalid argument for '{name}' parameter: {arg!r}") + + j, k = match.span() + yield arg[j:k] + arg = arg[k:] + + if arg: + raw_args.append(arg) + + # wrap around every 7 consecutive arguments + if i == 6: + i = 0 + else: + i += 1 + + if i != 0: + raise ValueError(f"Not enough arguments: '{arcdef}'") def parse_path(pathdef, pen, current_pos=(0, 0), arc_class=EllipticalArc): |