Merge pull request #204 from guzba/master
context and svg: miterLimit, line dashes
|
@ -1,8 +1,7 @@
|
||||||
import bumpy, chroma, flatty/binny, os, pixie/blends, pixie/common,
|
import bumpy, chroma, flatty/binny, os, pixie/blends, pixie/common,
|
||||||
pixie/context, pixie/fileformats/bmp, pixie/fileformats/gif,
|
pixie/context, pixie/fileformats/bmp, pixie/fileformats/gif,
|
||||||
pixie/fileformats/jpg, pixie/fileformats/png, pixie/fileformats/svg,
|
pixie/fileformats/jpg, pixie/fileformats/png, pixie/fileformats/svg,
|
||||||
pixie/fonts, pixie/images, pixie/masks, pixie/paints, pixie/paths, strutils,
|
pixie/fonts, pixie/images, pixie/masks, pixie/paints, pixie/paths, strutils, vmath
|
||||||
vmath
|
|
||||||
|
|
||||||
export blends, bumpy, chroma, common, context, fonts, images, masks, paints,
|
export blends, bumpy, chroma, common, context, fonts, images, masks, paints,
|
||||||
paths, vmath
|
paths, vmath
|
||||||
|
|
|
@ -11,11 +11,13 @@ type
|
||||||
image*: Image
|
image*: Image
|
||||||
fillStyle*, strokeStyle*: Paint
|
fillStyle*, strokeStyle*: Paint
|
||||||
lineWidth*: float32
|
lineWidth*: float32
|
||||||
|
miterLimit*: float32
|
||||||
lineCap*: LineCap
|
lineCap*: LineCap
|
||||||
lineJoin*: LineJoin
|
lineJoin*: LineJoin
|
||||||
font*: Font
|
font*: Font
|
||||||
textAlign*: HAlignMode
|
textAlign*: HAlignMode
|
||||||
path: Path
|
path: Path
|
||||||
|
lineDash: seq[float32]
|
||||||
mat: Mat3
|
mat: Mat3
|
||||||
mask: Mask
|
mask: Mask
|
||||||
layer: Image
|
layer: Image
|
||||||
|
@ -24,10 +26,12 @@ type
|
||||||
ContextState = object
|
ContextState = object
|
||||||
fillStyle, strokeStyle: Paint
|
fillStyle, strokeStyle: Paint
|
||||||
lineWidth: float32
|
lineWidth: float32
|
||||||
|
miterLimit: float32
|
||||||
lineCap: LineCap
|
lineCap: LineCap
|
||||||
lineJoin: LineJoin
|
lineJoin: LineJoin
|
||||||
font: Font
|
font: Font
|
||||||
textAlign: HAlignMode
|
textAlign: HAlignMode
|
||||||
|
lineDash: seq[float32]
|
||||||
mat: Mat3
|
mat: Mat3
|
||||||
mask: Mask
|
mask: Mask
|
||||||
layer: Image
|
layer: Image
|
||||||
|
@ -41,6 +45,7 @@ proc newContext*(image: Image): Context =
|
||||||
result.image = image
|
result.image = image
|
||||||
result.mat = mat3()
|
result.mat = mat3()
|
||||||
result.lineWidth = 1
|
result.lineWidth = 1
|
||||||
|
result.miterLimit = 10
|
||||||
result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
|
result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
|
||||||
result.strokeStyle = 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.fillStyle = ctx.fillStyle
|
||||||
result.strokeStyle = ctx.strokeStyle
|
result.strokeStyle = ctx.strokeStyle
|
||||||
result.lineWidth = ctx.lineWidth
|
result.lineWidth = ctx.lineWidth
|
||||||
|
result.miterLimit = ctx.miterLimit
|
||||||
result.lineCap = ctx.lineCap
|
result.lineCap = ctx.lineCap
|
||||||
result.lineJoin = ctx.lineJoin
|
result.lineJoin = ctx.lineJoin
|
||||||
result.font = ctx.font
|
result.font = ctx.font
|
||||||
result.textAlign = ctx.textAlign
|
result.textAlign = ctx.textAlign
|
||||||
|
result.lineDash = ctx.lineDash
|
||||||
result.mat = ctx.mat
|
result.mat = ctx.mat
|
||||||
result.mask = if ctx.mask != nil: ctx.mask.copy() else: nil
|
result.mask = if ctx.mask != nil: ctx.mask.copy() else: nil
|
||||||
|
|
||||||
proc save*(ctx: Context) {.inline.} =
|
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.
|
## a stack.
|
||||||
ctx.stateStack.add(ctx.state())
|
ctx.stateStack.add(ctx.state())
|
||||||
|
|
||||||
proc saveLayer*(ctx: Context) =
|
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()
|
var state = ctx.state()
|
||||||
state.layer = ctx.layer
|
state.layer = ctx.layer
|
||||||
ctx.stateStack.add(state)
|
ctx.stateStack.add(state)
|
||||||
ctx.layer = newImage(ctx.image.width, ctx.image.height)
|
ctx.layer = newImage(ctx.image.width, ctx.image.height)
|
||||||
|
|
||||||
proc restore*(ctx: Context) =
|
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
|
## in the drawing state stack. If there is no saved state, this method does
|
||||||
## nothing.
|
## nothing.
|
||||||
if ctx.stateStack.len == 0:
|
if ctx.stateStack.len == 0:
|
||||||
|
@ -85,10 +95,12 @@ proc restore*(ctx: Context) =
|
||||||
ctx.fillStyle = state.fillStyle
|
ctx.fillStyle = state.fillStyle
|
||||||
ctx.strokeStyle = state.strokeStyle
|
ctx.strokeStyle = state.strokeStyle
|
||||||
ctx.lineWidth = state.lineWidth
|
ctx.lineWidth = state.lineWidth
|
||||||
|
ctx.miterLimit = state.miterLimit
|
||||||
ctx.lineCap = state.lineCap
|
ctx.lineCap = state.lineCap
|
||||||
ctx.lineJoin = state.lineJoin
|
ctx.lineJoin = state.lineJoin
|
||||||
ctx.font = state.font
|
ctx.font = state.font
|
||||||
ctx.textAlign = state.textAlign
|
ctx.textAlign = state.textAlign
|
||||||
|
ctx.lineDash = state.lineDash
|
||||||
ctx.mat = state.mat
|
ctx.mat = state.mat
|
||||||
ctx.mask = state.mask
|
ctx.mask = state.mask
|
||||||
ctx.layer = state.layer
|
ctx.layer = state.layer
|
||||||
|
@ -118,7 +130,9 @@ proc stroke(ctx: Context, image: Image, path: Path) {.inline.} =
|
||||||
ctx.mat,
|
ctx.mat,
|
||||||
ctx.lineWidth,
|
ctx.lineWidth,
|
||||||
ctx.lineCap,
|
ctx.lineCap,
|
||||||
ctx.lineJoin
|
ctx.lineJoin,
|
||||||
|
ctx.miterLimit,
|
||||||
|
ctx.lineDash
|
||||||
)
|
)
|
||||||
|
|
||||||
proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
|
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,
|
ctx.lineWidth,
|
||||||
hAlign = ctx.textAlign,
|
hAlign = ctx.textAlign,
|
||||||
lineCap = ctx.lineCap,
|
lineCap = ctx.lineCap,
|
||||||
lineJoin = ctx.lineJoin
|
lineJoin = ctx.lineJoin,
|
||||||
|
miterLimit = ctx.miterLimit,
|
||||||
|
dashes = ctx.lineDash
|
||||||
)
|
)
|
||||||
|
|
||||||
proc beginPath*(ctx: Context) {.inline.} =
|
proc beginPath*(ctx: Context) {.inline.} =
|
||||||
|
@ -289,13 +305,13 @@ proc clearRect*(ctx: Context, rect: Rect) =
|
||||||
if ctx.layer != nil:
|
if ctx.layer != nil:
|
||||||
ctx.layer.fillPath(
|
ctx.layer.fillPath(
|
||||||
path,
|
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
|
ctx.mat
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ctx.image.fillPath(
|
ctx.image.fillPath(
|
||||||
path,
|
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
|
ctx.mat
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -368,6 +384,12 @@ proc measureText*(ctx: Context, text: string): TextMetrics =
|
||||||
let bounds = typeset(ctx.font, text).computeBounds()
|
let bounds = typeset(ctx.font, text).computeBounds()
|
||||||
result.width = bounds.x
|
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.} =
|
proc getTransform*(ctx: Context): Mat3 {.inline.} =
|
||||||
## Retrieves the current transform matrix being applied to the context.
|
## Retrieves the current transform matrix being applied to the context.
|
||||||
ctx.mat
|
ctx.mat
|
||||||
|
@ -399,12 +421,12 @@ proc translate*(ctx: Context, x, y: float32) {.inline.} =
|
||||||
ctx.mat = ctx.mat * translate(vec2(x, y))
|
ctx.mat = ctx.mat * translate(vec2(x, y))
|
||||||
|
|
||||||
proc scale*(ctx: Context, v: Vec2) {.inline.} =
|
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.
|
## vertically.
|
||||||
ctx.mat = ctx.mat * scale(v)
|
ctx.mat = ctx.mat * scale(v)
|
||||||
|
|
||||||
proc scale*(ctx: Context, x, y: float32) {.inline.} =
|
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.
|
## vertically.
|
||||||
ctx.mat = ctx.mat * scale(vec2(x, y))
|
ctx.mat = ctx.mat * scale(vec2(x, y))
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import opengl, pixie, pixie/context
|
import opengl, pixie, pixie/context, staticglfw except Image
|
||||||
import staticglfw except Image
|
|
||||||
export pixie
|
export pixie, staticglfw except Image
|
||||||
export staticglfw except Image
|
|
||||||
|
|
||||||
var
|
var
|
||||||
dpi: float32 = 1.0
|
dpi: float32 = 1.0
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
## Load SVG files.
|
## Load SVG files.
|
||||||
|
|
||||||
import chroma, pixie/common, pixie/images, pixie/paths, pixie/paints, strutils, vmath,
|
import chroma, pixie/common, pixie/images, pixie/paints, pixie/paths, strutils,
|
||||||
xmlparser, xmltree
|
vmath, xmlparser, xmltree
|
||||||
|
|
||||||
const
|
const
|
||||||
xmlSignature* = "<?xml"
|
xmlSignature* = "<?xml"
|
||||||
|
@ -13,6 +13,8 @@ type Ctx = object
|
||||||
strokeWidth: float32
|
strokeWidth: float32
|
||||||
strokeLineCap: LineCap
|
strokeLineCap: LineCap
|
||||||
strokeLineJoin: LineJoin
|
strokeLineJoin: LineJoin
|
||||||
|
strokeMiterLimit: float32
|
||||||
|
strokeDashArray: seq[float32]
|
||||||
transform: Mat3
|
transform: Mat3
|
||||||
shouldStroke: bool
|
shouldStroke: bool
|
||||||
|
|
||||||
|
@ -29,6 +31,7 @@ proc initCtx(): Ctx =
|
||||||
result.stroke = parseHtmlColor("black").rgbx
|
result.stroke = parseHtmlColor("black").rgbx
|
||||||
result.strokeWidth = 1
|
result.strokeWidth = 1
|
||||||
result.transform = mat3()
|
result.transform = mat3()
|
||||||
|
result.strokeMiterLimit = defaultMiterLimit
|
||||||
|
|
||||||
proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||||
result = inherited
|
result = inherited
|
||||||
|
@ -40,6 +43,8 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||||
strokeWidth = node.attr("stroke-width")
|
strokeWidth = node.attr("stroke-width")
|
||||||
strokeLineCap = node.attr("stroke-linecap")
|
strokeLineCap = node.attr("stroke-linecap")
|
||||||
strokeLineJoin = node.attr("stroke-linejoin")
|
strokeLineJoin = node.attr("stroke-linejoin")
|
||||||
|
strokeMiterLimit = node.attr("stroke-miterlimit")
|
||||||
|
strokeDashArray = node.attr("stroke-dasharray")
|
||||||
transform = node.attr("transform")
|
transform = node.attr("transform")
|
||||||
style = node.attr("style")
|
style = node.attr("style")
|
||||||
|
|
||||||
|
@ -64,6 +69,12 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||||
of "stroke-width":
|
of "stroke-width":
|
||||||
if strokeWidth.len == 0:
|
if strokeWidth.len == 0:
|
||||||
strokeWidth = parts[1].strip()
|
strokeWidth = parts[1].strip()
|
||||||
|
of "stroke-miterlimit":
|
||||||
|
if strokeMiterLimit.len == 0:
|
||||||
|
strokeMiterLimit = parts[1].strip()
|
||||||
|
of "stroke-dasharray":
|
||||||
|
if strokeDashArray.len == 0:
|
||||||
|
strokeDashArray = parts[1].strip()
|
||||||
|
|
||||||
if fillRule == "":
|
if fillRule == "":
|
||||||
discard # Inherit
|
discard # Inherit
|
||||||
|
@ -138,6 +149,18 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||||
PixieError, "Invalid stroke-linejoin value " & strokeLineJoin
|
PixieError, "Invalid stroke-linejoin value " & strokeLineJoin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if strokeMiterLimit == "":
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
result.strokeMiterLimit = parseFloat(strokeMiterLimit)
|
||||||
|
|
||||||
|
if strokeDashArray == "":
|
||||||
|
discard
|
||||||
|
else:
|
||||||
|
var values = strokeDashArray.replace(',', ' ').split(' ')
|
||||||
|
for value in values:
|
||||||
|
result.strokeDashArray.add(parseFloat(value))
|
||||||
|
|
||||||
if transform == "":
|
if transform == "":
|
||||||
discard # Inherit
|
discard # Inherit
|
||||||
else:
|
else:
|
||||||
|
@ -210,6 +233,19 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx =
|
||||||
else:
|
else:
|
||||||
failInvalidTransform(transform)
|
failInvalidTransform(transform)
|
||||||
|
|
||||||
|
proc fill(img: Image, ctx: Ctx, path: Path) {.inline.} =
|
||||||
|
img.fillPath(path, ctx.fill, ctx.transform, ctx.fillRule)
|
||||||
|
|
||||||
|
proc stroke(img: Image, ctx: Ctx, path: Path) {.inline.} =
|
||||||
|
img.strokePath(
|
||||||
|
path,
|
||||||
|
ctx.stroke,
|
||||||
|
ctx.transform,
|
||||||
|
ctx.strokeWidth,
|
||||||
|
miterLimit = ctx.strokeMiterLimit,
|
||||||
|
dashes = ctx.strokeDashArray
|
||||||
|
)
|
||||||
|
|
||||||
proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||||
if node.kind != xnElement:
|
if node.kind != xnElement:
|
||||||
# Skip <!-- comments -->
|
# Skip <!-- comments -->
|
||||||
|
@ -232,9 +268,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||||
ctx = decodeCtx(ctxStack[^1], node)
|
ctx = decodeCtx(ctxStack[^1], node)
|
||||||
path = parsePath(d)
|
path = parsePath(d)
|
||||||
if ctx.fill != ColorRGBX():
|
if ctx.fill != ColorRGBX():
|
||||||
img.fillPath(path, ctx.fill, ctx.transform, ctx.fillRule)
|
img.fill(ctx, path)
|
||||||
if ctx.shouldStroke:
|
if ctx.shouldStroke:
|
||||||
img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
|
img.stroke(ctx, path)
|
||||||
|
|
||||||
of "line":
|
of "line":
|
||||||
let
|
let
|
||||||
|
@ -247,12 +283,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||||
var path: Path
|
var path: Path
|
||||||
path.moveTo(x1, y1)
|
path.moveTo(x1, y1)
|
||||||
path.lineTo(x2, y2)
|
path.lineTo(x2, y2)
|
||||||
path.closePath()
|
|
||||||
|
|
||||||
if ctx.fill != ColorRGBX():
|
|
||||||
img.fillPath(path, ctx.fill, ctx.transform)
|
|
||||||
if ctx.shouldStroke:
|
if ctx.shouldStroke:
|
||||||
img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
|
img.stroke(ctx, path)
|
||||||
|
|
||||||
of "polyline", "polygon":
|
of "polyline", "polygon":
|
||||||
let
|
let
|
||||||
|
@ -282,13 +315,15 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||||
path.lineTo(vecs[i])
|
path.lineTo(vecs[i])
|
||||||
|
|
||||||
# The difference between polyline and polygon is whether we close the path
|
# The difference between polyline and polygon is whether we close the path
|
||||||
|
# and fill or not
|
||||||
if node.tag == "polygon":
|
if node.tag == "polygon":
|
||||||
path.closePath()
|
path.closePath()
|
||||||
|
|
||||||
if ctx.fill != ColorRGBX():
|
if ctx.fill != ColorRGBX():
|
||||||
img.fillPath(path, ctx.fill, ctx.transform)
|
img.fill(ctx, path)
|
||||||
|
|
||||||
if ctx.shouldStroke:
|
if ctx.shouldStroke:
|
||||||
img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
|
img.stroke(ctx, path)
|
||||||
|
|
||||||
of "rect":
|
of "rect":
|
||||||
let
|
let
|
||||||
|
@ -324,9 +359,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||||
path.rect(x, y, width, height)
|
path.rect(x, y, width, height)
|
||||||
|
|
||||||
if ctx.fill != ColorRGBX():
|
if ctx.fill != ColorRGBX():
|
||||||
img.fillPath(path, ctx.fill, ctx.transform)
|
img.fill(ctx, path)
|
||||||
if ctx.shouldStroke:
|
if ctx.shouldStroke:
|
||||||
img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
|
img.stroke(ctx, path)
|
||||||
|
|
||||||
of "circle", "ellipse":
|
of "circle", "ellipse":
|
||||||
let
|
let
|
||||||
|
@ -346,9 +381,9 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) =
|
||||||
path.ellipse(cx, cy, rx, ry)
|
path.ellipse(cx, cy, rx, ry)
|
||||||
|
|
||||||
if ctx.fill != ColorRGBX():
|
if ctx.fill != ColorRGBX():
|
||||||
img.fillPath(path, ctx.fill, ctx.transform)
|
img.fill(ctx, path)
|
||||||
if ctx.shouldStroke:
|
if ctx.shouldStroke:
|
||||||
img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth)
|
img.stroke(ctx, path)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise newException(PixieError, "Unsupported SVG tag: " & node.tag & ".")
|
raise newException(PixieError, "Unsupported SVG tag: " & node.tag & ".")
|
||||||
|
|
|
@ -442,7 +442,9 @@ proc strokeText*(
|
||||||
transform: Vec2 | Mat3 = vec2(0, 0),
|
transform: Vec2 | Mat3 = vec2(0, 0),
|
||||||
strokeWidth = 1.0,
|
strokeWidth = 1.0,
|
||||||
lineCap = lcButt,
|
lineCap = lcButt,
|
||||||
lineJoin = ljMiter
|
lineJoin = ljMiter,
|
||||||
|
miterLimit = defaultMiterLimit,
|
||||||
|
dashes: seq[float32] = @[]
|
||||||
) =
|
) =
|
||||||
## Strokes the text arrangement.
|
## Strokes the text arrangement.
|
||||||
for spanIndex, (start, stop) in arrangement.spans:
|
for spanIndex, (start, stop) in arrangement.spans:
|
||||||
|
@ -455,10 +457,25 @@ proc strokeText*(
|
||||||
)
|
)
|
||||||
when type(target) is Image:
|
when type(target) is Image:
|
||||||
target.strokePath(
|
target.strokePath(
|
||||||
path, font.paint, transform, strokeWidth, lineCap, lineJoin
|
path,
|
||||||
|
font.paint,
|
||||||
|
transform,
|
||||||
|
strokeWidth,
|
||||||
|
lineCap,
|
||||||
|
lineJoin,
|
||||||
|
miterLimit,
|
||||||
|
dashes
|
||||||
)
|
)
|
||||||
else: # target is Mask
|
else: # target is Mask
|
||||||
target.strokePath(path, transform, strokeWidth, lineCap, lineJoin)
|
target.strokePath(
|
||||||
|
path,
|
||||||
|
transform,
|
||||||
|
strokeWidth,
|
||||||
|
lineCap,
|
||||||
|
lineJoin,
|
||||||
|
miterLimit,
|
||||||
|
dashes
|
||||||
|
)
|
||||||
|
|
||||||
proc strokeText*(
|
proc strokeText*(
|
||||||
target: Image | Mask,
|
target: Image | Mask,
|
||||||
|
@ -470,7 +487,9 @@ proc strokeText*(
|
||||||
hAlign = haLeft,
|
hAlign = haLeft,
|
||||||
vAlign = vaTop,
|
vAlign = vaTop,
|
||||||
lineCap = lcButt,
|
lineCap = lcButt,
|
||||||
lineJoin = ljMiter
|
lineJoin = ljMiter,
|
||||||
|
miterLimit = defaultMiterLimit,
|
||||||
|
dashes: seq[float32] = @[]
|
||||||
) {.inline.} =
|
) {.inline.} =
|
||||||
## Typesets and strokes the text. Optional parameters:
|
## Typesets and strokes the text. Optional parameters:
|
||||||
## transform: translation or matrix to apply
|
## transform: translation or matrix to apply
|
||||||
|
@ -485,5 +504,7 @@ proc strokeText*(
|
||||||
transform,
|
transform,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
lineCap,
|
lineCap,
|
||||||
lineJoin
|
lineJoin,
|
||||||
|
miterLimit,
|
||||||
|
dashes
|
||||||
)
|
)
|
||||||
|
|
|
@ -36,7 +36,9 @@ type
|
||||||
|
|
||||||
SomePath* = Path | string | seq[seq[Vec2]]
|
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):
|
when defined(release):
|
||||||
{.push checks: off.}
|
{.push checks: off.}
|
||||||
|
@ -1422,15 +1424,16 @@ proc strokeShapes(
|
||||||
shape[0]
|
shape[0]
|
||||||
))
|
))
|
||||||
|
|
||||||
|
var dashes = dashes
|
||||||
|
if dashes.len mod 2 != 0:
|
||||||
|
dashes.add(dashes)
|
||||||
|
|
||||||
for i in 1 ..< shape.len:
|
for i in 1 ..< shape.len:
|
||||||
let
|
let
|
||||||
pos = shape[i]
|
pos = shape[i]
|
||||||
prevPos = shape[i - 1]
|
prevPos = shape[i - 1]
|
||||||
|
|
||||||
if dashes.len > 0:
|
if dashes.len > 0:
|
||||||
var dashes = dashes
|
|
||||||
if dashes.len mod 2 != 0:
|
|
||||||
dashes.add(dashes[^1])
|
|
||||||
var distance = dist(prevPos, pos)
|
var distance = dist(prevPos, pos)
|
||||||
let dir = dir(pos, prevPos)
|
let dir = dir(pos, prevPos)
|
||||||
var currPos = prevPos
|
var currPos = prevPos
|
||||||
|
@ -1546,8 +1549,8 @@ proc strokePath*(
|
||||||
strokeWidth = 1.0,
|
strokeWidth = 1.0,
|
||||||
lineCap = lcButt,
|
lineCap = lcButt,
|
||||||
lineJoin = ljMiter,
|
lineJoin = ljMiter,
|
||||||
miterLimit: float32 = 4,
|
miterLimit = defaultMiterLimit,
|
||||||
dashes: seq[float32] = @[],
|
dashes: seq[float32] = @[]
|
||||||
) =
|
) =
|
||||||
## Strokes a path.
|
## Strokes a path.
|
||||||
var strokeShapes = strokeShapes(
|
var strokeShapes = strokeShapes(
|
||||||
|
@ -1569,7 +1572,7 @@ proc strokePath*(
|
||||||
strokeWidth = 1.0,
|
strokeWidth = 1.0,
|
||||||
lineCap = lcButt,
|
lineCap = lcButt,
|
||||||
lineJoin = ljMiter,
|
lineJoin = ljMiter,
|
||||||
miterLimit: float32 = 4,
|
miterLimit = defaultMiterLimit,
|
||||||
dashes: seq[float32] = @[]
|
dashes: seq[float32] = @[]
|
||||||
) =
|
) =
|
||||||
## Strokes a path.
|
## Strokes a path.
|
||||||
|
|
BIN
tests/images/context/setLineDash_1.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 824 B After Width: | Height: | Size: 837 B |
BIN
tests/images/svg/dashes.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
20
tests/images/svg/dashes.svg
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<svg viewBox="0 0 600 200" transform="scale(20, 20)" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- No dashes nor gaps -->
|
||||||
|
<line x1="0" y1="1" x2="30" y2="1" stroke="black" />
|
||||||
|
|
||||||
|
<!-- Dashes and gaps of the same size -->
|
||||||
|
<line x1="0" y1="3" x2="30" y2="3" stroke="black"
|
||||||
|
stroke-dasharray="4" />
|
||||||
|
|
||||||
|
<!-- Dashes and gaps of different sizes -->
|
||||||
|
<line x1="0" y1="5" x2="30" y2="5" stroke="black"
|
||||||
|
stroke-dasharray="4 1" />
|
||||||
|
|
||||||
|
<!-- Dashes and gaps of various sizes with an odd number of values -->
|
||||||
|
<line x1="0" y1="7" x2="30" y2="7" stroke="black"
|
||||||
|
stroke-dasharray="4 1 2" />
|
||||||
|
|
||||||
|
<!-- Dashes and gaps of various sizes with an even number of values -->
|
||||||
|
<line x1="0" y1="9" x2="30" y2="9" stroke="black"
|
||||||
|
stroke-dasharray="4 1 2 3" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 778 B |
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 280 KiB |
Before Width: | Height: | Size: 630 KiB After Width: | Height: | Size: 631 KiB |
BIN
tests/images/svg/miterlimit.png
Normal file
After Width: | Height: | Size: 40 KiB |
27
tests/images/svg/miterlimit.svg
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<svg viewBox="0 0 760 600" transform="scale(20, 20)" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<!-- Impact of the default miter limit -->
|
||||||
|
<path stroke="black" fill="none" stroke-linejoin="miter" id="p1" d="M1,9 l7 ,-3 l7 ,3
|
||||||
|
m2,0 l3.5 ,-3 l3.5 ,3
|
||||||
|
m2,0 l2 ,-3 l2 ,3
|
||||||
|
m2,0 l0.75,-3 l0.75,3
|
||||||
|
m2,0 l0.5 ,-3 l0.5 ,3"></path>
|
||||||
|
|
||||||
|
<!-- Impact of the smallest miter limit (1) -->
|
||||||
|
<path stroke="black" fill="none" stroke-linejoin="miter" stroke-miterlimit="1" id="p2" d="M1,19 l7 ,-3 l7 ,3
|
||||||
|
m2, 0 l3.5 ,-3 l3.5 ,3
|
||||||
|
m2, 0 l2 ,-3 l2 ,3
|
||||||
|
m2, 0 l0.75,-3 l0.75,3
|
||||||
|
m2, 0 l0.5 ,-3 l0.5 ,3"></path>
|
||||||
|
|
||||||
|
<!-- Impact of a large miter limit (8) -->
|
||||||
|
<path stroke="black" fill="none" stroke-linejoin="miter" stroke-miterlimit="8" id="p3" d="M1,29 l7 ,-3 l7 ,3
|
||||||
|
m2, 0 l3.5 ,-3 l3.5 ,3
|
||||||
|
m2, 0 l2 ,-3 l2 ,3
|
||||||
|
m2, 0 l0.75,-3 l0.75,3
|
||||||
|
m2, 0 l0.5 ,-3 l0.5 ,3"></path>
|
||||||
|
|
||||||
|
<!-- the following pink lines highlight the position of the path for each stroke -->
|
||||||
|
<path stroke="pink" fill="none" stroke-width="0.05" d="M1, 9 l7,-3 l7,3 m2,0 l3.5,-3 l3.5,3 m2,0 l2,-3 l2,3 m2,0 l0.75,-3 l0.75,3 m2,0 l0.5,-3 l0.5,3
|
||||||
|
M1,19 l7,-3 l7,3 m2,0 l3.5,-3 l3.5,3 m2,0 l2,-3 l2,3 m2,0 l0.75,-3 l0.75,3 m2,0 l0.5,-3 l0.5,3
|
||||||
|
M1,29 l7,-3 l7,3 m2,0 l3.5,-3 l3.5,3 m2,0 l2,-3 l2,3 m2,0 l0.75,-3 l0.75,3 m2,0 l0.5,-3 l0.5,3"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -408,7 +408,6 @@ block:
|
||||||
|
|
||||||
ctx.image.writeFile("tests/images/context/clip_1e.png")
|
ctx.image.writeFile("tests/images/context/clip_1e.png")
|
||||||
|
|
||||||
|
|
||||||
block:
|
block:
|
||||||
let ctx = newContext(newImage(300, 150))
|
let ctx = newContext(newImage(300, 150))
|
||||||
|
|
||||||
|
@ -491,3 +490,28 @@ block:
|
||||||
|
|
||||||
let metrics = ctx.measureText("Hello world")
|
let metrics = ctx.measureText("Hello world")
|
||||||
doAssert metrics.width == 61
|
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")
|
||||||
|
|
|
@ -47,7 +47,7 @@ block:
|
||||||
image = newImage(100, 100)
|
image = newImage(100, 100)
|
||||||
pathStr = "M 10 10 L 90 90"
|
pathStr = "M 10 10 L 90 90"
|
||||||
color = rgba(255, 0, 0, 255)
|
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")
|
image.writeFile("tests/images/paths/pathStroke1.png")
|
||||||
|
|
||||||
block:
|
block:
|
||||||
|
@ -55,7 +55,7 @@ block:
|
||||||
image = newImage(100, 100)
|
image = newImage(100, 100)
|
||||||
pathStr = "M 10 10 L 50 60 90 90"
|
pathStr = "M 10 10 L 50 60 90 90"
|
||||||
color = rgba(255, 0, 0, 255)
|
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")
|
image.writeFile("tests/images/paths/pathStroke2.png")
|
||||||
|
|
||||||
block:
|
block:
|
||||||
|
@ -268,12 +268,12 @@ block:
|
||||||
|
|
||||||
image.strokePath(
|
image.strokePath(
|
||||||
path, rgba(0, 0, 0, 255), vec2(5, 25), 10, lcButt, ljBevel,
|
path, rgba(0, 0, 0, 255), vec2(5, 25), 10, lcButt, ljBevel,
|
||||||
dashes = @[2.float32,2]
|
dashes = @[2.float32, 2]
|
||||||
)
|
)
|
||||||
|
|
||||||
image.strokePath(
|
image.strokePath(
|
||||||
path, rgba(0, 0, 0, 255), vec2(5, 45), 10, lcButt, ljBevel,
|
path, rgba(0, 0, 0, 255), vec2(5, 45), 10, lcButt, ljBevel,
|
||||||
dashes = @[4.float32,4]
|
dashes = @[4.float32, 4]
|
||||||
)
|
)
|
||||||
|
|
||||||
image.strokePath(
|
image.strokePath(
|
||||||
|
|
|
@ -11,7 +11,9 @@ const files = [
|
||||||
"triangle01",
|
"triangle01",
|
||||||
"quad01",
|
"quad01",
|
||||||
"Ghostscript_Tiger",
|
"Ghostscript_Tiger",
|
||||||
"scale"
|
"scale",
|
||||||
|
"miterlimit",
|
||||||
|
"dashes"
|
||||||
]
|
]
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
|
|