Initial implementation of SVG path parser

This commit is contained in:
XANTRONIX Development 2024-01-01 23:12:37 -05:00
parent 61abfc73ac
commit c23a868513

View file

@ -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()