commit
040a66018b
2 changed files with 135 additions and 127 deletions
|
@ -1,4 +1,4 @@
|
||||||
version = "0.0.9"
|
version = "0.0.10"
|
||||||
author = "Andre von Houck and Ryan Oldenburg"
|
author = "Andre von Houck and Ryan Oldenburg"
|
||||||
description = "Full-featured 2d graphics library for Nim."
|
description = "Full-featured 2d graphics library for Nim."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
|
@ -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.
|
|
||||||
of 'm':
|
|
||||||
finishCommand()
|
|
||||||
command = RMove
|
|
||||||
of 'l':
|
|
||||||
finishCommand()
|
|
||||||
command = RLine
|
|
||||||
of 'h':
|
|
||||||
finishCommand()
|
|
||||||
command = RHLine
|
|
||||||
of 'v':
|
|
||||||
finishCommand()
|
|
||||||
command = RVLine
|
|
||||||
of 'c':
|
|
||||||
finishCommand()
|
|
||||||
command = RCubic
|
|
||||||
of 's':
|
|
||||||
finishCommand()
|
|
||||||
command = RSCurve
|
|
||||||
of 'q':
|
|
||||||
finishCommand()
|
|
||||||
command = RQuad
|
|
||||||
of 't':
|
|
||||||
finishCommand()
|
|
||||||
command = RTQuad
|
|
||||||
of 'a':
|
|
||||||
finishCommand()
|
|
||||||
command = RArc
|
|
||||||
of 'z':
|
|
||||||
finishCommand()
|
|
||||||
command = End
|
|
||||||
# Absolute
|
|
||||||
of 'M':
|
|
||||||
finishCommand()
|
|
||||||
command = Move
|
|
||||||
of 'L':
|
|
||||||
finishCommand()
|
|
||||||
command = Line
|
|
||||||
of 'H':
|
|
||||||
finishCommand()
|
|
||||||
command = HLine
|
|
||||||
of 'V':
|
|
||||||
finishCommand()
|
|
||||||
command = VLine
|
|
||||||
of 'C':
|
|
||||||
finishCommand()
|
|
||||||
command = Cubic
|
|
||||||
of 'S':
|
|
||||||
finishCommand()
|
|
||||||
command = SCurve
|
|
||||||
of 'Q':
|
|
||||||
finishCommand()
|
|
||||||
command = Quad
|
|
||||||
of 'T':
|
|
||||||
finishCommand()
|
|
||||||
command = TQuad
|
|
||||||
of 'A':
|
|
||||||
finishCommand()
|
|
||||||
command = Arc
|
|
||||||
of 'Z':
|
|
||||||
finishCommand()
|
|
||||||
command = End
|
|
||||||
# Punctuation
|
|
||||||
of '-', '+':
|
|
||||||
if number.len > 0 and number[^1] in {'e', 'E'}:
|
|
||||||
number &= c
|
|
||||||
else:
|
|
||||||
finishDigit()
|
|
||||||
number = $c
|
|
||||||
of ' ', ',', '\r', '\n', '\t':
|
|
||||||
finishDigit()
|
|
||||||
else: # TODO: still needed?
|
|
||||||
if command == Move and numbers.len == 2:
|
|
||||||
finishCommand()
|
|
||||||
command = Line
|
|
||||||
elif command == Line and numbers.len == 2:
|
|
||||||
finishCommand()
|
|
||||||
command = Line
|
|
||||||
number &= c
|
|
||||||
|
|
||||||
finishCommand()
|
while p < path.len:
|
||||||
|
case path[p]:
|
||||||
|
# Relative
|
||||||
|
of 'm':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RMove
|
||||||
|
of 'l':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RLine
|
||||||
|
of 'h':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RHLine
|
||||||
|
of 'v':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RVLine
|
||||||
|
of 'c':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RCubic
|
||||||
|
of 's':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RSCubic
|
||||||
|
of 'q':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RQuad
|
||||||
|
of 't':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RTQuad
|
||||||
|
of 'a':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = RArc
|
||||||
|
of 'z':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = Close
|
||||||
|
# Absolute
|
||||||
|
of 'M':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = Move
|
||||||
|
of 'L':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = Line
|
||||||
|
of 'H':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = HLine
|
||||||
|
of 'V':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = VLine
|
||||||
|
of 'C':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = Cubic
|
||||||
|
of 'S':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = SCubic
|
||||||
|
of 'Q':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = Quad
|
||||||
|
of 'T':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = TQuad
|
||||||
|
of 'A':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = Arc
|
||||||
|
of 'Z':
|
||||||
|
finishCommand(result)
|
||||||
|
kind = Close
|
||||||
|
of '-', '+':
|
||||||
|
if numberStart > 0 and path[p - 1] in {'e', 'E'}:
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
finishNumber()
|
||||||
|
numberStart = p
|
||||||
|
of ' ', ',', '\r', '\n', '\t':
|
||||||
|
finishNumber()
|
||||||
|
else:
|
||||||
|
if numberStart == 0:
|
||||||
|
numberStart = p
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
Loading…
Reference in a new issue