From c23a868513d0d8b4732e377fb9ef0501b45a08b2 Mon Sep 17 00:00:00 2001 From: XANTRONIX Development Date: Mon, 1 Jan 2024 23:12:37 -0500 Subject: [PATCH] Initial implementation of SVG path parser --- py/hexagram/path.py | 118 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/py/hexagram/path.py b/py/hexagram/path.py index 1dd70ba..024c956 100644 --- a/py/hexagram/path.py +++ b/py/hexagram/path.py @@ -1,13 +1,131 @@ from typing import Iterable +import enum import cairo +class State(enum.Enum): + NONE = 0 + COMMAND = 1 + INT = 2 + FLOAT = 3 + +class ParseError(Exception): + pass + +class PathCommand(): + __slots__ = 'command', 'args', 'arg', + + def __init__(self): + self.command = None + self.args = list() + self.arg = '' + + def set_command(self, command: str): + self.command = command + + def arg_addch(self, ch: str): + self.arg += ch + + def arg_next(self, ch: str): + if len(self.arg) == 0: + return + + self.arg += ch + self.args.append(self.arg) + self.arg = '' + + def arg_finish(self): + if len(self.arg) > 0: + self.args.append(self.arg) + + self.arg = '' + + def finish(self): + if len(self.arg) > 0: + self.args.append(self.arg) + + ret = [self.command, self.args] + + self.command = None + self.args = list() + self.arg = '' + + return ret + class Path(): __slots__ = 'commands', def __init__(self, commands: Iterable): self.commands = commands + def is_command(c): + return (c >= ord('a') and c <= ord('z')) or (c >= ord('A') and c <= ord('Z')) + + def is_number(c): + return (c >= ord('0') and c <= ord('9')) + + def parse(text: str) -> list: + commands = list() + command = PathCommand() + state = State.NONE + + for ch in text: + c = ord(ch) + + if state is State.NONE: + if Path.is_command(c): + command.set_command(ch) + state = State.COMMAND + elif ch == ' ': + continue + else: + raise ParseError() + elif state is State.COMMAND: + if Path.is_command(c): + commands.append(command.finish()) + command.set_command(ch) + elif Path.is_number(c) or ch == '-': + state = State.INT + command.arg_addch(ch) + elif ch == '.': + state = State.FLOAT + command.arg_addch(ch) + elif ch == ' ': + continue + else: + raise ParseError() + elif state is State.INT: + if Path.is_command(c): + commands.append(command.finish()) + command.set_command(ch) + elif Path.is_number(c): + command.arg_addch(ch) + elif ch == '.': + command.arg_addch(ch) + state = State.FLOAT + elif ch == '-': + command.arg_next(ch) + elif ch == ' ' or ch == ',': + command.arg_finish() + else: + raise ParseError() + elif state is State.FLOAT: + if Path.is_command(c): + commands.append(command.finish()) + command.set_command(ch) + elif Path.is_number(c): + command.arg_addch(ch) + elif ch == ' ' or ch == ',': + state = State.INT + command.arg_finish() + else: + raise ParseError(ch) + + if command.command is not None: + commands.append(command.finish()) + + return commands + def horiz_to(self, cr: cairo.Context, x2: float): _, y = cr.get_current_point()