diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index 8a238c8..24f5388 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -14,6 +14,7 @@ type Ctx = object strokeLineCap: LineCap strokeLineJoin: LineJoin transform: Mat3 + shouldStroke: bool template failInvalid() = raise newException(PixieError, "Invalid SVG data") @@ -24,7 +25,8 @@ proc attrOrDefault(node: XmlNode, name, default: string): string = result = default proc initCtx(): Ctx = - result.fill = parseHtmlColor("black").rgba.toPremultipliedAlpha() + result.fill = parseHtmlColor("black").rgba + result.stroke = parseHtmlColor("black").rgba result.strokeWidth = 1 result.transform = mat3() @@ -81,12 +83,15 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = else: result.fill = parseHtmlColor(fill).rgba.toPremultipliedAlpha() - if stroke == "" or fill == "currentColor": + if stroke == "": discard # Inherit + elif stroke == "currentColor": + result.shouldStroke = true elif stroke == "none": result.stroke = ColorRGBA() else: result.stroke = parseHtmlColor(stroke).rgba.toPremultipliedAlpha() + result.shouldStroke = true if strokeWidth == "": discard # Inherit @@ -94,6 +99,10 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = if strokeWidth.endsWith("px"): strokeWidth = strokeWidth[0 .. ^3] result.strokeWidth = parseFloat(strokeWidth) + result.shouldStroke = true + + if result.stroke == ColorRGBA() or result.strokeWidth <= 0: + result.shouldStroke = false if strokeLineCap == "": discard # Inherit @@ -165,14 +174,20 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = let components = f[10 .. ^2].split(" ") tx = parseFloat(components[0]) - ty = parseFloat(components[1]) + ty = if components[1].len == 0: 0.0 else: parseFloat(components[1]) result.transform = result.transform * translate(vec2(tx, ty)) elif f.startsWith("rotate("): - # let - # values = f[7 .. ^2].split(" ") - # angle = parseFloat(values[0]) * -PI / 180 - let angle = parseFloat(f[7 .. ^2]) * -PI / 180 - result.transform = result.transform * rotationMat3(angle) + let + values = f[7 .. ^2].split(" ") + angle = parseFloat(values[0]) * -PI / 180 + var cx, cy: float32 + if values.len > 1: + cx = parseFloat(values[1]) + if values.len > 2: + cy = parseFloat(values[2]) + let center = vec2(cx, cy) + result.transform = result.transform * + translate(center) * rotationMat3(angle) * translate(-center) else: failInvalidTransform(transform) @@ -199,7 +214,7 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = path = parsePath(d) if ctx.fill != ColorRGBA(): img.fillPath(path, ctx.fill, ctx.transform, ctx.fillRule) - if ctx.stroke != ColorRGBA() and ctx.strokeWidth > 0: + if ctx.shouldStroke: img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth) of "line": @@ -217,7 +232,7 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = if ctx.fill != ColorRGBA(): img.fillPath(path, ctx.fill, ctx.transform) - if ctx.stroke != ColorRGBA() and ctx.strokeWidth > 0: + if ctx.shouldStroke: img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth) of "polyline", "polygon": @@ -253,7 +268,7 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = if ctx.fill != ColorRGBA(): img.fillPath(path, ctx.fill, ctx.transform) - if ctx.stroke != ColorRGBA() and ctx.strokeWidth > 0: + if ctx.shouldStroke: img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth) of "rect": @@ -291,7 +306,7 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = if ctx.fill != ColorRGBA(): img.fillPath(path, ctx.fill, ctx.transform) - if ctx.stroke != ColorRGBA() and ctx.strokeWidth > 0: + if ctx.shouldStroke: img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth) of "circle", "ellipse": @@ -313,7 +328,7 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = if ctx.fill != ColorRGBA(): img.fillPath(path, ctx.fill, ctx.transform) - if ctx.stroke != ColorRGBA() and ctx.strokeWidth > 0: + if ctx.shouldStroke: img.strokePath(path, ctx.stroke, ctx.transform, ctx.strokeWidth) else: @@ -329,15 +344,11 @@ proc decodeSvg*(data: string, width = 0, height = 0): Image = let viewBox = root.attr("viewBox") box = viewBox.split(" ") - - # if parseInt(box[0]) != 0 or parseInt(box[1]) != 0: - # failInvalid() - - let viewBoxWidth = parseInt(box[2]) viewBoxHeight = parseInt(box[3]) var rootCtx = initCtx() + rootCtx = decodeCtx(rootCtx, root) if width == 0 and height == 0: # Default to the view box size result = newImage(viewBoxWidth, viewBoxHeight) else: diff --git a/tests/images/svg/tabler-icons.png b/tests/images/svg/tabler-icons.png new file mode 100644 index 0000000..a9bc3ec Binary files /dev/null and b/tests/images/svg/tabler-icons.png differ diff --git a/tests/megatest.nim b/tests/megatest.nim index 992140d..3084917 100644 --- a/tests/megatest.nim +++ b/tests/megatest.nim @@ -24,7 +24,7 @@ const IconSet(name: "twbs-icons", path: "../icons/icons/*"), IconSet(name: "flat-color-icons", path: "../flat-color-icons/svg/*"), IconSet(name: "ionicons", path: "../ionicons/src/svg/*"), - # IconSet(name: "tabler-icons", path: "../tabler-icons/icons/*"), + IconSet(name: "tabler-icons", path: "../tabler-icons/icons/*"), IconSet(name: "simple-icons", path: "../simple-icons/icons/*") ] width = 32