diff --git a/src/pixie.nim b/src/pixie.nim
index d8221cb..822f11a 100644
--- a/src/pixie.nim
+++ b/src/pixie.nim
@@ -1,8 +1,7 @@
import bumpy, chroma, flatty/binny, os, pixie/blends, pixie/common,
pixie/context, pixie/fileformats/bmp, pixie/fileformats/gif,
pixie/fileformats/jpg, pixie/fileformats/png, pixie/fileformats/svg,
- pixie/fonts, pixie/images, pixie/masks, pixie/paints, pixie/paths, strutils,
- vmath
+ pixie/fonts, pixie/images, pixie/masks, pixie/paints, pixie/paths, strutils, vmath
export blends, bumpy, chroma, common, context, fonts, images, masks, paints,
paths, vmath
diff --git a/src/pixie/context.nim b/src/pixie/context.nim
index 1ed8788..1f46bab 100644
--- a/src/pixie/context.nim
+++ b/src/pixie/context.nim
@@ -11,11 +11,13 @@ type
image*: Image
fillStyle*, strokeStyle*: Paint
lineWidth*: float32
+ miterLimit*: float32
lineCap*: LineCap
lineJoin*: LineJoin
font*: Font
textAlign*: HAlignMode
path: Path
+ lineDash: seq[float32]
mat: Mat3
mask: Mask
layer: Image
@@ -24,10 +26,12 @@ type
ContextState = object
fillStyle, strokeStyle: Paint
lineWidth: float32
+ miterLimit: float32
lineCap: LineCap
lineJoin: LineJoin
font: Font
textAlign: HAlignMode
+ lineDash: seq[float32]
mat: Mat3
mask: Mask
layer: Image
@@ -41,6 +45,7 @@ proc newContext*(image: Image): Context =
result.image = image
result.mat = mat3()
result.lineWidth = 1
+ result.miterLimit = 10
result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
result.strokeStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
@@ -52,26 +57,31 @@ proc state(ctx: Context): ContextState =
result.fillStyle = ctx.fillStyle
result.strokeStyle = ctx.strokeStyle
result.lineWidth = ctx.lineWidth
+ result.miterLimit = ctx.miterLimit
result.lineCap = ctx.lineCap
result.lineJoin = ctx.lineJoin
result.font = ctx.font
result.textAlign = ctx.textAlign
+ result.lineDash = ctx.lineDash
result.mat = ctx.mat
result.mask = if ctx.mask != nil: ctx.mask.copy() else: nil
proc save*(ctx: Context) {.inline.} =
- ## Saves the entire state of the canvas by pushing the current state onto
+ ## Saves the entire state of the context by pushing the current state onto
## a stack.
ctx.stateStack.add(ctx.state())
proc saveLayer*(ctx: Context) =
+ ## Saves the entire state of the context by pushing the current state onto
+ ## a stack and allocates a new image layer for subsequent drawing. Calling
+ ## restore blends the current layer image onto the prior layer or root image.
var state = ctx.state()
state.layer = ctx.layer
ctx.stateStack.add(state)
ctx.layer = newImage(ctx.image.width, ctx.image.height)
proc restore*(ctx: Context) =
- ## Restores the most recently saved canvas state by popping the top entry
+ ## Restores the most recently saved context state by popping the top entry
## in the drawing state stack. If there is no saved state, this method does
## nothing.
if ctx.stateStack.len == 0:
@@ -85,10 +95,12 @@ proc restore*(ctx: Context) =
ctx.fillStyle = state.fillStyle
ctx.strokeStyle = state.strokeStyle
ctx.lineWidth = state.lineWidth
+ ctx.miterLimit = state.miterLimit
ctx.lineCap = state.lineCap
ctx.lineJoin = state.lineJoin
ctx.font = state.font
ctx.textAlign = state.textAlign
+ ctx.lineDash = state.lineDash
ctx.mat = state.mat
ctx.mask = state.mask
ctx.layer = state.layer
@@ -118,7 +130,9 @@ proc stroke(ctx: Context, image: Image, path: Path) {.inline.} =
ctx.mat,
ctx.lineWidth,
ctx.lineCap,
- ctx.lineJoin
+ ctx.lineJoin,
+ ctx.miterLimit,
+ ctx.lineDash
)
proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
@@ -155,7 +169,9 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
ctx.lineWidth,
hAlign = ctx.textAlign,
lineCap = ctx.lineCap,
- lineJoin = ctx.lineJoin
+ lineJoin = ctx.lineJoin,
+ miterLimit = ctx.miterLimit,
+ dashes = ctx.lineDash
)
proc beginPath*(ctx: Context) {.inline.} =
@@ -289,13 +305,13 @@ proc clearRect*(ctx: Context, rect: Rect) =
if ctx.layer != nil:
ctx.layer.fillPath(
path,
- Paint(kind: pkSolid, color:rgbx(0, 0, 0, 0), blendMode: bmOverwrite),
+ Paint(kind: pkSolid, color: rgbx(0, 0, 0, 0), blendMode: bmOverwrite),
ctx.mat
)
else:
ctx.image.fillPath(
path,
- Paint(kind: pkSolid, color:rgbx(0, 0, 0, 0), blendMode: bmOverwrite),
+ Paint(kind: pkSolid, color: rgbx(0, 0, 0, 0), blendMode: bmOverwrite),
ctx.mat
)
@@ -368,6 +384,12 @@ proc measureText*(ctx: Context, text: string): TextMetrics =
let bounds = typeset(ctx.font, text).computeBounds()
result.width = bounds.x
+proc getLineDash*(ctx: Context): seq[float32] {.inline.} =
+ ctx.lineDash
+
+proc setLineDash*(ctx: Context, lineDash: seq[float32]) {.inline.} =
+ ctx.lineDash = lineDash
+
proc getTransform*(ctx: Context): Mat3 {.inline.} =
## Retrieves the current transform matrix being applied to the context.
ctx.mat
@@ -399,12 +421,12 @@ proc translate*(ctx: Context, x, y: float32) {.inline.} =
ctx.mat = ctx.mat * translate(vec2(x, y))
proc scale*(ctx: Context, v: Vec2) {.inline.} =
- ## Adds a scaling transformation to the canvas units horizontally and/or
+ ## Adds a scaling transformation to the context units horizontally and/or
## vertically.
ctx.mat = ctx.mat * scale(v)
proc scale*(ctx: Context, x, y: float32) {.inline.} =
- ## Adds a scaling transformation to the canvas units horizontally and/or
+ ## Adds a scaling transformation to the context units horizontally and/or
## vertically.
ctx.mat = ctx.mat * scale(vec2(x, y))
diff --git a/src/pixie/demo.nim b/src/pixie/demo.nim
index 284e947..7520da8 100644
--- a/src/pixie/demo.nim
+++ b/src/pixie/demo.nim
@@ -1,7 +1,6 @@
-import opengl, pixie, pixie/context
-import staticglfw except Image
-export pixie
-export staticglfw except Image
+import opengl, pixie, pixie/context, staticglfw except Image
+
+export pixie, staticglfw except Image
var
dpi: float32 = 1.0
diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim
index c460ed5..35e0b00 100644
--- a/src/pixie/fileformats/svg.nim
+++ b/src/pixie/fileformats/svg.nim
@@ -1,7 +1,7 @@
## Load SVG files.
-import chroma, pixie/common, pixie/images, pixie/paths, pixie/paints, strutils, vmath,
- xmlparser, xmltree
+import chroma, pixie/common, pixie/images, pixie/paints, pixie/paths, strutils,
+ vmath, xmlparser, xmltree
const
xmlSignature* = "
@@ -232,9 +268,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
ctx = decodeCtx(ctxStack[^1], node)
path = parsePath(d)
if ctx.fill != ColorRGBX():
- img.fillPath(path, ctx.fill, ctx.transform, ctx.fillRule)
+ img.fill(ctx, path)
if ctx.shouldStroke:
- img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
+ img.stroke(ctx, path)
of "line":
let
@@ -247,12 +283,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
var path: Path
path.moveTo(x1, y1)
path.lineTo(x2, y2)
- path.closePath()
- if ctx.fill != ColorRGBX():
- img.fillPath(path, ctx.fill, ctx.transform)
if ctx.shouldStroke:
- img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
+ img.stroke(ctx, path)
of "polyline", "polygon":
let
@@ -282,13 +315,15 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
path.lineTo(vecs[i])
# The difference between polyline and polygon is whether we close the path
+ # and fill or not
if node.tag == "polygon":
path.closePath()
- if ctx.fill != ColorRGBX():
- img.fillPath(path, ctx.fill, ctx.transform)
+ if ctx.fill != ColorRGBX():
+ img.fill(ctx, path)
+
if ctx.shouldStroke:
- img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
+ img.stroke(ctx, path)
of "rect":
let
@@ -324,9 +359,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
path.rect(x, y, width, height)
if ctx.fill != ColorRGBX():
- img.fillPath(path, ctx.fill, ctx.transform)
+ img.fill(ctx, path)
if ctx.shouldStroke:
- img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
+ img.stroke(ctx, path)
of "circle", "ellipse":
let
@@ -346,9 +381,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
path.ellipse(cx, cy, rx, ry)
if ctx.fill != ColorRGBX():
- img.fillPath(path, ctx.fill, ctx.transform)
+ img.fill(ctx, path)
if ctx.shouldStroke:
- img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
+ img.stroke(ctx, path)
else:
raise newException(PixieError, "Unsupported SVG tag: " & node.tag & ".")
diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim
index 02a2254..4159620 100644
--- a/src/pixie/fonts.nim
+++ b/src/pixie/fonts.nim
@@ -442,7 +442,9 @@ proc strokeText*(
transform: Vec2 | Mat3 = vec2(0, 0),
strokeWidth = 1.0,
lineCap = lcButt,
- lineJoin = ljMiter
+ lineJoin = ljMiter,
+ miterLimit = defaultMiterLimit,
+ dashes: seq[float32] = @[]
) =
## Strokes the text arrangement.
for spanIndex, (start, stop) in arrangement.spans:
@@ -455,10 +457,25 @@ proc strokeText*(
)
when type(target) is Image:
target.strokePath(
- path, font.paint, transform, strokeWidth, lineCap, lineJoin
+ path,
+ font.paint,
+ transform,
+ strokeWidth,
+ lineCap,
+ lineJoin,
+ miterLimit,
+ dashes
)
else: # target is Mask
- target.strokePath(path, transform, strokeWidth, lineCap, lineJoin)
+ target.strokePath(
+ path,
+ transform,
+ strokeWidth,
+ lineCap,
+ lineJoin,
+ miterLimit,
+ dashes
+ )
proc strokeText*(
target: Image | Mask,
@@ -470,7 +487,9 @@ proc strokeText*(
hAlign = haLeft,
vAlign = vaTop,
lineCap = lcButt,
- lineJoin = ljMiter
+ lineJoin = ljMiter,
+ miterLimit = defaultMiterLimit,
+ dashes: seq[float32] = @[]
) {.inline.} =
## Typesets and strokes the text. Optional parameters:
## transform: translation or matrix to apply
@@ -485,5 +504,7 @@ proc strokeText*(
transform,
strokeWidth,
lineCap,
- lineJoin
+ lineJoin,
+ miterLimit,
+ dashes
)
diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim
index af95fd6..f027f84 100644
--- a/src/pixie/paths.nim
+++ b/src/pixie/paths.nim
@@ -36,7 +36,9 @@ type
SomePath* = Path | string | seq[seq[Vec2]]
-const epsilon = 0.0001 * PI ## Tiny value used for some computations.
+const
+ epsilon = 0.0001 * PI ## Tiny value used for some computations.
+ defaultMiterLimit*: float32 = 4
when defined(release):
{.push checks: off.}
@@ -1422,15 +1424,16 @@ proc strokeShapes(
shape[0]
))
+ var dashes = dashes
+ if dashes.len mod 2 != 0:
+ dashes.add(dashes)
+
for i in 1 ..< shape.len:
let
pos = shape[i]
prevPos = shape[i - 1]
if dashes.len > 0:
- var dashes = dashes
- if dashes.len mod 2 != 0:
- dashes.add(dashes[^1])
var distance = dist(prevPos, pos)
let dir = dir(pos, prevPos)
var currPos = prevPos
@@ -1546,8 +1549,8 @@ proc strokePath*(
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter,
- miterLimit: float32 = 4,
- dashes: seq[float32] = @[],
+ miterLimit = defaultMiterLimit,
+ dashes: seq[float32] = @[]
) =
## Strokes a path.
var strokeShapes = strokeShapes(
@@ -1569,7 +1572,7 @@ proc strokePath*(
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter,
- miterLimit: float32 = 4,
+ miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[]
) =
## Strokes a path.
diff --git a/tests/images/context/setLineDash_1.png b/tests/images/context/setLineDash_1.png
new file mode 100644
index 0000000..e57dbf8
Binary files /dev/null and b/tests/images/context/setLineDash_1.png differ
diff --git a/tests/images/paths/dashes.png b/tests/images/paths/dashes.png
index 5389bd8..3681bb0 100644
Binary files a/tests/images/paths/dashes.png and b/tests/images/paths/dashes.png differ
diff --git a/tests/images/svg/dashes.png b/tests/images/svg/dashes.png
new file mode 100644
index 0000000..3e1f667
Binary files /dev/null and b/tests/images/svg/dashes.png differ
diff --git a/tests/images/svg/dashes.svg b/tests/images/svg/dashes.svg
new file mode 100644
index 0000000..cb340e7
--- /dev/null
+++ b/tests/images/svg/dashes.svg
@@ -0,0 +1,20 @@
+
diff --git a/tests/images/svg/flat-color-icons.png b/tests/images/svg/flat-color-icons.png
index 661bd07..1af1b33 100644
Binary files a/tests/images/svg/flat-color-icons.png and b/tests/images/svg/flat-color-icons.png differ
diff --git a/tests/images/svg/ionicons.png b/tests/images/svg/ionicons.png
index 2b2838a..7d74d34 100644
Binary files a/tests/images/svg/ionicons.png and b/tests/images/svg/ionicons.png differ
diff --git a/tests/images/svg/miterlimit.png b/tests/images/svg/miterlimit.png
new file mode 100644
index 0000000..fde660e
Binary files /dev/null and b/tests/images/svg/miterlimit.png differ
diff --git a/tests/images/svg/miterlimit.svg b/tests/images/svg/miterlimit.svg
new file mode 100644
index 0000000..82b9a32
--- /dev/null
+++ b/tests/images/svg/miterlimit.svg
@@ -0,0 +1,27 @@
+
diff --git a/tests/test_context.nim b/tests/test_context.nim
index 8c1f499..b3d3152 100644
--- a/tests/test_context.nim
+++ b/tests/test_context.nim
@@ -408,7 +408,6 @@ block:
ctx.image.writeFile("tests/images/context/clip_1e.png")
-
block:
let ctx = newContext(newImage(300, 150))
@@ -491,3 +490,28 @@ block:
let metrics = ctx.measureText("Hello world")
doAssert metrics.width == 61
+
+block:
+ let
+ image = newImage(300, 150)
+ ctx = newContext(image)
+
+ var y = 15.float32
+
+ proc drawDashedLine(pattern: seq[float32]) =
+ ctx.beginPath();
+ ctx.setLineDash(pattern);
+ ctx.moveTo(0, y);
+ ctx.lineTo(300, y);
+ ctx.stroke();
+ y += 20;
+
+ drawDashedLine(@[]);
+ drawDashedLine(@[1.float32, 1]);
+ drawDashedLine(@[10.float32, 10]);
+ drawDashedLine(@[20.float32, 5]);
+ drawDashedLine(@[15.float32, 3, 3, 3]);
+ drawDashedLine(@[20.float32, 3, 3, 3, 3, 3, 3, 3]);
+ drawDashedLine(@[12.float32, 3, 3]);
+
+ image.writeFile("tests/images/context/setLineDash_1.png")
diff --git a/tests/test_paths.nim b/tests/test_paths.nim
index 93d2d8e..8d2f96e 100644
--- a/tests/test_paths.nim
+++ b/tests/test_paths.nim
@@ -47,7 +47,7 @@ block:
image = newImage(100, 100)
pathStr = "M 10 10 L 90 90"
color = rgba(255, 0, 0, 255)
- image.strokePath(pathStr, color, strokeWidth=10)
+ image.strokePath(pathStr, color, strokeWidth = 10)
image.writeFile("tests/images/paths/pathStroke1.png")
block:
@@ -55,7 +55,7 @@ block:
image = newImage(100, 100)
pathStr = "M 10 10 L 50 60 90 90"
color = rgba(255, 0, 0, 255)
- image.strokePath(pathStr, color, strokeWidth=10)
+ image.strokePath(pathStr, color, strokeWidth = 10)
image.writeFile("tests/images/paths/pathStroke2.png")
block:
@@ -268,12 +268,12 @@ block:
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 25), 10, lcButt, ljBevel,
- dashes = @[2.float32,2]
+ dashes = @[2.float32, 2]
)
image.strokePath(
path, rgba(0, 0, 0, 255), vec2(5, 45), 10, lcButt, ljBevel,
- dashes = @[4.float32,4]
+ dashes = @[4.float32, 4]
)
image.strokePath(
diff --git a/tests/test_svg.nim b/tests/test_svg.nim
index 8acb6d8..ac620aa 100644
--- a/tests/test_svg.nim
+++ b/tests/test_svg.nim
@@ -11,7 +11,9 @@ const files = [
"triangle01",
"quad01",
"Ghostscript_Tiger",
- "scale"
+ "scale",
+ "miterlimit",
+ "dashes"
]
for file in files: