diff --git a/.gitignore b/.gitignore index b9fb92a..9d605e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ original/ output/ +Cargo.lock +target/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0e69d91 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "blobfox-template" +version = "0.1.0" +edition = "2021" +default-run = "blobfox-template" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.140", features = ["derive"] } +toml = "0.5.9" +xmltree = "0.10.3" +mustache = { git = "https://git.shadamethyst.xyz/adri326/rust-mustache.git" } +clap = { version = "3.2", features = ["derive"] } +mkdirp = "1.0.0" +resvg = "0.23" +usvg = "0.23" +tiny-skia = "0.6" +png = "0.17" diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..6698dca --- /dev/null +++ b/clean.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cargo run --bin clean -- "$1" +xmllint "$1" --format --output "$1" diff --git a/species/blobamber/assets/base.svg b/species/blobamber/assets/base.svg new file mode 100644 index 0000000..0dae62e --- /dev/null +++ b/species/blobamber/assets/base.svg @@ -0,0 +1,61 @@ + + + blobcat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobcat + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + + diff --git a/species/blobamber/assets/owo.svg b/species/blobamber/assets/owo.svg new file mode 100644 index 0000000..482e06e --- /dev/null +++ b/species/blobamber/assets/owo.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/species/blobamber/species.toml b/species/blobamber/species.toml new file mode 100644 index 0000000..50ec5e4 --- /dev/null +++ b/species/blobamber/species.toml @@ -0,0 +1,10 @@ +name = "blobamber" +base = "../blobcat/" + +[vars] +body_color = "#6a6862" +ear_color = "#313131" +ear_fluff_color = "#474747" +hand_color = "#6a6862" +tail_color = "#6a6862" +marks_color = "#c8c8c8" diff --git a/species/blobamber/templates/body-basic.mustache b/species/blobamber/templates/body-basic.mustache new file mode 100644 index 0000000..ad731ad --- /dev/null +++ b/species/blobamber/templates/body-basic.mustache @@ -0,0 +1,3 @@ +{{>blobcat.body-basic}} + +{{#set-fill}} {{vars.marks_color}} | {{#base}}#belly{{/base}} {{/set-fill}} diff --git a/species/blobamber/templates/eyes.mustache b/species/blobamber/templates/eyes.mustache new file mode 100644 index 0000000..b9efa84 --- /dev/null +++ b/species/blobamber/templates/eyes.mustache @@ -0,0 +1,14 @@ + + +{{#blobamber.owo}}#blobamber-owo-defs{{/blobamber.owo}} +{{#blobamber.base}}#blobamber-defs{{/blobamber.base}} + +{{#tags.eyes-owo}} + + {{#owo}}#left-eye{{/owo}} + {{#owo}}#right-eye{{/owo}} + +{{/tags.eyes-owo}} +{{^tags.eyes-owo}} + {{>blobcat.eyes}} +{{/tags.eyes-owo}} diff --git a/species/blobamber/templates/hands.mustache b/species/blobamber/templates/hands.mustache new file mode 100644 index 0000000..1371796 --- /dev/null +++ b/species/blobamber/templates/hands.mustache @@ -0,0 +1,14 @@ +{{#tags.hand-3c}} +{{#tags.holding}} + +{{/tags.holding}} +{{^tags.holding}} + +{{/tags.holding}} +{{/tags.hand-3c}} + +{{>blobcat.hands}} + +{{#tags.hand-3c}} + +{{/tags.hand-3c}} diff --git a/species/blobcat/assets/base.svg b/species/blobcat/assets/base.svg new file mode 100644 index 0000000..83b05b7 --- /dev/null +++ b/species/blobcat/assets/base.svg @@ -0,0 +1,47 @@ + + + blobcat + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobcat + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + + diff --git a/species/blobcat/species.toml b/species/blobcat/species.toml new file mode 100644 index 0000000..b712f45 --- /dev/null +++ b/species/blobcat/species.toml @@ -0,0 +1,9 @@ +name = "blobcat" +base = "../blobfox/" + +[vars] +body_color = "#fcc21b" +ear_color = "#e9ae20" +ear_fluff_color = "#8a6135" +hand_color = "#fcc21b" +tail_color = "#fcc21b" diff --git a/species/blobcat/templates/blush.mustache b/species/blobcat/templates/blush.mustache new file mode 100644 index 0000000..a886130 --- /dev/null +++ b/species/blobcat/templates/blush.mustache @@ -0,0 +1,14 @@ +{{! Pull the defs from the svg for the gradients }} +{{#blush}}#blush-defs{{/blush}} + + + {{#blush}}#left-blush{{/blush}} + {{#blush}}#left-blush-line{{/blush}} + {{#blush}}#left-blush-line-2{{/blush}} + + + + {{#blush}}#right-blush{{/blush}} + {{#blush}}#right-blush-line{{/blush}} + {{#blush}}#right-blush-line-2{{/blush}} + diff --git a/species/blobcat/templates/body-basic.mustache b/species/blobcat/templates/body-basic.mustache new file mode 100644 index 0000000..b11822a --- /dev/null +++ b/species/blobcat/templates/body-basic.mustache @@ -0,0 +1,20 @@ +{{! Left ear }} +{{#set-fill}} {{vars.ear_color}} | {{#base}}#left-ear{{/base}} {{/set-fill}} + +{{! Body }} + + + {{#base}}#body{{/base}} + + +{{#set-fill}} {{vars.body_color}} | {{#base}}#body{{/base}} {{/set-fill}} +{{#set-fill}} {{vars.body_color}} | {{#base}}#hair{{/base}} {{/set-fill}} + +{{! Right ear }} +{{#set-fill}} {{vars.ear_color}} | {{#base}}#right-ear{{/base}} {{/set-fill}} +{{#set-fill}} {{vars.ear_fluff_color}} | {{#base}}#right-ear-fluff{{/base}} {{/set-fill}} + +{{! Whiskers }} +{{^tags.hands-reach}} + {{>whiskers}} +{{/tags.hands-reach}} diff --git a/species/blobcat/templates/body-snug.mustache b/species/blobcat/templates/body-snug.mustache new file mode 100644 index 0000000..b5dcb32 --- /dev/null +++ b/species/blobcat/templates/body-snug.mustache @@ -0,0 +1,23 @@ + + + + {{#snug}}#body{{/snug}} + + + + + {{#set-fill}} {{vars.ear_color}} | {{#base}}#left-ear{{/base}} {{/set-fill}} + + + {{#set-fill}} + {{vars.body_color}} + | {{#snug}}#body{{/snug}} + {{/set-fill}} + + + {{#set-fill}} {{vars.ear_color}} | {{#base}}#right-ear{{/base}} {{/set-fill}} + {{#set-fill}} {{vars.ear_fluff_color}} | {{#base}}#right-ear-fluff{{/base}} {{/set-fill}} + + + {{>tail}} + diff --git a/species/blobcat/templates/hand-boop.mustache b/species/blobcat/templates/hand-boop.mustache new file mode 100644 index 0000000..5650a3e --- /dev/null +++ b/species/blobcat/templates/hand-boop.mustache @@ -0,0 +1,3 @@ + + {{>blobfox.hand-boop}} + diff --git a/species/blobcat/templates/hands.mustache b/species/blobcat/templates/hands.mustache new file mode 100644 index 0000000..d4457ed --- /dev/null +++ b/species/blobcat/templates/hands.mustache @@ -0,0 +1,3 @@ +{{#set-fill}} + {{vars.hand_color}} | {{>blobfox.hands}} +{{/set-fill}} diff --git a/species/blobcat/templates/mouth.mustache b/species/blobcat/templates/mouth.mustache new file mode 100644 index 0000000..e40ceef --- /dev/null +++ b/species/blobcat/templates/mouth.mustache @@ -0,0 +1,13 @@ +{{#tags.boop}} + +{{/tags.boop}} +{{#tags.mouth-hmpf}} + +{{/tags.mouth-hmpf}} + {{>blobfox.mouth}} +{{#tags.boop}} + +{{/tags.boop}} +{{#tags.mouth-hmpf}} + +{{/tags.mouth-hmpf}} diff --git a/species/blobcat/templates/nose.mustache b/species/blobcat/templates/nose.mustache new file mode 100644 index 0000000..b2a78db --- /dev/null +++ b/species/blobcat/templates/nose.mustache @@ -0,0 +1,17 @@ +{{#tags.body-snug}} + + {{#blobfox.base}}#nose{{/blobfox.base}} + +{{/tags.body-snug}} + +{{#tags.boop}} + + {{#blobfox.base}}#nose{{/blobfox.base}} + +{{/tags.boop}} + +{{#tags.mouth-hmpf}} + + {{#blobfox.base}}#nose{{/blobfox.base}} + +{{/tags.mouth-hmpf}} diff --git a/species/blobcat/templates/tail.mustache b/species/blobcat/templates/tail.mustache new file mode 100644 index 0000000..640f6fe --- /dev/null +++ b/species/blobcat/templates/tail.mustache @@ -0,0 +1,4 @@ +{{#set-fill}} + {{vars.tail_color}} + | {{>blobfox.tail}} +{{/set-fill}} diff --git a/species/blobcat/templates/whiskers.mustache b/species/blobcat/templates/whiskers.mustache new file mode 100644 index 0000000..8c0a005 --- /dev/null +++ b/species/blobcat/templates/whiskers.mustache @@ -0,0 +1,14 @@ + +{{#tags.left-hand}}{{#tags.right-hand}}{{#tags.holding}} + +{{/tags.holding}}{{/tags.right-hand}}{{/tags.left-hand}} + + {{#base}}#left-whisker{{/base}} + {{#base}}#left-whisker-2{{/base}} + {{#base}}#right-whisker{{/base}} + {{#base}}#right-whisker-2{{/base}} + +{{#tags.left-hand}}{{#tags.right-hand}}{{#tags.holding}} + +{{/tags.holding}}{{/tags.right-hand}}{{/tags.left-hand}} + diff --git a/species/blobfox/assets/3c_evil.svg b/species/blobfox/assets/3c_evil.svg new file mode 100644 index 0000000..a657904 --- /dev/null +++ b/species/blobfox/assets/3c_evil.svg @@ -0,0 +1,50 @@ + + + blobfox_3c_evil + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox_3c_evil + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + Shad Amethyst + + + + + + diff --git a/species/blobfox/assets/base.svg b/species/blobfox/assets/base.svg new file mode 100644 index 0000000..e0bc79d --- /dev/null +++ b/species/blobfox/assets/base.svg @@ -0,0 +1,161 @@ + + + + + blobfox + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + Shad Amethyst + + + + + + diff --git a/species/blobfox/assets/blush.svg b/species/blobfox/assets/blush.svg new file mode 100644 index 0000000..e8ae7d9 --- /dev/null +++ b/species/blobfox/assets/blush.svg @@ -0,0 +1,63 @@ + + + blobfox_blush + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox_blush + https://git.shadamethyst.xyz/adri326/blobfox + + + Feuerfuchs + + + + + Shad Amethyst + + + + + + diff --git a/species/blobfox/assets/boop_owo.svg b/species/blobfox/assets/boop_owo.svg new file mode 100644 index 0000000..f9557ef --- /dev/null +++ b/species/blobfox/assets/boop_owo.svg @@ -0,0 +1 @@ +blobfoxBlobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 LicenseblobfoxFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfoxShad Amethyst \ No newline at end of file diff --git a/species/blobfox/assets/egg.svg b/species/blobfox/assets/egg.svg new file mode 100644 index 0000000..b7aa6aa --- /dev/null +++ b/species/blobfox/assets/egg.svg @@ -0,0 +1,52 @@ + + + blobfox_egg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox_egg + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + Shad Amethyst + + + + + + diff --git a/species/blobfox/assets/heart.svg b/species/blobfox/assets/heart.svg new file mode 100644 index 0000000..f756250 --- /dev/null +++ b/species/blobfox/assets/heart.svg @@ -0,0 +1,50 @@ + + + blobfox_heart + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox_heart + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + Shad Amethyst + + + + + + diff --git a/species/blobfox/assets/knife.svg b/species/blobfox/assets/knife.svg new file mode 100644 index 0000000..6f5b720 --- /dev/null +++ b/species/blobfox/assets/knife.svg @@ -0,0 +1,43 @@ + + + blobfox + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + Shad Amethyst + + + + + + diff --git a/species/blobfox/assets/reach_aww.svg b/species/blobfox/assets/reach_aww.svg new file mode 100644 index 0000000..8af0283 --- /dev/null +++ b/species/blobfox/assets/reach_aww.svg @@ -0,0 +1,194 @@ + + + + + blobfox_reach_aww + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 Licenseblobfox_reach_awwFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfoxShad Amethyst + + diff --git a/species/blobfox/assets/snug.svg b/species/blobfox/assets/snug.svg new file mode 100644 index 0000000..eb08925 --- /dev/null +++ b/species/blobfox/assets/snug.svg @@ -0,0 +1,49 @@ + + + blobfox_snug + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox_snug + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + Shad Amethyst + + + + + + diff --git a/species/blobfox/species.toml b/species/blobfox/species.toml new file mode 100644 index 0000000..7fe64c8 --- /dev/null +++ b/species/blobfox/species.toml @@ -0,0 +1,38 @@ +# Add options in here as needs be +name = "blobfox" + +[vars] +body_color = "#ff8702" +ear_color = "#313131" +ear_fluff_color = "#ebdccc" +hand_color = "#ff8702" +tail_color = "#ff8702" + +[variants] +base = ["body-basic", "eyes-basic", "mouth-w"] +happy = ["body-basic", "eyes-happy", "mouth-w"] +evil = ["body-basic", "eyes-evil", "mouth-w"] +owo = ["body-basic", "ear-owo", "eyes-owo", "mouth-w"] + +"3c" = ["body-basic", "eyes-basic", "mouth-w", "hand-3c", "left-hand"] +"3c_evil" = ["body-basic", "eyes-evil", "mouth-w", "hand-3c", "left-hand"] + +boop = ["body-basic", "boop", "eyes-basic", "mouth-w"] +boop_aww = ["body-basic", "boop", "eyes-aww", "mouth-w"] +boop_owo = ["body-basic", "ear-owo", "boop", "eyes-owo", "mouth-w"] + +reach = ["body-basic", "eyes-basic", "mouth-w", "hands-reach", "left-hand", "right-hand"] +reach_aww = ["body-basic", "eyes-aww", "mouth-w", "hands-reach", "left-hand", "right-hand"] +reach_owo = ["body-basic", "ear-owo", "eyes-owo", "mouth-w", "hands-reach", "left-hand", "right-hand"] + +snug = ["body-snug", "eyes-happy", "tail"] +snug_aww = ["body-snug", "eyes-aww", "tail"] +snug_owo = ["body-snug", "ear-owo", "eyes-owo", "tail"] +snug_boop_owo = ["body-snug", "ear-owo", "eyes-owo", "tail", "boop"] + +stabby = ["body-basic", "holding", "eyes-evil", "mouth-w", "hand-3c", "left-hand"] + +blush = ["body-basic", "ear-blush", "eyes-closed", "blush", "mouth-hmpf"] + +heart = ["body-basic", "eyes-basic", "left-hand", "right-hand", "holding", "big-object"] +egg = ["body-basic", "eyes-basic", "left-hand", "right-hand", "holding"] diff --git a/species/blobfox/templates/blush.mustache b/species/blobfox/templates/blush.mustache new file mode 100644 index 0000000..33212d1 --- /dev/null +++ b/species/blobfox/templates/blush.mustache @@ -0,0 +1,10 @@ +{{! Pull the defs from the svg for the gradients }} +{{#blush}}#blush-defs{{/blush}} + +{{#blush}}#right-blush{{/blush}} +{{#blush}}#right-blush-line{{/blush}} +{{#blush}}#right-blush-line-2{{/blush}} + +{{#blush}}#left-blush{{/blush}} +{{#blush}}#left-blush-line{{/blush}} +{{#blush}}#left-blush-line-2{{/blush}} diff --git a/species/blobfox/templates/body-basic.mustache b/species/blobfox/templates/body-basic.mustache new file mode 100644 index 0000000..9ccaefa --- /dev/null +++ b/species/blobfox/templates/body-basic.mustache @@ -0,0 +1,21 @@ +{{! Left ear }} +{{#tags.ear-owo}} + {{#set-fill}} {{vars.ear_color}} | {{#boop_owo}}#left-ear{{/boop_owo}} {{/set-fill}} + {{#set-fill}} {{vars.ear_fluff_color}} | {{#boop_owo}}#left-ear-fluff{{/boop_owo}} {{/set-fill}} +{{/tags.ear-owo}} +{{^tags.ear-owo}} + {{#set-fill}} {{vars.ear_color}} | {{#base}}#left-ear{{/base}} {{/set-fill}} +{{/tags.ear-owo}} + +{{! Body }} + + + {{#base}}#body{{/base}} + + +{{#set-fill}} {{vars.body_color}} | {{#base}}#body{{/base}} {{/set-fill}} +{{#set-fill}} {{vars.body_color}} | {{#base}}#hair{{/base}} {{/set-fill}} + +{{! Right ear }} +{{#set-fill}} {{vars.ear_color}} | {{#base}}#right-ear{{/base}} {{/set-fill}} +{{#set-fill}} {{vars.ear_fluff_color}} | {{#base}}#right-ear-fluff{{/base}} {{/set-fill}} diff --git a/species/blobfox/templates/body-snug.mustache b/species/blobfox/templates/body-snug.mustache new file mode 100644 index 0000000..6633363 --- /dev/null +++ b/species/blobfox/templates/body-snug.mustache @@ -0,0 +1,36 @@ + + + + {{#snug}}#body{{/snug}} + + + + {{! TODO: owo ear }} + {{#set-fill}} {{vars.ear_color}} | {{#snug}}#left-ear{{/snug}} {{/set-fill}} + + {{#set-fill}} {{vars.body_color}} | {{#snug}}#body{{/snug}} {{/set-fill}} + {{#set-fill}} {{vars.body_color}} | {{#snug}}#hair{{/snug}} {{/set-fill}} + + {{#set-fill}} {{vars.ear_color}} | {{#snug}}#right-ear{{/snug}} {{/set-fill}} + {{#set-fill}} {{vars.ear_fluff_color}} | {{#snug}}#right-ear-fluff{{/snug}} {{/set-fill}} + + {{#tags.tail}} + + {{#set-fill}} {{vars.body_color}} | {{#snug}}#tail{{/snug}} {{/set-fill}} + + {{/tags.tail}} + + + diff --git a/species/blobfox/templates/body.mustache b/species/blobfox/templates/body.mustache new file mode 100644 index 0000000..1eff5db --- /dev/null +++ b/species/blobfox/templates/body.mustache @@ -0,0 +1,8 @@ + + {{#tags.body-basic}} + {{>body-basic}} + {{/tags.body-basic}} + {{#tags.body-snug}} + {{>body-snug}} + {{/tags.body-snug}} + diff --git a/species/blobfox/templates/eyes.mustache b/species/blobfox/templates/eyes.mustache new file mode 100644 index 0000000..9658a4e --- /dev/null +++ b/species/blobfox/templates/eyes.mustache @@ -0,0 +1,26 @@ + + {{#tags.eyes-basic}} + {{#base}}#left-eye{{/base}} + {{#base}}#right-eye{{/base}} + {{/tags.eyes-basic}} + {{#tags.eyes-owo}} + {{#boop_owo}}#left-eye{{/boop_owo}} + {{#boop_owo}}#right-eye{{/boop_owo}} + {{/tags.eyes-owo}} + {{#tags.eyes-happy}} + {{#snug}}#left-eye{{/snug}} + {{#snug}}#right-eye{{/snug}} + {{/tags.eyes-happy}} + {{#tags.eyes-aww}} + {{#reach_aww}}#left-eye{{/reach_aww}} + {{#reach_aww}}#right-eye{{/reach_aww}} + {{/tags.eyes-aww}} + {{#tags.eyes-evil}} + {{#3c_evil}}#left-eye{{/3c_evil}} + {{#3c_evil}}#right-eye{{/3c_evil}} + {{/tags.eyes-evil}} + {{#tags.eyes-closed}} + {{#blush}}#left-eye{{/blush}} + {{#blush}}#right-eye{{/blush}} + {{/tags.eyes-closed}} + diff --git a/species/blobfox/templates/footer.mustache b/species/blobfox/templates/footer.mustache new file mode 100644 index 0000000..c2667cb --- /dev/null +++ b/species/blobfox/templates/footer.mustache @@ -0,0 +1,2 @@ + + diff --git a/species/blobfox/templates/hand-boop.mustache b/species/blobfox/templates/hand-boop.mustache new file mode 100644 index 0000000..716d758 --- /dev/null +++ b/species/blobfox/templates/hand-boop.mustache @@ -0,0 +1 @@ +{{#boop_owo}}#hand-boop{{/boop_owo}} diff --git a/species/blobfox/templates/hands.mustache b/species/blobfox/templates/hands.mustache new file mode 100644 index 0000000..f9e39ab --- /dev/null +++ b/species/blobfox/templates/hands.mustache @@ -0,0 +1,26 @@ + + {{#tags.hands-reach}} + {{#reach_aww}}#left-hand{{/reach_aww}} + {{#reach_aww}}#right-hand{{/reach_aww}} + {{/tags.hands-reach}} + {{#tags.hand-3c}} + {{#tags.holding}} + {{#knife}}#left-hand{{/knife}} + {{/tags.holding}} + {{^tags.holding}} + {{! :3c hand }} + {{#3c_evil}}#left-hand{{/3c_evil}} + {{/tags.holding}} + {{/tags.hand-3c}} + {{^tags.hand-3c}} + {{#tags.holding}} + {{#tags.left-hand}} + {{#heart}}#left-hand{{/heart}} + {{/tags.left-hand}} + + {{#tags.right-hand}} + {{#heart}}#right-hand{{/heart}} + {{/tags.right-hand}} + {{/tags.holding}} + {{/tags.hand-3c}} + diff --git a/species/blobfox/templates/header.mustache b/species/blobfox/templates/header.mustache new file mode 100644 index 0000000..b2b7858 --- /dev/null +++ b/species/blobfox/templates/header.mustache @@ -0,0 +1,9 @@ + + {{variant_name}} + diff --git a/species/blobfox/templates/knife.mustache b/species/blobfox/templates/knife.mustache new file mode 100644 index 0000000..6f1dbe3 --- /dev/null +++ b/species/blobfox/templates/knife.mustache @@ -0,0 +1,6 @@ + + {{#knife}}#blade-back{{/knife}} + {{#knife}}#blade-front{{/knife}} + {{#knife}}#handle{{/knife}} + {{#knife}}#handle-screw{{/knife}} + diff --git a/species/blobfox/templates/mouth.mustache b/species/blobfox/templates/mouth.mustache new file mode 100644 index 0000000..a1674b5 --- /dev/null +++ b/species/blobfox/templates/mouth.mustache @@ -0,0 +1,6 @@ +{{#tags.mouth-w}} + {{#base}}#mouth{{/base}} +{{/tags.mouth-w}} +{{#tags.mouth-hmpf}} + {{#blush}}#mouth{{/blush}} +{{/tags.mouth-hmpf}} diff --git a/species/blobfox/templates/nose.mustache b/species/blobfox/templates/nose.mustache new file mode 100644 index 0000000..edbdedf --- /dev/null +++ b/species/blobfox/templates/nose.mustache @@ -0,0 +1,19 @@ + + {{#tags.eyes-basic}} + {{#base}}#nose-outline{{/base}} + {{/tags.eyes-basic}} + {{#tags.eyes-owo}} + {{#boop_owo}}#nose-outline{{/boop_owo}} + {{/tags.eyes-owo}} + {{#tags.eyes-aww}} + {{#reach_aww}}#nose-outline{{/reach_aww}} + {{/tags.eyes-aww}} + {{#tags.eyes-evil}} + {{#3c_evil}}#nose-outline{{/3c_evil}} + {{/tags.eyes-evil}} + {{#tags.eyes-closed}} + {{#blush}}#nose-outline{{/blush}} + {{/tags.eyes-closed}} + + {{#base}}#nose{{/base}} + diff --git a/species/blobfox/templates/tail.mustache b/species/blobfox/templates/tail.mustache new file mode 100644 index 0000000..5466a72 --- /dev/null +++ b/species/blobfox/templates/tail.mustache @@ -0,0 +1 @@ +{{#snug}}#tail{{/snug}} diff --git a/species/blobfox/variants/3c.mustache b/species/blobfox/variants/3c.mustache new file mode 100644 index 0000000..4665694 --- /dev/null +++ b/species/blobfox/variants/3c.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hands}} +{{>footer}} diff --git a/species/blobfox/variants/3c_evil.mustache b/species/blobfox/variants/3c_evil.mustache new file mode 100644 index 0000000..4665694 --- /dev/null +++ b/species/blobfox/variants/3c_evil.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hands}} +{{>footer}} diff --git a/species/blobfox/variants/base.mustache b/species/blobfox/variants/base.mustache new file mode 100644 index 0000000..be8b98b --- /dev/null +++ b/species/blobfox/variants/base.mustache @@ -0,0 +1,7 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} +{{>footer}} diff --git a/species/blobfox/variants/blush.mustache b/species/blobfox/variants/blush.mustache new file mode 100644 index 0000000..4f0c58b --- /dev/null +++ b/species/blobfox/variants/blush.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>blush}} +{{>footer}} diff --git a/species/blobfox/variants/boop.mustache b/species/blobfox/variants/boop.mustache new file mode 100644 index 0000000..153c9c3 --- /dev/null +++ b/species/blobfox/variants/boop.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hand-boop}} +{{>footer}} diff --git a/species/blobfox/variants/boop_aww.mustache b/species/blobfox/variants/boop_aww.mustache new file mode 100644 index 0000000..153c9c3 --- /dev/null +++ b/species/blobfox/variants/boop_aww.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hand-boop}} +{{>footer}} diff --git a/species/blobfox/variants/boop_owo.mustache b/species/blobfox/variants/boop_owo.mustache new file mode 100644 index 0000000..153c9c3 --- /dev/null +++ b/species/blobfox/variants/boop_owo.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hand-boop}} +{{>footer}} diff --git a/species/blobfox/variants/egg.mustache b/species/blobfox/variants/egg.mustache new file mode 100644 index 0000000..d0487e7 --- /dev/null +++ b/species/blobfox/variants/egg.mustache @@ -0,0 +1,10 @@ +{{>header}} + {{>body}} + + {{>eyes}} + + {{#egg}}#egg{{/egg}} + {{#egg}}#egg-reflection{{/egg}} + + {{>hands}} +{{>footer}} diff --git a/species/blobfox/variants/evil.mustache b/species/blobfox/variants/evil.mustache new file mode 100644 index 0000000..be8b98b --- /dev/null +++ b/species/blobfox/variants/evil.mustache @@ -0,0 +1,7 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} +{{>footer}} diff --git a/species/blobfox/variants/happy.mustache b/species/blobfox/variants/happy.mustache new file mode 100644 index 0000000..be8b98b --- /dev/null +++ b/species/blobfox/variants/happy.mustache @@ -0,0 +1,7 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} +{{>footer}} diff --git a/species/blobfox/variants/heart.mustache b/species/blobfox/variants/heart.mustache new file mode 100644 index 0000000..c32d417 --- /dev/null +++ b/species/blobfox/variants/heart.mustache @@ -0,0 +1,10 @@ +{{>header}} + {{>body}} + + {{>eyes}} + + {{#heart}}#heart{{/heart}} + {{#heart}}#heart-reflection{{/heart}} + + {{>hands}} +{{>footer}} diff --git a/species/blobfox/variants/owo.mustache b/species/blobfox/variants/owo.mustache new file mode 100644 index 0000000..be8b98b --- /dev/null +++ b/species/blobfox/variants/owo.mustache @@ -0,0 +1,7 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} +{{>footer}} diff --git a/species/blobfox/variants/reach.mustache b/species/blobfox/variants/reach.mustache new file mode 100644 index 0000000..4665694 --- /dev/null +++ b/species/blobfox/variants/reach.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hands}} +{{>footer}} diff --git a/species/blobfox/variants/reach_aww.mustache b/species/blobfox/variants/reach_aww.mustache new file mode 100644 index 0000000..4665694 --- /dev/null +++ b/species/blobfox/variants/reach_aww.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hands}} +{{>footer}} diff --git a/species/blobfox/variants/reach_owo.mustache b/species/blobfox/variants/reach_owo.mustache new file mode 100644 index 0000000..4665694 --- /dev/null +++ b/species/blobfox/variants/reach_owo.mustache @@ -0,0 +1,9 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>hands}} +{{>footer}} diff --git a/species/blobfox/variants/snug.mustache b/species/blobfox/variants/snug.mustache new file mode 100644 index 0000000..b6d9c9e --- /dev/null +++ b/species/blobfox/variants/snug.mustache @@ -0,0 +1,6 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} +{{>footer}} diff --git a/species/blobfox/variants/snug_aww.mustache b/species/blobfox/variants/snug_aww.mustache new file mode 100644 index 0000000..b6d9c9e --- /dev/null +++ b/species/blobfox/variants/snug_aww.mustache @@ -0,0 +1,6 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} +{{>footer}} diff --git a/species/blobfox/variants/snug_boop_owo.mustache b/species/blobfox/variants/snug_boop_owo.mustache new file mode 100644 index 0000000..7b06cd4 --- /dev/null +++ b/species/blobfox/variants/snug_boop_owo.mustache @@ -0,0 +1,8 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + + {{>hand-boop}} +{{>footer}} diff --git a/species/blobfox/variants/snug_owo.mustache b/species/blobfox/variants/snug_owo.mustache new file mode 100644 index 0000000..b6d9c9e --- /dev/null +++ b/species/blobfox/variants/snug_owo.mustache @@ -0,0 +1,6 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} +{{>footer}} diff --git a/species/blobfox/variants/stabby.mustache b/species/blobfox/variants/stabby.mustache new file mode 100644 index 0000000..699f4ec --- /dev/null +++ b/species/blobfox/variants/stabby.mustache @@ -0,0 +1,10 @@ +{{>header}} + {{>body}} + + {{>eyes}} + {{>nose}} + {{>mouth}} + + {{>knife}} + {{>hands}} +{{>footer}} diff --git a/species/blobstella/assets/base.svg b/species/blobstella/assets/base.svg new file mode 100644 index 0000000..ebce6d1 --- /dev/null +++ b/species/blobstella/assets/base.svg @@ -0,0 +1,57 @@ + + + blobcat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobcat + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + + diff --git a/species/blobstella/species.toml b/species/blobstella/species.toml new file mode 100644 index 0000000..9a4ba0f --- /dev/null +++ b/species/blobstella/species.toml @@ -0,0 +1,10 @@ +name = "blobstella" +base = "../blobcat/" + +[vars] +body_color = "#54b6e7" +ear_color = "#3398c7" +ear_fluff_color = "#224f66" +hand_color = "#54b6e7" +tail_color = "#54b6e7" +marks_color = "#eee64e" diff --git a/species/blobstella/templates/body-basic.mustache b/species/blobstella/templates/body-basic.mustache new file mode 100644 index 0000000..fb9dbc0 --- /dev/null +++ b/species/blobstella/templates/body-basic.mustache @@ -0,0 +1,26 @@ +{{! Left ear }} +{{#set-fill}} {{vars.ear_color}} | {{#base}}#left-ear{{/base}} {{/set-fill}} + +{{! Body }} + + + {{#base}}#body{{/base}} + + +{{#set-fill}} {{vars.body_color}} | {{#base}}#body{{/base}} {{/set-fill}} +{{#set-fill}} {{vars.body_color}} | {{#base}}#hair{{/base}} {{/set-fill}} + +{{! Right ear }} +{{#set-fill}} {{vars.ear_color}} | {{#base}}#right-ear{{/base}} {{/set-fill}} +{{#set-fill}} {{vars.ear_fluff_color}} | {{#base}}#right-ear-full{{/base}} {{/set-fill}} + +{{! Marks }} + + {{#base}}#left-marks{{/base}} + {{#base}}#right-marks{{/base}} + + +{{! Whiskers }} +{{^tags.hands-reach}} + {{>whiskers}} +{{/tags.hands-reach}} diff --git a/species/blobstella/templates/body-snug.mustache b/species/blobstella/templates/body-snug.mustache new file mode 100644 index 0000000..9253199 --- /dev/null +++ b/species/blobstella/templates/body-snug.mustache @@ -0,0 +1,14 @@ +{{>blobcat.body-snug}} + + + + {{#set-fill}} + {{vars.marks_color}} + | {{#base}}#left-marks{{/base}} + {{/set-fill}} + {{#set-fill}} + {{vars.marks_color}} + | {{#base}}#right-marks{{/base}} + {{/set-fill}} + + diff --git a/src/bin/clean.rs b/src/bin/clean.rs new file mode 100644 index 0000000..0b9aefd --- /dev/null +++ b/src/bin/clean.rs @@ -0,0 +1,69 @@ +use xmltree::{XMLNode, Element}; +use clap::Parser; +use std::collections::HashMap; +use std::path::PathBuf; + +fn main() { + let args = Args::parse(); + + for path in args.files { + let file = std::fs::File::open(path.clone()).unwrap_or_else(|err| { + panic!("Error while reading {}: {}", path.display(), err); + }); + let mut element = Element::parse(file).expect("Couldn't parse SVG!"); + + clean(&mut element); + + let mut s: Vec = Vec::new(); + element.write(&mut s).expect("Couldn't export SVG!"); + + std::fs::write(path.clone(), s).unwrap_or_else(|err| { + panic!("Error while writing {}: {}", path.display(), err); + }); + } +} + +fn clean(element: &mut Element) { + let mut counts: HashMap = HashMap::new(); + + fn count_rec(element: &Element, counts: &mut HashMap) { + if let Some(label) = element.attributes.get("label") { + if let Some(count) = counts.get_mut(label) { + *count += 1; + } else { + counts.insert(label.to_string(), 1); + } + } + + for child in element.children.iter() { + if let XMLNode::Element(ref child) = child { + count_rec(child, counts); + } + } + } + + count_rec(element, &mut counts); + + fn update_rec(element: &mut Element, counts: &HashMap) { + if let Some(label) = element.attributes.get("label") { + if let Some(1) = counts.get(label) { + element.attributes.insert("id".to_string(), label.to_string()); + } + } + + for child in element.children.iter_mut() { + if let XMLNode::Element(ref mut child) = child { + update_rec(child, counts); + } + } + } + + update_rec(element, &counts); +} + +#[derive(Parser)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(value_parser)] + files: Vec, +} diff --git a/src/export.rs b/src/export.rs new file mode 100644 index 0000000..7537777 --- /dev/null +++ b/src/export.rs @@ -0,0 +1,178 @@ +use usvg::{ + Tree, + NodeExt, + Options, +}; +use xmltree::{XMLNode, Element}; +use std::path::{PathBuf}; +use std::collections::HashSet; + +#[derive(Debug)] +pub enum ExportError { + Xml(xmltree::Error), + XmlParse(xmltree::ParseError), + Usvg(usvg::Error), + Io(PathBuf, std::io::Error), + NoBBox, + Utf8(std::string::FromUtf8Error), + Encode(png::EncodingError), +} + +impl From for ExportError { + fn from(err: xmltree::ParseError) -> Self { + Self::XmlParse(err) + } +} + +impl From for ExportError { + fn from(err: xmltree::Error) -> Self { + Self::Xml(err) + } +} + +impl From for ExportError { + fn from(err: usvg::Error) -> Self { + Self::Usvg(err) + } +} + +impl From for ExportError { + fn from(err: std::string::FromUtf8Error) -> Self { + Self::Utf8(err) + } +} + +impl From for ExportError { + fn from(err: png::EncodingError) -> Self { + Self::Encode(err) + } +} + +fn get_new_bbox(svg: &Tree) -> Option<(f64, f64, f64, f64)> { + let bbox = svg.root().calculate_bbox()?; + + // FIXME: remove once https://github.com/RazrFalcon/resvg/issues/528 is fixed + const MARGIN: f64 = 1.0; + + let x = bbox.x() - MARGIN; + let y = bbox.y() - MARGIN; + let width = bbox.width() + MARGIN * 2.0; + let height = bbox.height() + MARGIN * 2.0; + + if width > height { + let y = y - (width - height) / 2.0; + + Some((x, y, width, width)) + } else { + let x = x - (height - width) / 2.0; + + Some((x, y, height, height)) + } +} + +fn get_usvg(svg_str: &str) -> Result { + let usvg_options = Options::default(); + Tree::from_str(svg_str, &usvg_options.to_ref()) +} + +fn get_xml(svg_str: &str) -> Result { + Element::parse(svg_str.as_bytes()) +} + +fn xml_to_str(svg_xml: &Element) -> Result { + let mut s: Vec = Vec::new(); + + svg_xml.write(&mut s)?; + + Ok(String::from_utf8(s)?) +} + +pub fn resize(svg_str: String) -> Result { + if let Some(new_bbox) = get_new_bbox(&get_usvg(&svg_str)?) { + let mut svg_xml = get_xml(&svg_str)?; + svg_xml.attributes.insert( + "viewBox".to_string(), + format!("{} {} {} {}", new_bbox.0, new_bbox.1, new_bbox.2, new_bbox.3), + ); + + xml_to_str(&svg_xml) + } else { + Err(ExportError::NoBBox) + } +} + +/// Finds all the `` in the svg and combines them all into one +pub fn combine_defs(svg_str: String) -> Result { + let mut svg_xml = get_xml(&svg_str)?; + + let mut defs = Vec::new(); + + fn collect_defs(element: &mut Element, defs: &mut Vec) { + for child in std::mem::take(&mut element.children) { + match child { + XMLNode::Element(child) if child.name == "defs" => { + defs.push(child); + } + mut child => { + if let XMLNode::Element(ref mut child) = &mut child { + collect_defs(child, defs); + } + element.children.push(child); + } + } + } + } + + collect_defs(&mut svg_xml, &mut defs); + + let mut defs_element = Element::new("defs"); + defs_element.children = defs + .into_iter() + .map(|def| { + def.children.into_iter().filter(|child| matches!(child, XMLNode::Element(_))) + }) + .flatten() + .collect::>(); + defs_element.attributes.insert("id".to_string(), "defs".to_string()); + + svg_xml.children.insert(0, XMLNode::Element(defs_element)); + + xml_to_str(&svg_xml) +} + +pub fn export( + mut svg_str: String, + output_dir: &PathBuf, + output_name: String, + args: &super::Args, +) -> Result<(), ExportError> { + if !args.no_resize { + svg_str = resize(svg_str)?; + } + + svg_str = combine_defs(svg_str)?; + + mkdirp::mkdirp(output_dir.join("vector")).unwrap(); + + let output = output_dir.join(&format!("vector/{}.svg", output_name)); + std::fs::write(output.clone(), svg_str.clone()).map_err(|err| ExportError::Io(output, err))?; + + let svg_usvg = get_usvg(&svg_str)?; + for resolution in args.dim.iter().copied().filter(|r| *r != 0).collect::>() { + mkdirp::mkdirp(output_dir.join(&format!("{}", resolution))).unwrap(); + let output = output_dir.join(&format!("{}/{}.png", resolution, output_name)); + + let mut image = tiny_skia::Pixmap::new(resolution, resolution).unwrap(); + + resvg::render( + &svg_usvg, + usvg::FitTo::Width(resolution), + tiny_skia::Transform::identity(), + image.as_mut() + ).unwrap(); + + image.save_png(output)?; + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b25f515 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,81 @@ +use clap::Parser; +use std::path::PathBuf; + +pub mod parse; +use parse::*; + +pub mod template; +use template::*; + +pub mod export; +use export::*; + +fn main() { + let args = Args::parse(); + + let species = load_species(args.decl.clone()).unwrap(); + let context = RenderingContext::new(species); + + let output_dir = args.output_dir.clone().unwrap_or(PathBuf::from("output/")); + + if args.names.is_empty() { + for name in context.species().variant_paths.keys() { + generate_variant(&context, name, &output_dir, &args); + } + } else { + for name in args.names.iter() { + generate_variant(&context, name, &output_dir, &args); + } + } +} + +fn generate_variant(context: &RenderingContext, name: &str, output_dir: &PathBuf, args: &Args) { + if let Some(path) = context.species().variant_paths.get(name) { + match context.compile(path).and_then(|template| { + template.render_data_to_string(&context.get_data(name)) + }) { + Ok(svg) => { + match export( + svg, + output_dir, + format!("{}_{}", context.species().name, name), + args + ) { + Ok(_) => {} + Err(err) => { + eprintln!("Error while rendering {}: {:?}", name, err); + } + } + } + Err(err) => { + eprintln!("Error while rendering {}: {}", name, err); + } + } + } else { + eprintln!("No variant named {}!", name); + } +} + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +pub struct Args { + /// A folder containing the declaration from which the emotes should be generated + #[clap(short, long, value_parser)] + decl: PathBuf, + + /// List of the emote names to export + #[clap(value_parser)] + names: Vec, + + /// Disable automatically resizing the SVG's viewBox, defaults to false + #[clap(short, long, value_parser, default_value = "false")] + no_resize: bool, + + /// Dimension to export the images as; can be specified multiple times + #[clap(long, value_parser)] + dim: Vec, + + /// Output directory + #[clap(short, long, value_parser)] + output_dir: Option, +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..a4aef5c --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,128 @@ +use xmltree::{Element}; +use serde::{Serialize, Deserialize}; +use std::path::{PathBuf, Path}; +use std::collections::HashMap; + +/// Error returned upon failing to parse something +#[derive(Debug)] +pub enum ParseError { + Io(PathBuf, std::io::Error), + XmlParse(xmltree::ParseError), + Toml(toml::de::Error), +} + +impl From for ParseError { + fn from(err: xmltree::ParseError) -> Self { + Self::XmlParse(err) + } +} + +impl From for ParseError { + fn from(err: toml::de::Error) -> Self { + Self::Toml(err) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SpeciesDecl { + /// Imports xml and svg files from this folder if they aren't found + pub base: Option, + + /// The name of the species + pub name: String, + + #[serde(default)] + pub variants: HashMap>, + + #[serde(default)] + pub vars: HashMap, + + #[serde(skip)] + pub template_paths: HashMap, + + #[serde(skip)] + pub variant_paths: HashMap, + + #[serde(skip)] + pub asset_paths: HashMap, + + #[serde(skip)] + pub parent: Option>, +} + +/// Loads the given file as an XML tree +pub fn load_xml(path: impl AsRef) -> Result { + let file = std::fs::File::open(path.as_ref()).map_err(|err| { + ParseError::Io(path.as_ref().to_path_buf(), err) + })?; + + Ok(Element::parse(file)?) +} + +/// Loads the basic description of a SpeciesDecl +pub fn load_species(path: impl AsRef) -> Result { + let declaration_path = path.as_ref().join("species.toml"); + let declaration = std::fs::read_to_string(&declaration_path).map_err(|err| { + ParseError::Io(declaration_path, err) + })?; + + let mut res: SpeciesDecl = toml::from_str(&declaration)?; + + if let Some(ref base) = &res.base { + let path = path.as_ref().to_path_buf().join(base); + let base = load_species(path)?; + + res.template_paths = base.template_paths.clone(); + res.variant_paths = base.variant_paths.clone(); + res.asset_paths = base.asset_paths.clone(); + res.variants = base.variants.clone(); + for (key, value) in base.vars.iter() { + if !res.vars.contains_key(key) { + res.vars.insert(key.clone(), value.clone()); + } + } + res.parent = Some(Box::new(base)); + } + + // Read the `templates` directory and populate the `template_paths` field; + // on error, ignore the directory. + for (name, path) in read_dir_xml(path.as_ref().join("templates")) { + res.template_paths.insert(name, path); + } + + // Read the `variants` directory + for (name, path) in read_dir_xml(path.as_ref().join("variants")) { + res.variant_paths.insert(name, path); + } + + // Read the `assets` directory + for (name, path) in read_dir_xml(path.as_ref().join("assets")) { + res.asset_paths.insert(name, path); + } + + Ok(res) +} + +fn read_dir_xml(path: impl AsRef) -> HashMap { + let mut res = HashMap::new(); + + if let Ok(iter) = std::fs::read_dir(path) { + for entry in iter.filter_map(|x| x.ok()) { + match (entry.path().file_stem(), entry.path().extension()) { + (Some(name), Some(ext)) => { + if matches!(ext.to_str(), Some("xml") | Some("svg") | Some("mustache")) { + if let Some(name) = name.to_str() { + res.insert( + name.to_string(), + entry.path().to_path_buf() + ); + } + } + } + _ => {} + } + } + } + + res +} diff --git a/src/template.rs b/src/template.rs new file mode 100644 index 0000000..471efa9 --- /dev/null +++ b/src/template.rs @@ -0,0 +1,294 @@ +use super::*; +use mustache::{Context, Data, MapBuilder, PartialLoader, Template}; +use std::collections::HashMap; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use xmltree::{Element, XMLNode}; + +#[derive(Debug, Clone)] +pub struct RenderingContext { + species: Arc, + + rendered_variants: Arc>>, + + loaded_assets: Arc>>, + + parent: Option>, +} + +impl RenderingContext { + pub fn new(mut species: SpeciesDecl) -> Self { + let parent = std::mem::take(&mut species.parent).map(|parent| { + Box::new(Self::new(*parent)) + }); + + Self { + species: Arc::new(species), + rendered_variants: Arc::new(Mutex::new(HashMap::new())), + loaded_assets: Arc::new(Mutex::new(HashMap::new())), + parent + } + } + + pub fn compile(&self, path: impl AsRef) -> Result, mustache::Error> { + let template = std::fs::read_to_string(path)?; + Context::with_loader(self.clone()).compile(template.chars()) + } + + fn render_to_string( + &self, + string: &str, + variant_name: &str, + ) -> Result { + Context::with_loader(self.clone()) + .compile(string.chars())? + .render_data_to_string(&self.get_data(variant_name)) + } + + pub fn get_data(&self, variant_name: &str) -> Data { + self.get_builder(variant_name, true).build() + } + + fn get_builder(&self, variant_name: &str, include_parent: bool) -> MapBuilder { + let mut builder = MapBuilder::new(); + + builder = builder.insert_map("variant", |mut builder| { + for variant_name in self.species.variant_paths.keys() { + let this = self.clone(); + let variant_name = variant_name.to_string(); + builder = builder.insert_fn(variant_name.clone(), move |selector| { + let svg = this.get_variant(&variant_name); + if let Some(svg) = svg { + if let Some(element) = query_selector(svg, &selector) { + if let Some(string) = xml_to_string(element) { + return string; + } + } + } + + String::new() + }) + } + builder + }); + + for asset_name in self.species.asset_paths.keys() { + let this = self.clone(); + let asset_name = asset_name.to_string(); + + builder = builder.insert_fn(asset_name.clone(), move |selector| { + let svg = this.get_asset(&asset_name); + if let Some(svg) = svg { + if let Some(element) = query_selector(svg, &selector) { + if let Some(string) = xml_to_string(element) { + return string; + } + } + } + + String::new() + }); + } + + let this = self.clone(); + let variant_name_owned = variant_name.to_string(); + builder = builder.insert_fn("set-fill", move |input| { + // Parse `color|xml` + if let [color, xml] = input.splitn(2, '|').collect::>()[..] { + // Render `color` and `xml` + if let (Ok(color), Ok(xml)) = ( + this.render_to_string(&color, &variant_name_owned), + this.render_to_string(&xml, &variant_name_owned), + ) { + // Convert `xml` to XML + match Element::parse(xml.as_bytes()) { + Ok(mut xml) => { + set_fill(&color.trim(), &mut xml); + + // Render XML to string + if let Some(res) = xml_to_string(xml) { + res + } else { + String::from("") + } + } + Err(err) => { + format!("", err) + } + } + } else { + String::from("") + } + } else { + String::from("") + } + }); + + builder = builder.insert("vars", &self.species.vars).unwrap(); + + if include_parent { + let mut this = self.clone(); + + loop { + builder = builder.insert_map(&this.species.name, |_| { + this.get_builder(variant_name, false) + }); + + if let Some(ref parent) = this.parent { + this = *parent.clone(); + } else { + break + } + } + } + + // TODO: memoize the builder to this stage + + // Variant tags + if let Some(tags) = self.species.variants.get(variant_name) { + builder = builder.insert_map("tags", move |mut builder| { + for tag in tags.iter() { + builder = builder.insert_bool(tag, true); + } + + builder + }); + } + + builder + } + + pub fn get_variant(&self, name: &String) -> Option { + let rendered = self.rendered_variants.lock().unwrap().get(name).cloned(); + if let Some(rendered) = rendered { + Some(rendered) + } else if let Some(path) = self.species.variant_paths.get(name) { + // TODO: log error + let template = self.compile(path).ok()?; + let data = self.get_data(name); + let rendered = template.render_data_to_string(&data).ok()?; + + let parsed = Element::parse(rendered.as_bytes()).ok()?; + self.rendered_variants + .lock() + .unwrap() + .insert(name.clone(), parsed.clone()); + + Some(parsed) + } else { + None + } + } + + pub fn get_asset(&self, name: &String) -> Option { + let loaded = self.loaded_assets.lock().unwrap().get(name).cloned(); + if let Some(loaded) = loaded { + Some(loaded) + } else if let Some(path) = self.species.asset_paths.get(name) { + let string = std::fs::read_to_string(path).ok()?; + let parsed = Element::parse(string.as_bytes()).ok()?; + self.loaded_assets + .lock() + .unwrap() + .insert(name.clone(), parsed.clone()); + + Some(parsed) + } else { + None + } + } + + pub fn species(&self) -> Arc { + Arc::clone(&self.species) + } +} + +impl PartialLoader for RenderingContext { + fn load(&self, name: impl AsRef) -> Result { + let name = name.as_ref().to_str().ok_or(mustache::Error::InvalidStr)?; + + let components = name.split('.').collect::>(); + + if components.len() == 1 { + if let Some(path) = self.species.template_paths.get(name) { + Ok(std::fs::read_to_string(path)?) + } else { + eprintln!("No template named {}", name); + Err(mustache::Error::NoFilename) + } + } else if components.len() == 2 { + if components[0] == self.species.name { + self.load(components[1]) + } else if let Some(ref parent) = self.parent { + parent.load(name) + } else { + eprintln!( + "Cannot get template named {}: no species called {} in the inheritance tree", + name, + components[0] + ); + Err(mustache::Error::NoFilename) + } + } else { + eprintln!("Cannot get template named {}: expected `name` or `species.name`", name); + Err(mustache::Error::NoFilename) + } + } +} + +fn set_fill(color: &str, xml: &mut Element) { + // Substitute the fill color + if let Some(style) = xml.attributes.get("style") { + xml.attributes.insert( + "style".to_string(), + format!("{};fill: {};", style, color), + ); + } + if let Some(_fill) = xml.attributes.get("fill") { + xml.attributes.insert("fill".to_string(), color.to_string()); + } + + for child in xml.children.iter_mut() { + if let XMLNode::Element(ref mut child) = child { + set_fill(color, child); + } + } +} + +pub fn query_selector(svg: Element, pattern: &str) -> Option { + if pattern == "" { + return Some(svg); + } + + for child in svg.children { + if let XMLNode::Element(child) = child { + if let ("#", pattern_id) = pattern.split_at(1) { + if child + .attributes + .get("id") + .map(|id| id == pattern_id) + .unwrap_or(false) + { + return Some(child); + } else if child.children.len() > 0 { + if let Some(res) = query_selector(child, pattern) { + return Some(res); + } + } + } + } + } + + None +} + +pub fn xml_to_string(element: Element) -> Option { + let mut s: Vec = Vec::new(); + let mut config = xmltree::EmitterConfig::default(); + config.perform_indent = true; + config.write_document_declaration = false; + + element.write_with_config(&mut s, config).ok()?; + + String::from_utf8(s).ok() +} diff --git a/vector/blobamber.svg b/vector/blobamber.svg new file mode 100644 index 0000000..b88445f --- /dev/null +++ b/vector/blobamber.svg @@ -0,0 +1,207 @@ + + + +blobcatBlobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 LicenseblobcatFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfox diff --git a/vector/blobamber_owo.svg b/vector/blobamber_owo.svg new file mode 100644 index 0000000..cb524e7 --- /dev/null +++ b/vector/blobamber_owo.svg @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/blobcat.svg b/vector/blobcat.svg index 622b258..acf7b77 100644 --- a/vector/blobcat.svg +++ b/vector/blobcat.svg @@ -28,15 +28,15 @@ inkscape:pagecheckerboard="1" inkscape:document-units="mm" showgrid="false" - inkscape:zoom="3.8198421" - inkscape:cx="40.839384" - inkscape:cy="67.542059" + inkscape:zoom="2.13551" + inkscape:cx="28.096333" + inkscape:cy="-15.452983" inkscape:window-width="1536" inkscape:window-height="779" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="layer3" + inkscape:current-layer="layer1" units="px" inkscape:showpageshadow="2" inkscape:deskcolor="#505050"> - blobfox_blush - blobfox_blush - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License - - - blobfox_blush - https://git.shadamethyst.xyz/adri326/blobfox - - - Feuerfuchs - - - - - Shad Amethyst - - - - - - + inkscape:label="right-blush-line" />Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 Licenseblobfox_blushhttps://git.shadamethyst.xyz/adri326/blobfoxFeuerfuchsShad Amethyst diff --git a/vector/blobfox_boop_owo.svg b/vector/blobfox_boop_owo.svg new file mode 100644 index 0000000..8223ebc --- /dev/null +++ b/vector/blobfox_boop_owo.svg @@ -0,0 +1,163 @@ + + + +blobfoxBlobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 LicenseblobfoxFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfoxShad Amethyst diff --git a/vector/blobfox_reach_aww.svg b/vector/blobfox_reach_aww.svg index adaea0e..8af0283 100644 --- a/vector/blobfox_reach_aww.svg +++ b/vector/blobfox_reach_aww.svg @@ -2,159 +2,193 @@ blobfox_reach_awwBlobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 Licenseblobfox_reach_awwFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfoxShad Amethyst + width="128" + height="128" + viewBox="0 0 33.866668 33.866668" + version="1.1" + id="svg5" + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" + sodipodi:docname="blobfox_reach_aww.svg" + xml:space="preserve" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + blobfox_reach_aww + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 Licenseblobfox_reach_awwFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfoxShad Amethyst + + diff --git a/vector/blobfox_snug.svg b/vector/blobfox_snug.svg index 1be4320..dc4d912 100644 --- a/vector/blobfox_snug.svg +++ b/vector/blobfox_snug.svg @@ -17,102 +17,159 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" - xmlns:dc="http://purl.org/dc/elements/1.1/">blobfox_snugBlobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 Licenseblobfox_snugFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfoxShad Amethyst + xmlns:dc="http://purl.org/dc/elements/1.1/"> + blobfox_snug + + + + + + + + + + + + + + + + + + + + + + + + + Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License + + + blobfox_snug + + + Feuerfuchs + + + https://git.shadamethyst.xyz/adri326/blobfox + + + Shad Amethyst + + + + + + diff --git a/vector/blobstella.svg b/vector/blobstella.svg new file mode 100644 index 0000000..4351565 --- /dev/null +++ b/vector/blobstella.svg @@ -0,0 +1,186 @@ + + + +blobcatBlobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 LicenseblobcatFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfox diff --git a/vector/knife.svg b/vector/knife.svg new file mode 100644 index 0000000..fc0a684 --- /dev/null +++ b/vector/knife.svg @@ -0,0 +1,103 @@ + + + +blobfoxBlobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 LicenseblobfoxFeuerfuchshttps://git.shadamethyst.xyz/adri326/blobfoxShad Amethyst