Merge pull request #468 from treeform/guzba

path.commands is seq[float32]
This commit is contained in:
Andre von Houck 2022-07-21 17:54:20 -07:00 committed by GitHub
commit 94cbbb8d3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -21,14 +21,9 @@ type
Move, Line, HLine, VLine, Cubic, SCubic, Quad, TQuad, Arc, Move, Line, HLine, VLine, Cubic, SCubic, Quad, TQuad, Arc,
RMove, RLine, RHLine, RVLine, RCubic, RSCubic, RQuad, RTQuad, RArc RMove, RLine, RHLine, RVLine, RCubic, RSCubic, RQuad, RTQuad, RArc
PathCommand = object
## Binary version of an SVG command.
kind: PathCommandKind
numbers: seq[float32]
Path* = ref object Path* = ref object
## Used to hold paths and create paths. ## Used to hold paths and create paths.
commands: seq[PathCommand] commands: seq[float32]
start, at: Vec2 # Maintained by moveTo, lineTo, etc. Used by arcTo. start, at: Vec2 # Maintained by moveTo, lineTo, etc. Used by arcTo.
SomePath* = Path | string SomePath* = Path | string
@ -81,8 +76,11 @@ proc parameterCount(kind: PathCommandKind): int =
proc `$`*(path: Path): string {.raises: [].} = proc `$`*(path: Path): string {.raises: [].} =
## Turn path int into a string. ## Turn path int into a string.
for i, command in path.commands: var i: int
case command.kind while i < path.commands.len:
let kind = path.commands[i].PathCommandKind
inc i
case kind
of Move: result.add "M" of Move: result.add "M"
of Line: result.add "L" of Line: result.add "L"
of HLine: result.add "H" of HLine: result.add "H"
@ -102,13 +100,15 @@ proc `$`*(path: Path): string {.raises: [].} =
of RTQuad: result.add "t" of RTQuad: result.add "t"
of RArc: result.add "a" of RArc: result.add "a"
of Close: result.add "Z" of Close: result.add "Z"
for j, number in command.numbers: for _ in 0 ..< kind.parameterCount():
let number = path.commands[i]
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:
result.add " " result.add " "
inc i
proc parsePath*(path: string): Path {.raises: [PixieError].} = proc parsePath*(path: string): Path {.raises: [PixieError].} =
## Converts a SVG style path string into seq of commands. ## Converts a SVG style path string into seq of commands.
@ -140,7 +140,7 @@ proc parsePath*(path: string): Path {.raises: [PixieError].} =
if paramCount == 0: if paramCount == 0:
if numbers.len != 0: if numbers.len != 0:
raise newException(PixieError, "Invalid path, unexpected parameters") raise newException(PixieError, "Invalid path, unexpected parameters")
result.commands.add(PathCommand(kind: kind)) result.commands.add(kind.float32)
else: else:
if numbers.len mod paramCount != 0: if numbers.len mod paramCount != 0:
raise newException( raise newException(
@ -153,10 +153,9 @@ proc parsePath*(path: string): Path {.raises: [PixieError].} =
kind = Line kind = Line
elif kind == RMove: elif kind == RMove:
kind = RLine kind = RLine
result.commands.add(PathCommand( result.commands.add(kind.float32)
kind: kind, for i in 0 ..< paramCount:
numbers: numbers[batch * paramCount ..< (batch + 1) * paramCount] result.commands.add(numbers[batch * paramCount + i])
))
numbers.setLen(0) numbers.setLen(0)
armed = true armed = true
@ -261,65 +260,72 @@ proc transform*(path: Path, mat: Mat3) {.raises: [].} =
if mat == mat3(): if mat == mat3():
return return
if path.commands.len > 0 and path.commands[0].kind == RMove: if path.commands.len > 0 and path.commands[0] == RMove.float32:
path.commands[0].kind = Move path.commands[0] = Move.float32
var i: int
# for command in path.commands.mitems:
while i < path.commands.len:
let kind = path.commands[i].PathCommandKind
inc i
for command in path.commands.mitems:
var mat = mat var mat = mat
if command.kind.isRelative(): if kind.isRelative():
mat.pos = vec2(0) mat.pos = vec2(0)
case command.kind: case kind:
of Close: of Close:
discard discard
of Move, Line, RMove, RLine, TQuad, RTQuad: of Move, Line, RMove, RLine, TQuad, RTQuad:
var pos = vec2(command.numbers[0], command.numbers[1]) var pos = vec2(path.commands[i + 0], path.commands[i + 1])
pos = mat * pos pos = mat * pos
command.numbers[0] = pos.x path.commands[i + 0] = pos.x
command.numbers[1] = pos.y path.commands[i + 1] = pos.y
of HLine, RHLine: of HLine, RHLine:
var pos = vec2(command.numbers[0], 0) var pos = vec2(path.commands[i + 0], 0)
pos = mat * pos pos = mat * pos
command.numbers[0] = pos.x path.commands[i + 0] = pos.x
of VLine, RVLine: of VLine, RVLine:
var pos = vec2(0, command.numbers[0]) var pos = vec2(0, path.commands[i + 0])
pos = mat * pos pos = mat * pos
command.numbers[0] = pos.y path.commands[i + 0] = pos.y
of Cubic, RCubic: of Cubic, RCubic:
var var
ctrl1 = vec2(command.numbers[0], command.numbers[1]) ctrl1 = vec2(path.commands[i + 0], path.commands[i + 1])
ctrl2 = vec2(command.numbers[2], command.numbers[3]) ctrl2 = vec2(path.commands[i + 2], path.commands[i + 3])
to = vec2(command.numbers[4], command.numbers[5]) to = vec2(path.commands[i + 4], path.commands[i + 5])
ctrl1 = mat * ctrl1 ctrl1 = mat * ctrl1
ctrl2 = mat * ctrl2 ctrl2 = mat * ctrl2
to = mat * to to = mat * to
command.numbers[0] = ctrl1.x path.commands[i + 0] = ctrl1.x
command.numbers[1] = ctrl1.y path.commands[i + 1] = ctrl1.y
command.numbers[2] = ctrl2.x path.commands[i + 2] = ctrl2.x
command.numbers[3] = ctrl2.y path.commands[i + 3] = ctrl2.y
command.numbers[4] = to.x path.commands[i + 4] = to.x
command.numbers[5] = to.y path.commands[i + 5] = to.y
of SCubic, RSCubic, Quad, RQuad: of SCubic, RSCubic, Quad, RQuad:
var var
ctrl = vec2(command.numbers[0], command.numbers[1]) ctrl = vec2(path.commands[i + 0], path.commands[i + 1])
to = vec2(command.numbers[2], command.numbers[3]) to = vec2(path.commands[i + 2], path.commands[i + 3])
ctrl = mat * ctrl ctrl = mat * ctrl
to = mat * to to = mat * to
command.numbers[0] = ctrl.x path.commands[i + 0] = ctrl.x
command.numbers[1] = ctrl.y path.commands[i + 1] = ctrl.y
command.numbers[2] = to.x path.commands[i + 2] = to.x
command.numbers[3] = to.y path.commands[i + 3] = to.y
of Arc, RArc: of Arc, RArc:
var var
radii = vec2(command.numbers[0], command.numbers[1]) radii = vec2(path.commands[i + 0], path.commands[i + 1])
to = vec2(command.numbers[5], command.numbers[6]) to = vec2(path.commands[i + 5], path.commands[i + 6])
# Extract the scale from the matrix and only apply that to the radii # Extract the scale from the matrix and only apply that to the radii
radii = scale(vec2(mat[0, 0], mat[1, 1])) * radii radii = scale(vec2(mat[0, 0], mat[1, 1])) * radii
to = mat * to to = mat * to
command.numbers[0] = radii.x path.commands[i + 0] = radii.x
command.numbers[1] = radii.y path.commands[i + 1] = radii.y
command.numbers[5] = to.x path.commands[i + 5] = to.x
command.numbers[6] = to.y path.commands[i + 6] = to.y
i += kind.parameterCount()
proc addPath*(path: Path, other: Path) {.raises: [].} = proc addPath*(path: Path, other: Path) {.raises: [].} =
## Adds a path to the current path. ## Adds a path to the current path.
@ -329,12 +335,12 @@ proc closePath*(path: Path) {.raises: [].} =
## Attempts to add a straight line from the current point to the start of ## Attempts to add a straight line from the current point to the start of
## the current sub-path. If the shape has already been closed or has only ## the current sub-path. If the shape has already been closed or has only
## one point, this function does nothing. ## one point, this function does nothing.
path.commands.add(PathCommand(kind: Close)) path.commands.add(Close.float32)
path.at = path.start path.at = path.start
proc moveTo*(path: Path, x, y: float32) {.raises: [].} = proc moveTo*(path: Path, x, y: float32) {.raises: [].} =
## Begins a new sub-path at the point (x, y). ## Begins a new sub-path at the point (x, y).
path.commands.add(PathCommand(kind: Move, numbers: @[x, y])) path.commands.add(@[Move.float32, x, y])
path.start = vec2(x, y) path.start = vec2(x, y)
path.at = path.start path.at = path.start
@ -345,7 +351,7 @@ proc moveTo*(path: Path, v: Vec2) {.inline, raises: [].} =
proc lineTo*(path: Path, x, y: float32) {.raises: [].} = proc lineTo*(path: Path, x, y: float32) {.raises: [].} =
## Adds a straight line to the current sub-path by connecting the sub-path's ## Adds a straight line to the current sub-path by connecting the sub-path's
## last point to the specified (x, y) coordinates. ## last point to the specified (x, y) coordinates.
path.commands.add(PathCommand(kind: Line, numbers: @[x, y])) path.commands.add(@[Line.float32, x, y])
path.at = vec2(x, y) path.at = vec2(x, y)
proc lineTo*(path: Path, v: Vec2) {.inline, raises: [].} = proc lineTo*(path: Path, v: Vec2) {.inline, raises: [].} =
@ -358,10 +364,7 @@ proc bezierCurveTo*(path: Path, x1, y1, x2, y2, x3, y3: float32) {.raises: [].}
## points: the first two are control points and the third one is the end ## points: the first two are control points and the third one is the end
## point. The starting point is the latest point in the current path, ## point. The starting point is the latest point in the current path,
## which can be changed using moveTo() before creating the Bézier curve. ## which can be changed using moveTo() before creating the Bézier curve.
path.commands.add(PathCommand( path.commands.add(@[Cubic.float32, x1, y1, x2, y2, x3, y3])
kind: Cubic,
numbers: @[x1, y1, x2, y2, x3, y3]
))
path.at = vec2(x3, y3) path.at = vec2(x3, y3)
proc bezierCurveTo*(path: Path, ctrl1, ctrl2, to: Vec2) {.inline, raises: [].} = proc bezierCurveTo*(path: Path, ctrl1, ctrl2, to: Vec2) {.inline, raises: [].} =
@ -377,10 +380,7 @@ proc quadraticCurveTo*(path: Path, x1, y1, x2, y2: float32) {.raises: [].} =
## point. The starting point is the latest point in the current path, ## point. The starting point is the latest point in the current path,
## which can be changed using moveTo() before creating the quadratic ## which can be changed using moveTo() before creating the quadratic
## Bézier curve. ## Bézier curve.
path.commands.add(PathCommand( path.commands.add(@[Quad.float32, x1, y1, x2, y2])
kind: Quad,
numbers: @[x1, y1, x2, y2]
))
path.at = vec2(x2, y2) path.at = vec2(x2, y2)
proc quadraticCurveTo*(path: Path, ctrl, to: Vec2) {.inline, raises: [].} = proc quadraticCurveTo*(path: Path, ctrl, to: Vec2) {.inline, raises: [].} =
@ -400,12 +400,12 @@ proc ellipticalArcTo*(
) {.raises: [].} = ) {.raises: [].} =
## Adds an elliptical arc to the current sub-path, using the given radius ## Adds an elliptical arc to the current sub-path, using the given radius
## ratios, sweep flags, and end position. ## ratios, sweep flags, and end position.
path.commands.add(PathCommand( path.commands.add(@[
kind: Arc, Arc.float32,
numbers: @[ rx, ry,
rx, ry, xAxisRotation, largeArcFlag.float32, sweepFlag.float32, x, y xAxisRotation, largeArcFlag.float32, sweepFlag.float32,
] x, y
)) ])
path.at = vec2(x, y) path.at = vec2(x, y)
proc arc*( proc arc*(
@ -878,49 +878,50 @@ proc commandsToShapes(
t += step t += step
step = min(step * 2, 1 - t) # Optimistically attempt larger steps step = min(step * 2, 1 - t) # Optimistically attempt larger steps
for command in path.commands: var i: int
if command.numbers.len != command.kind.parameterCount(): while i < path.commands.len:
raise newException(PixieError, "Invalid path") let kind = path.commands[i].PathCommandKind
inc i
case command.kind: case kind:
of Move: of Move:
if shape.len > 0: if shape.len > 0:
if closeSubpaths: if closeSubpaths:
shape.addSegment(at, start) shape.addSegment(at, start)
result.add(shape) result.add(shape)
shape = newSeq[Vec2]() shape = newSeq[Vec2]()
at.x = command.numbers[0] at.x = path.commands[i + 0]
at.y = command.numbers[1] at.y = path.commands[i + 1]
start = at start = at
of Line: of Line:
let to = vec2(command.numbers[0], command.numbers[1]) let to = vec2(path.commands[i + 0], path.commands[i + 1])
shape.addSegment(at, to) shape.addSegment(at, to)
at = to at = to
of HLine: of HLine:
let to = vec2(command.numbers[0], at.y) let to = vec2(path.commands[i + 0], at.y)
shape.addSegment(at, to) shape.addSegment(at, to)
at = to at = to
of VLine: of VLine:
let to = vec2(at.x, command.numbers[0]) let to = vec2(at.x, path.commands[i + 0])
shape.addSegment(at, to) shape.addSegment(at, to)
at = to at = to
of Cubic: of Cubic:
let let
ctrl1 = vec2(command.numbers[0], command.numbers[1]) ctrl1 = vec2(path.commands[i + 0], path.commands[i + 1])
ctrl2 = vec2(command.numbers[2], command.numbers[3]) ctrl2 = vec2(path.commands[i + 2], path.commands[i + 3])
to = vec2(command.numbers[4], command.numbers[5]) to = vec2(path.commands[i + 4], path.commands[i + 5])
shape.addCubic(at, ctrl1, ctrl2, to) shape.addCubic(at, ctrl1, ctrl2, to)
at = to at = to
prevCtrl2 = ctrl2 prevCtrl2 = ctrl2
of SCubic: of SCubic:
let let
ctrl2 = vec2(command.numbers[0], command.numbers[1]) ctrl2 = vec2(path.commands[i + 0], path.commands[i + 1])
to = vec2(command.numbers[2], command.numbers[3]) to = vec2(path.commands[i + 2], path.commands[i + 3])
if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}: if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}:
let ctrl1 = at * 2 - prevCtrl2 let ctrl1 = at * 2 - prevCtrl2
shape.addCubic(at, ctrl1, ctrl2, to) shape.addCubic(at, ctrl1, ctrl2, to)
@ -931,15 +932,15 @@ proc commandsToShapes(
of Quad: of Quad:
let let
ctrl = vec2(command.numbers[0], command.numbers[1]) ctrl = vec2(path.commands[i + 0], path.commands[i + 1])
to = vec2(command.numbers[2], command.numbers[3]) to = vec2(path.commands[i + 2], path.commands[i + 3])
shape.addQuadratic(at, ctrl, to) shape.addQuadratic(at, ctrl, to)
at = to at = to
prevCtrl = ctrl prevCtrl = ctrl
of TQuad: of TQuad:
let let
to = vec2(command.numbers[0], command.numbers[1]) to = vec2(path.commands[i + 0], path.commands[i + 1])
ctrl = ctrl =
if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}: if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}:
at * 2 - prevCtrl at * 2 - prevCtrl
@ -951,11 +952,11 @@ proc commandsToShapes(
of Arc: of Arc:
let let
radii = vec2(command.numbers[0], command.numbers[1]) radii = vec2(path.commands[i + 0], path.commands[i + 1])
rotation = command.numbers[2] rotation = path.commands[i + 2]
large = command.numbers[3] == 1 large = path.commands[i + 3] == 1
sweep = command.numbers[4] == 1 sweep = path.commands[i + 4] == 1
to = vec2(command.numbers[5], command.numbers[6]) to = vec2(path.commands[i + 5], path.commands[i + 6])
shape.addArc(at, radii, rotation, large, sweep, to) shape.addArc(at, radii, rotation, large, sweep, to)
at = to at = to
@ -963,38 +964,38 @@ proc commandsToShapes(
if shape.len > 0: if shape.len > 0:
result.add(shape) result.add(shape)
shape = newSeq[Vec2]() shape = newSeq[Vec2]()
at.x += command.numbers[0] at.x += path.commands[i + 0]
at.y += command.numbers[1] at.y += path.commands[i + 1]
start = at start = at
of RLine: of RLine:
let to = vec2(at.x + command.numbers[0], at.y + command.numbers[1]) let to = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1])
shape.addSegment(at, to) shape.addSegment(at, to)
at = to at = to
of RHLine: of RHLine:
let to = vec2(at.x + command.numbers[0], at.y) let to = vec2(at.x + path.commands[i + 0], at.y)
shape.addSegment(at, to) shape.addSegment(at, to)
at = to at = to
of RVLine: of RVLine:
let to = vec2(at.x, at.y + command.numbers[0]) let to = vec2(at.x, at.y + path.commands[i + 0])
shape.addSegment(at, to) shape.addSegment(at, to)
at = to at = to
of RCubic: of RCubic:
let let
ctrl1 = vec2(at.x + command.numbers[0], at.y + command.numbers[1]) ctrl1 = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1])
ctrl2 = vec2(at.x + command.numbers[2], at.y + command.numbers[3]) ctrl2 = vec2(at.x + path.commands[i + 2], at.y + path.commands[i + 3])
to = vec2(at.x + command.numbers[4], at.y + command.numbers[5]) to = vec2(at.x + path.commands[i + 4], at.y + path.commands[i + 5])
shape.addCubic(at, ctrl1, ctrl2, to) shape.addCubic(at, ctrl1, ctrl2, to)
at = to at = to
prevCtrl2 = ctrl2 prevCtrl2 = ctrl2
of RSCubic: of RSCubic:
let let
ctrl2 = vec2(at.x + command.numbers[0], at.y + command.numbers[1]) ctrl2 = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1])
to = vec2(at.x + command.numbers[2], at.y + command.numbers[3]) to = vec2(at.x + path.commands[i + 2], at.y + path.commands[i + 3])
ctrl1 = ctrl1 =
if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}: if prevCommandKind in {Cubic, SCubic, RCubic, RSCubic}:
at * 2 - prevCtrl2 at * 2 - prevCtrl2
@ -1006,15 +1007,15 @@ proc commandsToShapes(
of RQuad: of RQuad:
let let
ctrl = vec2(at.x + command.numbers[0], at.y + command.numbers[1]) ctrl = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1])
to = vec2(at.x + command.numbers[2], at.y + command.numbers[3]) to = vec2(at.x + path.commands[i + 2], at.y + path.commands[i + 3])
shape.addQuadratic(at, ctrl, to) shape.addQuadratic(at, ctrl, to)
at = to at = to
prevCtrl = ctrl prevCtrl = ctrl
of RTQuad: of RTQuad:
let let
to = vec2(at.x + command.numbers[0], at.y + command.numbers[1]) to = vec2(at.x + path.commands[i + 0], at.y + path.commands[i + 1])
ctrl = ctrl =
if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}: if prevCommandKind in {Quad, TQuad, RQuad, RTQuad}:
at * 2 - prevCtrl at * 2 - prevCtrl
@ -1026,11 +1027,11 @@ proc commandsToShapes(
of RArc: of RArc:
let let
radii = vec2(command.numbers[0], command.numbers[1]) radii = vec2(path.commands[i + 0], path.commands[i + 1])
rotation = command.numbers[2] rotation = path.commands[i + 2]
large = command.numbers[3] == 1 large = path.commands[i + 3] == 1
sweep = command.numbers[4] == 1 sweep = path.commands[i + 4] == 1
to = vec2(at.x + command.numbers[5], at.y + command.numbers[6]) to = vec2(at.x + path.commands[i + 5], at.y + path.commands[i + 6])
shape.addArc(at, radii, rotation, large, sweep, to) shape.addArc(at, radii, rotation, large, sweep, to)
at = to at = to
@ -1042,7 +1043,8 @@ proc commandsToShapes(
result.add(shape) result.add(shape)
shape = newSeq[Vec2]() shape = newSeq[Vec2]()
prevCommandKind = command.kind i += kind.parameterCount()
prevCommandKind = kind
if shape.len > 0: if shape.len > 0:
if closeSubpaths: if closeSubpaths: