faster, no start

This commit is contained in:
Ryan Oldenburg 2021-01-02 22:47:00 -06:00
parent f636fbaae1
commit 3f3d645778

View file

@ -7,9 +7,9 @@ type
PathCommandKind* = enum PathCommandKind* = enum
## Type of path commands ## Type of path commands
Start, End Close,
Move, Line, HLine, VLine, Cubic, SCurve, Quad, TQuad, Arc, Move, Line, HLine, VLine, Cubic, SCubic, Quad, TQuad, Arc,
RMove, RLine, RHLine, RVLine, RCubic, RSCurve, RQuad, RTQuad, RArc RMove, RLine, RHLine, RVLine, RCubic, RSCubic, RQuad, RTQuad, RArc
PathCommand* = object PathCommand* = object
## Binary version of an SVG command ## Binary version of an SVG command
@ -23,131 +23,140 @@ type
proc newPath*(): Path = proc newPath*(): Path =
result = Path() result = Path()
proc commandNumbers(kind: PathCommandKind): int = proc parameterCount(kind: PathCommandKind): int =
## How many numbers does a command take:
case kind: case kind:
of Start, End: 0 of Close: 0
of Move, Line, RMove, RLine: 2 of Move, Line, RMove, RLine: 2
of HLine, VLine, RHLine, RVLine: 1 of HLine, VLine, RHLine, RVLine: 1
of Cubic, RCubic: 6 of Cubic, RCubic: 6
of SCurve, RSCurve, Quad, RQuad: 4 of SCubic, RSCubic, Quad, RQuad: 4
of TQuad, RTQuad: 2 of TQuad, RTQuad: 2
of Arc, RArc: 7 of Arc, RArc: 7
proc parsePath*(path: string): Path = proc parsePath*(path: string): Path =
## Converts a SVG style path into seq of commands. ## Converts a SVG style path into seq of commands.
result = newPath() result = newPath()
var command = Start
var number = ""
var numbers = newSeq[float32]()
template finishDigit() = if path.len == 0:
if number.len > 0: return
numbers.add(parseFloat(number))
number = ""
template finishCommand() = var
finishDigit() p, numberStart: int
if command != Start: armed: bool
let num = commandNumbers(command) kind: PathCommandKind
if num > 0: numbers: seq[float32]
if numbers.len mod num != 0:
raise newException(PixieError, proc finishNumber() =
"Could not parse path: " & $command & " has wrong number of prams," & if numberStart > 0:
" got " & $numbers.len & " but expected " & $num & ".") try:
for batch in 0 ..< numbers.len div num: numbers.add(parseFloat(path[numberStart ..< p]))
result.commands.add PathCommand( except ValueError:
kind: command, raise newException(PixieError, "Invalid path, parsing paramter failed")
numbers: numbers[batch*num ..< (batch+1)*num] numberStart = 0
)
numbers.setLen(0) proc finishCommand(result: var Path) =
finishNumber()
if armed: # The first finishCommand() arms
let paramCount = parameterCount(kind)
if paramCount == 0:
if numbers.len != 0:
raise newException(PixieError, "Invalid path, unexpected paramters")
result.commands.add(PathCommand(kind: kind))
else: else:
assert numbers.len == 0 if numbers.len mod paramCount != 0:
result.commands.add PathCommand(kind: command) raise newException(
PixieError,
"Invalid path, wrong number of parameters"
)
for batch in 0 ..< numbers.len div paramCount:
result.commands.add(PathCommand(
kind: kind,
numbers: numbers[batch * paramCount ..< (batch + 1) * paramCount]
))
numbers.setLen(0)
for c in path: armed = true
case c:
# Relative. while p < path.len:
case path[p]:
# Relative
of 'm': of 'm':
finishCommand() finishCommand(result)
command = RMove kind = RMove
of 'l': of 'l':
finishCommand() finishCommand(result)
command = RLine kind = RLine
of 'h': of 'h':
finishCommand() finishCommand(result)
command = RHLine kind = RHLine
of 'v': of 'v':
finishCommand() finishCommand(result)
command = RVLine kind = RVLine
of 'c': of 'c':
finishCommand() finishCommand(result)
command = RCubic kind = RCubic
of 's': of 's':
finishCommand() finishCommand(result)
command = RSCurve kind = RSCubic
of 'q': of 'q':
finishCommand() finishCommand(result)
command = RQuad kind = RQuad
of 't': of 't':
finishCommand() finishCommand(result)
command = RTQuad kind = RTQuad
of 'a': of 'a':
finishCommand() finishCommand(result)
command = RArc kind = RArc
of 'z': of 'z':
finishCommand() finishCommand(result)
command = End kind = Close
# Absolute # Absolute
of 'M': of 'M':
finishCommand() finishCommand(result)
command = Move kind = Move
of 'L': of 'L':
finishCommand() finishCommand(result)
command = Line kind = Line
of 'H': of 'H':
finishCommand() finishCommand(result)
command = HLine kind = HLine
of 'V': of 'V':
finishCommand() finishCommand(result)
command = VLine kind = VLine
of 'C': of 'C':
finishCommand() finishCommand(result)
command = Cubic kind = Cubic
of 'S': of 'S':
finishCommand() finishCommand(result)
command = SCurve kind = SCubic
of 'Q': of 'Q':
finishCommand() finishCommand(result)
command = Quad kind = Quad
of 'T': of 'T':
finishCommand() finishCommand(result)
command = TQuad kind = TQuad
of 'A': of 'A':
finishCommand() finishCommand(result)
command = Arc kind = Arc
of 'Z': of 'Z':
finishCommand() finishCommand(result)
command = End kind = Close
# Punctuation
of '-', '+': of '-', '+':
if number.len > 0 and number[^1] in {'e', 'E'}: if numberStart > 0 and path[p - 1] in {'e', 'E'}:
number &= c discard
else: else:
finishDigit() finishNumber()
number = $c numberStart = p
of ' ', ',', '\r', '\n', '\t': of ' ', ',', '\r', '\n', '\t':
finishDigit() finishNumber()
else: # TODO: still needed? else:
if command == Move and numbers.len == 2: if numberStart == 0:
finishCommand() numberStart = p
command = Line
elif command == Line and numbers.len == 2:
finishCommand()
command = Line
number &= c
finishCommand() inc p
finishCommand(result)
proc `$`*(path: Path): string = proc `$`*(path: Path): string =
for i, command in path.commands: for i, command in path.commands:
@ -157,7 +166,7 @@ proc `$`*(path: Path): string =
of HLine: result.add "H" of HLine: result.add "H"
of VLine: result.add "V" of VLine: result.add "V"
of Cubic: result.add "C" of Cubic: result.add "C"
of SCurve: result.add "S" of SCubic: result.add "S"
of Quad: result.add "Q" of Quad: result.add "Q"
of TQuad: result.add "T" of TQuad: result.add "T"
of Arc: result.add "A" of Arc: result.add "A"
@ -166,15 +175,14 @@ proc `$`*(path: Path): string =
of RHLine: result.add "h" of RHLine: result.add "h"
of RVLine: result.add "v" of RVLine: result.add "v"
of RCubic: result.add "c" of RCubic: result.add "c"
of RSCurve: result.add "s" of RSCubic: result.add "s"
of RQuad: result.add "q" of RQuad: result.add "q"
of RTQuad: result.add "t" of RTQuad: result.add "t"
of RArc: result.add "a" of RArc: result.add "a"
of End: result.add "Z" of Close: result.add "Z"
of Start: result.add "?"
for j, number in command.numbers: for j, number in command.numbers:
if floor(number) == number: if floor(number) == number:
result.add $(number.int) result.add $number.int
else: else:
result.add $number result.add $number
if i != path.commands.len - 1 or j != command.numbers.len - 1: if i != path.commands.len - 1 or j != command.numbers.len - 1:
@ -412,7 +420,7 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
a += step a += step
at = polygon[^1] at = polygon[^1]
of End: of Close:
assert command.numbers.len == 0 assert command.numbers.len == 0
if at != start: if at != start:
if prevCommand == Quad or prevCommand == TQuad: if prevCommand == Quad or prevCommand == TQuad:
@ -481,7 +489,7 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] =
drawCurve(@[at, ctr, ctr2, to]) drawCurve(@[at, ctr, ctr2, to])
at = to at = to
of RSCurve: of RSCubic:
assert command.numbers.len == 4 assert command.numbers.len == 4
ctr = at ctr = at
ctr2.x = at.x + command.numbers[0] ctr2.x = at.x + command.numbers[0]
@ -795,7 +803,7 @@ proc addPath*(path: Path, other: Path) =
proc closePath*(path: Path) = proc closePath*(path: Path) =
## Causes the point of the pen to move back to the start of the current sub-path. It tries to draw a straight line from the current point to the start. If the shape has already been closed or has only one point, this function does nothing. ## Causes the point of the pen to move back to the start of the current sub-path. It tries to draw a straight line from the current point to the start. If the shape has already been closed or has only one point, this function does nothing.
path.commands.add PathCommand(kind: End) path.commands.add PathCommand(kind: Close)
proc moveTo*(path: Path, x, y: float32) = proc moveTo*(path: Path, x, y: float32) =
## Moves the starting point of a new sub-path to the (x, y) coordinates. ## Moves the starting point of a new sub-path to the (x, y) coordinates.