diff --git a/src/pixie/fileformats/svg.nim b/src/pixie/fileformats/svg.nim index 35e0b00..0592938 100644 --- a/src/pixie/fileformats/svg.nim +++ b/src/pixie/fileformats/svg.nim @@ -36,6 +36,13 @@ proc initCtx(): Ctx = proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = result = inherited + proc splitArgs(s: string): seq[string] = + # Handles (1,1) or (1 1) or (1, 1) or (1,1 2,2) etc + let tmp = s.replace(',', ' ').split(' ') + for entry in tmp: + if entry.len > 0: + result.add(entry) + var fillRule = node.attr("fill-rule") fill = node.attr("fill") @@ -157,7 +164,7 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = if strokeDashArray == "": discard else: - var values = strokeDashArray.replace(',', ' ').split(' ') + var values = splitArgs(strokeDashArray) for value in values: result.strokeDashArray.add(parseFloat(value)) @@ -178,55 +185,46 @@ proc decodeCtx(inherited: Ctx, node: XmlNode): Ctx = remaining = remaining[index + 1 .. ^1] if f.startsWith("matrix("): - let arr = - if f.contains(","): - f[7 .. ^2].split(",") - else: - f[7 .. ^2].split(" ") + let arr = splitArgs(f[7 .. ^2]) if arr.len != 6: failInvalidTransform(transform) var m = mat3() - m[0, 0] = parseFloat(arr[0].strip()) - m[0, 1] = parseFloat(arr[1].strip()) - m[1, 0] = parseFloat(arr[2].strip()) - m[1, 1] = parseFloat(arr[3].strip()) - m[2, 0] = parseFloat(arr[4].strip()) - m[2, 1] = parseFloat(arr[5].strip()) + m[0, 0] = parseFloat(arr[0]) + m[0, 1] = parseFloat(arr[1]) + m[1, 0] = parseFloat(arr[2]) + m[1, 1] = parseFloat(arr[3]) + m[2, 0] = parseFloat(arr[4]) + m[2, 1] = parseFloat(arr[5]) result.transform = result.transform * m elif f.startsWith("translate("): let - components = f[10 .. ^2].split(" ") - tx = parseFloat(components[0].strip()) + components = splitArgs(f[10 .. ^2]) + tx = parseFloat(components[0]) ty = - if components[1].len == 0: + if components.len == 1: 0.0 else: - parseFloat(components[1].strip()) + parseFloat(components[1]) result.transform = result.transform * translate(vec2(tx, ty)) elif f.startsWith("rotate("): let - values = f[7 .. ^2].split(" ") - angle: float32 = parseFloat(values[0].strip()) * -PI / 180 + values = splitArgs(f[7 .. ^2]) + angle: float32 = parseFloat(values[0]) * -PI / 180 var cx, cy: float32 if values.len > 1: - cx = parseFloat(values[1].strip()) + cx = parseFloat(values[1]) if values.len > 2: - cy = parseFloat(values[2].strip()) + cy = parseFloat(values[2]) let center = vec2(cx, cy) result.transform = result.transform * translate(center) * rotate(angle) * translate(-center) elif f.startsWith("scale("): let - values = - if f.contains(","): - f[6 .. ^2].split(",") - else: - f[6 .. ^2].split(" ") - let - sx: float32 = parseFloat(values[0].strip()) + values = splitArgs(f[6 .. ^2]) + sx: float32 = parseFloat(values[0]) sy: float32 = if values.len > 1: - parseFloat(values[1].strip()) + parseFloat(values[1]) else: sx result.transform = result.transform * scale(vec2(sx, sy)) @@ -242,6 +240,8 @@ proc stroke(img: Image, ctx: Ctx, path: Path) {.inline.} = ctx.stroke, ctx.transform, ctx.strokeWidth, + ctx.strokeLineCap, + ctx.strokeLineJoin, miterLimit = ctx.strokeMiterLimit, dashes = ctx.strokeDashArray ) @@ -330,8 +330,11 @@ proc draw(img: Image, node: XmlNode, ctxStack: var seq[Ctx]) = ctx = decodeCtx(ctxStack[^1], node) x = parseFloat(node.attrOrDefault("x", "0")) y = parseFloat(node.attrOrDefault("y", "0")) - width = parseFloat(node.attr("width")) - height = parseFloat(node.attr("height")) + width = parseFloat(node.attrOrDefault("width", "0")) + height = parseFloat(node.attrOrDefault("height", "0")) + + if width == 0 or height == 0: + return var rx = max(parseFloat(node.attrOrDefault("rx", "0")), 0) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index fe01f0b..70ce9fb 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -604,7 +604,7 @@ proc polygon*(path: var Path, pos: Vec2, size: float32, sides: int) {.inline.} = ## Adds a n-sided regular polygon at (x, y) with the parameter size. path.polygon(pos.x, pos.y, size, sides) -proc commandsToShapes(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] = +proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] = ## Converts SVG-like commands to line segments. var start, at: Vec2 diff --git a/tests/emoji_megatest.nim b/tests/emoji_megatest.nim new file mode 100644 index 0000000..0807ccc --- /dev/null +++ b/tests/emoji_megatest.nim @@ -0,0 +1,59 @@ +import cligen, os, pixie, pixie/fileformats/svg, strformat + +# Clone https://github.com/twitter/twemoji +# Check out commit 59cb0eacce837d0f5de30223bd8f530e447f547a + +# Clone https://github.com/hfg-gmuend/openmoji +# Check out commit c1f14ae0be29b20c7eed215d1e03df23b1c9a5d5 + +type EmojiSet = object + name: string + path: string + +const + emojiSets = [ + EmojiSet(name: "twemoji", path: "../twemoji/assets/svg/*"), + EmojiSet(name: "openmoji", path: "../openmoji/color/svg/*") + ] + width = 32 + height = 32 + +proc renderEmojiSet(index: int) = + let emojiSet = emojiSets[index] + + var images: seq[(string, Image)] + + for filePath in walkFiles(emojiSet.path): + let (_, name, _) = splitFile(filePath) + var image: Image + try: + image = decodeSvg(readFile(filePath), width, height) + except PixieError: + echo &"Failed decoding {name}" + image = newImage(width, height) + images.add((name, image)) + + let + columns = 40 + rows = (images.len + columns - 1) div columns + rendered = newImage((width + 4) * columns, (height + 4) * rows) + + for i in 0 ..< rows: + for j in 0 ..< max(images.len - i * columns, 0): + let (_, icon) = images[i * columns + j] + rendered.draw( + icon, + vec2(((width + 4) * j + 2).float32, ((height + 4) * i + 2).float32), + bmOverwrite + ) + + rendered.writeFile(&"tests/images/svg/{emojiSet.name}.png") + +proc main(index = -1) = + if index >= 0: + renderEmojiSet(index) + else: + for i in 0 ..< emojiSets.len: + renderEmojiSet(i) + +dispatch(main) diff --git a/tests/images/svg/flat-color-icons.png b/tests/images/svg/flat-color-icons.png index 1af1b33..d121a74 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 3dbcea9..fe48a91 100644 Binary files a/tests/images/svg/ionicons.png and b/tests/images/svg/ionicons.png differ diff --git a/tests/images/svg/openmoji.png b/tests/images/svg/openmoji.png new file mode 100644 index 0000000..1b2931a Binary files /dev/null and b/tests/images/svg/openmoji.png differ diff --git a/tests/images/svg/tabler-icons.png b/tests/images/svg/tabler-icons.png index 9c838ee..646add2 100644 Binary files a/tests/images/svg/tabler-icons.png and b/tests/images/svg/tabler-icons.png differ diff --git a/tests/images/svg/twemoji.png b/tests/images/svg/twemoji.png new file mode 100644 index 0000000..363da42 Binary files /dev/null and b/tests/images/svg/twemoji.png differ