🔥 Snuggle generator [WIP]

pull/2/head
Shad Amethyst 2 years ago
parent 235722ff83
commit 1cd8665df7
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

1
.gitignore vendored

@ -2,3 +2,4 @@ original/
output/
Cargo.lock
target/
blobfox-*.zip

@ -20,3 +20,4 @@ resvg = "0.23"
usvg = "0.23"
tiny-skia = "0.6"
png = "0.17"
css-color-parser = "0.1.2"

@ -41,9 +41,21 @@ If you'd like to help, there are a few things that need attention outside of imp
- german shepherd
- collie
- sheep
- bird
- etc.
- clean up the SVG for the existing emotes (the `clean` binary in `feat/template` is meant to do the heavy-lifting)
### TODO
- set_stroke!
- blobfox_ohmy
- blobfox_trumpet
- blobfox_highfive
- googly eyes?
- tea/coffee
- sad
- uwu
## License
All the code, images and assets of this repository are made available under the Apache 2.0 license.

@ -0,0 +1,4 @@
name = "snuggle"
dx = -60
dy = -40
transform = "scale(1.02 1.02) translate(-1.5 -1)"

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" height="128" width="128" sodipodi:docname="blobfox_snuggle_right.svg" id="svg5" inkscape:version="1.1" viewBox="0 0 128 128">
<title id="title30762">blobfox</title>
<sodipodi:namedview id="namedview7" borderopacity="1" bordercolor="#ffffff" inkscape:window-height="779" inkscape:zoom="9.7841572" inkscape:pageopacity="0" inkscape:showpageshadow="2" inkscape:current-layer="g9491" units="px" inkscape:document-units="px" inkscape:window-maximized="1" inkscape:window-x="0" pagecolor="#505050" inkscape:pagecheckerboard="1" showgrid="false" inkscape:window-width="1536" inkscape:window-y="0" inkscape:cy="46.401544" inkscape:pageshadow="0" inkscape:deskcolor="#505050" inkscape:cx="59.68833">
<inkscape:grid type="xygrid" id="grid10"/>
</sodipodi:namedview>
<defs id="defs2"/>
<g style="display:inline" inkscape:label="ref" inkscape:groupmode="layer" id="ref">
<image id="blobfox" inkscape:label="blobfox" x="0" preserveAspectRatio="none" y="0" width="127.99999" xlink:href="../original/blobfox.png" style="display:none;fill:#313131;fill-opacity:1;stroke:none;image-rendering:optimizeQuality" height="127.99999"/>
<image id="blobfoxsnuggle" xlink:href="../original/blobfoxsnuggle.png" y="-106.29223" style="image-rendering:optimizeSpeed" preserveAspectRatio="none" inkscape:label="blobfoxsnuggle" height="221.0103" width="221.0103" x="-93.369904"/>
</g>
<g id="Base" inkscape:label="Base" inkscape:groupmode="layer" style="display:inline">
<path d="M 12.87231,40.818966 C -2.8719971,22.749014 -4.3550533,3.1367661 -2.9535978,0.24374984 -1.8493312,-2.0357805 -0.73468479,-3.6569086 1.9073133,-2.7558898 5.0260913,-1.6922712 17.794722,2.5508374 34.540893,8.7120265 c 7.146252,2.6292225 3.161601,9.4167855 1.87439,12.1261315 -2.856028,5.37387 -17.877727,10.71439 -23.542973,19.980808 z" id="left-ear" sodipodi:nodetypes="cssscc" style="display:inline;fill:#313131;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" inkscape:label="left-ear"/>
<path d="m 23.769283,6.3584073 c 2.991294,4.2455057 4.569848,7.4475027 8.895201,10.9888377 -12.070861,-0.541455 -15.260708,1.69375 -19.417541,5.559159 7.973832,0.09289 11.531043,0.522786 15.204226,1.155863 12.215874,2.105419 38.518977,5.035068 47.371493,-2.486915 4.32997,-3.679173 3.410201,-7.265503 -2.316952,-10.378575 C 67.610927,7.9925869 55.645095,4.8584742 51.222049,3.8438904 52.004054,6.0173862 53.35612,8.7380188 54.213344,11.268754 45.9815,7.6883395 37.893549,5.4027147 23.769283,6.3584073 Z" style="display:inline;fill:#ff8702;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.4501" sodipodi:nodetypes="cccsssccc" id="hair" inkscape:label="hair"/>
<path style="display:inline;fill:#ff8702;fill-opacity:1;stroke:none;stroke-width:2.64567;stroke-linecap:square;stroke-miterlimit:3" inkscape:label="body" d="m 6.0599734,91.649344 c 0,-8.73002 2.525321,-5.814368 2.6883167,-12.346173 C 8.9112858,72.771365 3.4640017,66.503444 3.0623888,53.796109 2.608823,39.444946 14.618107,15.642023 46.64457,15.642023 c 33.541601,0 57.93714,1.121349 70.19416,23.200939 12.45071,22.428499 12.91096,52.067554 6.38429,59.723645 -6.52667,7.656093 -22.73142,15.897513 -56.529275,15.897513 -33.797859,0 -60.6337716,-12.74755 -60.6337716,-22.814776 z" id="body" sodipodi:nodetypes="cssssssc"/>
<path style="display:inline;fill:#313131;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.4501" sodipodi:nodetypes="scscccss" d="m 109.65875,-11.393576 c -9.0534,1.6065407 -29.38453,9.7496393 -44.524796,30.678621 0.210609,4.044387 7.117027,6.692013 15.231023,5.036186 8.412314,-1.716705 21.594053,-22.0009765 26.970613,-27.4823623 1.00114,9.5497778 -12.65173,31.3913833 -4.57278,40.2752173 3.27823,3.604628 6.48404,-4.729898 7.41016,-7.69531 3.7465,-12.049663 6.22482,-21.0511645 6.3764,-27.8592275 0.0598,-2.686778 1.92218,-14.5169695 -6.89062,-12.9531245 z" inkscape:label="right-ear" id="right-ear"/>
<path id="right-ear-fluff" style="display:inline;fill:#ebdccc;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" inkscape:label="right-ear-fluff" d="M 109.7442,-7.9162135 C 101.79383,-4.5766893 95.432201,2.858004 93.256387,11.957451 92.338149,15.7976 86.384544,21.344383 83.242552,23.116446 c 3.859188,-0.486555 8.080586,-0.662358 9.623136,-0.582576 -0.593528,2.540618 -1.150986,5.084216 -1.270641,7.589149 2.095641,-2.400784 6.129327,-4.663902 8.596323,-5.42458 -0.870477,4.814098 -0.218062,7.843034 1.18668,10.437489 0.3855,-11.747093 7.77479,-14.345491 10.05823,-21.014332 1.71162,-4.9987961 4.75466,-15.8385462 1.25678,-21.4073931 -0.64101,-1.0205338 -2.00455,-1.0270711 -2.94886,-0.6304164 z" sodipodi:nodetypes="sscccccsss"/>
</g>
<g id="Features" inkscape:label="Features" transform="rotate(-10 45 75)" style="display:inline" inkscape:groupmode="layer">
<path id="nose-outline" d="m 33.65228,80.15316 c -1.84571,-3.806604 -1.872556,-7.54518 0.527665,-10.056125 2.400221,-2.510944 6.697437,-10.228997 6.681819,-14.101173" inkscape:label="nose-outline" sodipodi:nodetypes="csc" style="fill:none;fill-opacity:1;stroke:#313131;stroke-width:4.40315;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
<path inkscape:label="nose" d="m 40.349664,72.473149 c 3.611758,-1.970317 14.548618,-2.669295 17.071165,2.5758 0.40334,0.838655 -6.115736,7.143991 -10.106864,7.135136 -3.859049,-0.0087 -7.870186,-9.216748 -6.964301,-9.710936 z" style="fill:#313131;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" id="nose" sodipodi:nodetypes="ssss"/>
<path id="mouth" d="m 30.329986,85.466434 c 2.073506,6.374978 4.020579,10.401679 5.959243,12.940501 3.750744,-3.131043 8.538975,-7.475595 10.436736,-9.373813 2.962201,4.818455 7.017346,10.375135 8.671615,12.645698 4.859078,-3.873207 9.518861,-7.995561 11.43575,-10.162866" style="fill:none;stroke:#313131;stroke-width:4.40315;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" inkscape:label="mouth" sodipodi:nodetypes="ccccc"/>
<path d="m 24.641601,45.918665 c -4.075919,0.730189 -6.501377,3.925039 -6.273966,5.603347 0.227411,1.678308 1.347515,2.224702 3.8872,1.666965 2.397981,-0.52641 9.098771,-2.404689 17.303469,3.65651 2.148157,1.586937 4.650972,-0.810343 2.741314,-2.795355 -7.65445,-7.956509 -12.747952,-9.01107 -17.658017,-8.131467 z" sodipodi:nodetypes="ssssss" inkscape:label="left-eye" style="display:inline;fill:#313131;fill-opacity:1;stroke-width:4.45223;stroke-linecap:round" id="left-eye"/>
<path id="right-eye" inkscape:label="right-eye" sodipodi:nodetypes="ssssss" style="display:inline;fill:#313131;fill-opacity:1;stroke-width:4.41528;stroke-linecap:round" d="m 83.448042,49.242516 c 3.928913,1.194349 5.945041,4.623512 5.525393,6.249833 -0.419635,1.626313 -1.586453,2.033818 -4.022559,1.188423 -2.300205,-0.798011 -8.680288,-3.429088 -17.467185,1.58346 -2.300578,1.312383 -4.485891,-1.340335 -2.373777,-3.072561 8.46599,-6.943249 13.605145,-7.38791 18.338128,-5.949155 z"/>
</g>
<metadata id="metadata30760">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:rights>
<cc:Agent>
<dc:title>Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License</dc:title>
</cc:Agent>
</dc:rights>
<dc:title>blobfox</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Feuerfuchs</dc:title>
</cc:Agent>
</dc:creator>
<dc:source>https://git.shadamethyst.xyz/adri326/blobfox</dc:source>
<dc:contributor>
<cc:Agent>
<dc:title>Shad Amethyst</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

@ -6,6 +6,7 @@ body_color = "#ff8702"
ear_color = "#313131"
ear_fluff_color = "#ebdccc"
hand_color = "#ff8702"
hand_stroke_color = "#313131"
tail_color = "#ff8702"
[variants]
@ -51,3 +52,7 @@ heart_enby = ["body-basic", "eyes-basic", "left-hand", "right-hand", "holding",
heart_ace = ["body-basic", "eyes-basic", "left-hand", "right-hand", "holding", "big-object"]
heart_demisexual = ["body-basic", "eyes-basic", "left-hand", "right-hand", "holding", "big-object"]
heart_pan = ["body-basic", "eyes-basic", "left-hand", "right-hand", "holding", "big-object"]
# Snuggle
snuggle_right = ["body-snuggle", "eyes-snuggle", "mouth-w"]
snuggle_right_shadow = ["body-snuggle", "eyes-snuggle", "mouth-w"]

@ -0,0 +1,15 @@
{{! Left ear }}
{{#set-fill}} {{vars.ear_color}} | {{#snuggle_right}}#left-ear{{/snuggle_right}} {{/set-fill}}
{{! Body }}
<defs>
<clipPath id="body-clip">
{{#snuggle_right}}#body{{/snuggle_right}}
</clipPath>
</defs>
{{#set-fill}} {{vars.body_color}} | {{#snuggle_right}}#body{{/snuggle_right}} {{/set-fill}}
{{#set-fill}} {{vars.body_color}} | {{#snuggle_right}}#hair{{/snuggle_right}} {{/set-fill}}
{{! Right ear }}
{{#set-fill}} {{vars.ear_color}} | {{#snuggle_right}}#right-ear{{/snuggle_right}} {{/set-fill}}
{{#set-fill}} {{vars.ear_fluff_color}} | {{#snuggle_right}}#right-ear-fluff{{/snuggle_right}} {{/set-fill}}

@ -8,4 +8,7 @@
{{#tags.body-comfy}}
{{>body-comfy}}
{{/tags.body-comfy}}
{{#tags.body-snuggle}}
{{>body-snuggle}}
{{/tags.body-snuggle}}
</g>

@ -23,4 +23,8 @@
{{#blush}}#left-eye{{/blush}}
{{#blush}}#right-eye{{/blush}}
{{/tags.eyes-closed}}
{{#tags.eyes-snuggle}}
{{#snuggle_right}}#left-eye{{/snuggle_right}}
{{#snuggle_right}}#right-eye{{/snuggle_right}}
{{/tags.eyes-snuggle}}
</g>

@ -1,7 +1,19 @@
<g id="hands">
{{#tags.hands-reach}}
{{#set-fill}} {{vars.hand_color}} | {{#reach_aww}}#left-hand{{/reach_aww}} {{/set-fill}}
{{#set-fill}} {{vars.hand_color}} | {{#reach_aww}}#right-hand{{/reach_aww}} {{/set-fill}}
{{#set-stroke}}
{{vars.hand_stroke_color}} |
{{#set-fill}}
{{vars.hand_color}}
| {{#reach_aww}}#left-hand{{/reach_aww}}
{{/set-fill}}
{{/set-stroke}}
{{#set-stroke}}
{{vars.hand_stroke_color}} |
{{#set-fill}}
{{vars.hand_color}}
| {{#reach_aww}}#right-hand{{/reach_aww}}
{{/set-fill}}
{{/set-stroke}}
{{/tags.hands-reach}}
{{#tags.hand-3c}}
{{#tags.holding}}

@ -14,6 +14,9 @@
{{#tags.eyes-closed}}
{{#blush}}#nose-outline{{/blush}}
{{/tags.eyes-closed}}
{{#tags.eyes-snuggle}}
{{#snuggle_right}}#nose-outline{{/snuggle_right}}
{{/tags.eyes-snuggle}}
{{#base}}#nose{{/base}}
</g>

@ -9,7 +9,11 @@
</clipPath>
</defs>
<g clip-path="url(#clip-heart)">
<g blobfox-only-size="true">
{{#heart}}#heart{{/heart}}
</g>
<g clip-path="url(#clip-heart)" blobfox-ignore-size="true">
<g transform="rotate(9) scale(1.05 1.05) translate(10 45)">
{{#flag_ace}}{{/flag_ace}}
</g>

@ -9,7 +9,11 @@
</clipPath>
</defs>
<g clip-path="url(#clip-heart)">
<g blobfox-only-size="true">
{{#heart}}#heart{{/heart}}
</g>
<g clip-path="url(#clip-heart)" blobfox-ignore-size="true">
<g transform="rotate(9) scale(1.05 1.05) translate(10 45)">
{{#flag_demisexual}}{{/flag_demisexual}}
</g>

@ -9,7 +9,11 @@
</clipPath>
</defs>
<g clip-path="url(#clip-heart)">
<g blobfox-only-size="true">
{{#heart}}#heart{{/heart}}
</g>
<g clip-path="url(#clip-heart)" blobfox-ignore-size="true">
<g transform="rotate(9) scale(1.05 1.05) translate(10 45)">
{{#flag_enby}}{{/flag_enby}}
</g>

@ -9,7 +9,11 @@
</clipPath>
</defs>
<g clip-path="url(#clip-heart)">
<g blobfox-only-size="true">
{{#heart}}#heart{{/heart}}
</g>
<g clip-path="url(#clip-heart)" blobfox-ignore-size="true">
<g transform="rotate(9) scale(1.05 1.05) translate(10 45)">
{{#flag_pan}}{{/flag_pan}}
</g>

@ -9,7 +9,11 @@
</clipPath>
</defs>
<g clip-path="url(#clip-heart)">
<g blobfox-only-size="true">
{{#heart}}#heart{{/heart}}
</g>
<g clip-path="url(#clip-heart)" blobfox-ignore-size="true">
<g transform="rotate(9) scale(0.95 0.95) translate(20 50)">
{{#flag_progress}}{{/flag_progress}}
</g>

@ -0,0 +1,9 @@
{{>header}}
{{>body}}
<g transform="rotate(-10 45 75)">
{{>eyes}}
{{>nose}}
{{>mouth}}
</g>
{{>footer}}

@ -0,0 +1,148 @@
//! Very crude tool for generating snuggle emotes
use clap::Parser;
use std::fmt::Write;
use std::path::PathBuf;
use serde::{Serialize, Deserialize};
use xmltree::{Element, XMLNode};
use blobfox_template::{
parse,
template,
export,
};
#[derive(Serialize, Deserialize, Debug)]
struct Desc {
dx: f64,
dy: f64,
scale: Option<f64>,
#[serde(default)]
transform: String,
}
fn main() {
let args = Args::parse();
let left = std::fs::read_to_string(&args.input_left).unwrap_or_else(|err| {
panic!("Couldn't open {}: {}", args.input_right.display(), err);
});
let right = std::fs::read_to_string(&args.input_right).unwrap_or_else(|err| {
panic!("Couldn't open {}: {}", args.input_right.display(), err);
});
let desc = std::fs::read_to_string(&args.desc).unwrap_or_else(|err| {
panic!("Couldn't open {}: {}", args.desc.display(), err);
});
let desc: Desc = toml::from_str(&desc).unwrap();
let snuggle = generate_snuggle(left, right, desc);
let snuggle = export::xml_to_str(&snuggle).unwrap();
let output_dir = args.output_dir.clone().unwrap_or(PathBuf::from("output/"));
export::export(
snuggle,
&output_dir,
args.name.clone(),
&args.clone().into()
).unwrap();
}
fn generate_snuggle(left: String, right: String, desc: Desc) -> Element {
let left_usvg = export::get_usvg(&left).unwrap();
let left_bbox = left_usvg.svg_node().view_box.rect;
//
let mut mask = Element::new("mask");
mask.attributes.insert("id".to_string(), "snuggle-mask".to_string());
let mut rect = Element::new("rect");
rect.attributes.insert("fill".to_string(), "white".to_string());
// TODO: use scale?
rect.attributes.insert("x".to_string(), (desc.dx + left_bbox.x()).to_string());
rect.attributes.insert("y".to_string(), (desc.dy + left_bbox.y()).to_string());
rect.attributes.insert("width".to_string(), left_bbox.width().to_string());
rect.attributes.insert("height".to_string(), left_bbox.height().to_string());
mask.children.push(XMLNode::Element(rect));
let mut right_mask = Element::new("g");
right_mask.attributes.insert("transform".to_string(), desc.transform);
let mut right_xml = Element::parse(right.as_bytes()).unwrap();
template::set_fill("#000000", &mut right_xml);
template::set_stroke("#000000", &mut right_xml);
for child in right_xml.children {
if let XMLNode::Element(child) = child {
right_mask.children.push(XMLNode::Element(child));
}
}
mask.children.push(XMLNode::Element(right_mask));
let mut right_xml = Element::parse(right.as_bytes()).unwrap();
let left_xml = Element::parse(left.as_bytes()).unwrap();
let mut left_group = Element::new("g");
left_group.attributes.insert("transform".to_string(), format!(
"translate({} {})",
desc.dx,
desc.dy
));
left_group.children = left_xml.children;
let mut left_group2 = Element::new("g");
left_group2.attributes.insert("mask".to_string(), "url(#snuggle-mask)".to_string());
left_group2.children.push(XMLNode::Element(left_group));
let mut res = Element::new("svg");
res.attributes.insert("xmlns".to_string(), "http://www.w3.org/2000/svg".to_string());
res.attributes.insert("version".to_string(), "1.1".to_string());
res.attributes.insert("width".to_string(), "128".to_string());
res.attributes.insert("height".to_string(), "128".to_string());
res.children.push(XMLNode::Element(mask));
res.children.append(&mut right_xml.children);
res.children.push(XMLNode::Element(left_group2));
res
}
#[derive(Parser, Clone)]
#[clap(author, version, about, long_about = None)]
struct Args {
#[clap(value_parser)]
input_left: PathBuf,
#[clap(value_parser)]
input_right: PathBuf,
#[clap(short, long, value_parser)]
desc: PathBuf,
/// Disable automatically resizing the SVG's viewBox, defaults to false
#[clap(short, long, value_parser, default_value = "false")]
no_resize: bool,
#[clap(long, value_parser)]
name: String,
/// Dimension to export the images as; can be specified multiple times
#[clap(long, value_parser)]
dim: Vec<u32>,
/// Output directory
#[clap(short, long, value_parser)]
output_dir: Option<PathBuf>,
}
impl From<Args> for export::ExportArgs {
fn from(args: Args) -> export::ExportArgs {
export::ExportArgs {
no_resize: args.no_resize,
dim: args.dim,
}
}
}

@ -48,6 +48,28 @@ impl From<png::EncodingError> for ExportError {
}
}
pub struct ExportArgs {
pub no_resize: bool,
pub dim: Vec<u32>,
}
pub fn get_usvg(svg_str: &str) -> Result<usvg::Tree, usvg::Error> {
let usvg_options = Options::default();
Tree::from_str(svg_str, &usvg_options.to_ref())
}
pub fn get_xml(svg_str: &str) -> Result<Element, xmltree::ParseError> {
Element::parse(svg_str.as_bytes())
}
pub fn xml_to_str(svg_xml: &Element) -> Result<String, ExportError> {
let mut s: Vec<u8> = Vec::new();
svg_xml.write(&mut s)?;
Ok(String::from_utf8(s)?)
}
fn get_new_bbox(svg: &Tree) -> Option<(f64, f64, f64, f64)> {
let bbox = svg.root().calculate_bbox()?;
@ -70,25 +92,45 @@ fn get_new_bbox(svg: &Tree) -> Option<(f64, f64, f64, f64)> {
}
}
fn get_usvg(svg_str: &str) -> Result<usvg::Tree, usvg::Error> {
let usvg_options = Options::default();
Tree::from_str(svg_str, &usvg_options.to_ref())
}
/// Removes all elements marked with `blobfox-ignore-size="true"`
fn get_xml(svg_str: &str) -> Result<Element, xmltree::ParseError> {
Element::parse(svg_str.as_bytes())
macro_rules! strip {
( $name:tt, $attribute:expr ) => {
fn $name(svg_str: &str) -> Result<String, ExportError> {
let mut xml = get_xml(svg_str)?;
fn rec(element: &mut Element) {
// TODO: replace with Vec::drain_filter once https://github.com/rust-lang/rust/issues/43244 gets merged
for child in std::mem::take(&mut element.children) {
match child {
XMLNode::Element(mut child) => {
if let Some("true") = child.attributes.get($attribute).map(|s| s.as_str()) {
continue
}
fn xml_to_str(svg_xml: &Element) -> Result<String, ExportError> {
let mut s: Vec<u8> = Vec::new();
rec(&mut child);
svg_xml.write(&mut s)?;
element.children.push(XMLNode::Element(child));
}
child => element.children.push(child),
}
}
}
Ok(String::from_utf8(s)?)
rec(&mut xml);
xml_to_str(&xml)
}
}
}
strip!(strip_ignore_size, "blobfox-ignore-size");
strip!(strip_only_size, "blobfox-only-size");
pub fn resize(svg_str: String) -> Result<String, ExportError> {
if let Some(new_bbox) = get_new_bbox(&get_usvg(&svg_str)?) {
let stripped = strip_ignore_size(&svg_str)?;
if let Some(new_bbox) = get_new_bbox(&get_usvg(&stripped)?) {
let mut svg_xml = get_xml(&svg_str)?;
svg_xml.attributes.insert(
"viewBox".to_string(),
@ -144,12 +186,14 @@ pub fn export(
mut svg_str: String,
output_dir: &PathBuf,
output_name: String,
args: &super::Args,
args: &ExportArgs,
) -> Result<(), ExportError> {
if !args.no_resize {
svg_str = resize(svg_str)?;
}
svg_str = strip_only_size(&svg_str)?;
svg_str = combine_defs(svg_str)?;
mkdirp::mkdirp(output_dir.join("vector")).unwrap();

@ -0,0 +1,3 @@
pub mod parse;
pub mod template;
pub mod export;

@ -1,14 +1,11 @@
use clap::Parser;
use std::path::PathBuf;
pub mod parse;
use parse::*;
pub mod template;
use template::*;
pub mod export;
use export::*;
use blobfox_template::{
parse::*,
template::*,
export::*,
};
fn main() {
let args = Args::parse();
@ -30,6 +27,8 @@ fn main() {
}
fn generate_variant(context: &RenderingContext, name: &str, output_dir: &PathBuf, args: &Args) {
let args: ExportArgs = args.clone().into();
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))
@ -39,7 +38,7 @@ fn generate_variant(context: &RenderingContext, name: &str, output_dir: &PathBuf
svg,
output_dir,
format!("{}_{}", context.species().name, name),
args
&args
) {
Ok(_) => {}
Err(err) => {
@ -56,7 +55,7 @@ fn generate_variant(context: &RenderingContext, name: &str, output_dir: &PathBuf
}
}
#[derive(Parser, Debug)]
#[derive(Parser, Debug, Clone)]
#[clap(author, version, about, long_about = None)]
pub struct Args {
/// A folder containing the declaration from which the emotes should be generated
@ -79,3 +78,12 @@ pub struct Args {
#[clap(short, long, value_parser)]
output_dir: Option<PathBuf>,
}
impl From<Args> for ExportArgs {
fn from(args: Args) -> ExportArgs {
ExportArgs {
no_resize: args.no_resize,
dim: args.dim,
}
}
}

@ -1,9 +1,10 @@
use super::*;
use crate::parse::SpeciesDecl;
use mustache::{Context, Data, MapBuilder, PartialLoader, Template};
use std::collections::HashMap;
use std::path::Path;
use std::sync::{Arc, Mutex};
use xmltree::{Element, XMLNode};
use css_color_parser::Color as CssColor;
#[derive(Debug, Clone)]
pub struct RenderingContext {
@ -90,9 +91,14 @@ impl RenderingContext {
});
}
for (cb, name) in [
(set_fill as fn(&str, &mut Element), "set-fill"),
(set_stroke, "set-stroke")
] {
let this = self.clone();
let variant_name_owned = variant_name.to_string();
builder = builder.insert_fn("set-fill", move |input| {
builder = builder.insert_fn(name, move |input| {
// Parse `color|xml`
if let [color, xml] = input.splitn(2, '|').collect::<Vec<_>>()[..] {
// Render `color` and `xml`
@ -103,7 +109,7 @@ impl RenderingContext {
// Convert `xml` to XML
match Element::parse(xml.as_bytes()) {
Ok(mut xml) => {
set_fill(&color.trim(), &mut xml);
cb(&color.trim(), &mut xml);
// Render XML to string
if let Some(res) = xml_to_string(xml) {
@ -123,6 +129,7 @@ impl RenderingContext {
String::from("<!-- Invalid syntax: expected `color|xml` -->")
}
});
}
builder = builder.insert("vars", &self.species.vars).unwrap();
@ -236,34 +243,54 @@ impl PartialLoader for RenderingContext {
}
}
fn set_fill(color: &str, xml: &mut Element) {
// Substitute the fill color; TODO: handle transparency for SVG 1.1
macro_rules! set_color {
( $fn_name:tt, $color_name:expr, $opacity_name:expr ) => {
pub fn $fn_name(color: &str, xml: &mut Element) {
let (color, opacity) = if let Ok(parsed) = color.parse::<CssColor>() {
(format!("#{:02x}{:02x}{:02x}", parsed.r, parsed.g, parsed.b), parsed.a)
} else {
(color.to_string(), 1.0)
};
fn rec(color: &str, opacity: f32, xml: &mut Element) {
if let Some(style) = xml.attributes.get_mut("style") {
let mut new_style = Vec::new();
for rule in style.split(';') {
if let [name, value] = rule.splitn(2, ':').collect::<Vec<_>>()[..] {
if name.trim() != "fill" && name.trim() != "fill-opacity" {
if name.trim() != $color_name && name.trim() != $opacity_name {
new_style.push(format!("{}:{}", name, value));
}
}
}
new_style.push(format!("fill: {};", color));
new_style.push(format!(concat!($color_name, ": {};"), color));
new_style.push(format!(concat!($opacity_name, ": {};"), opacity));
*style = new_style.join(";");
}
if let Some(_fill) = xml.attributes.get("fill") {
xml.attributes.insert("fill".to_string(), color.to_string());
if let Some(_fill) = xml.attributes.get($color_name) {
xml.attributes.insert($color_name.to_string(), color.to_string());
}
if let Some(_fill) = xml.attributes.get($opacity_name) {
xml.attributes.insert($opacity_name.to_string(), opacity.to_string());
}
for child in xml.children.iter_mut() {
if let XMLNode::Element(ref mut child) = child {
set_fill(color, child);
rec(color, opacity, child);
}
}
}
rec(&color, opacity, xml)
}
}
}
set_color!(set_fill, "fill", "fill-opacity");
set_color!(set_stroke, "stroke", "stroke-opacity");
pub fn query_selector(svg: Element, pattern: &str) -> Option<Element> {
if pattern == "" {
// NOTE: it looks like having a nested svg makes resvg unhappy

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
viewBox="0 0 128 128"
width="128"
sodipodi:docname="blobfox_snuggle_right.svg"
id="svg5"
height="128"
version="1.1"
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/"><title
id="title30762">blobfox</title><sodipodi:namedview
inkscape:window-maximized="1"
bordercolor="#ffffff"
inkscape:showpageshadow="2"
inkscape:window-height="779"
inkscape:pageopacity="0"
borderopacity="1"
inkscape:cy="46.401544"
inkscape:window-x="0"
id="namedview7"
pagecolor="#505050"
inkscape:pageshadow="0"
showgrid="false"
inkscape:zoom="9.7841572"
inkscape:window-width="1536"
inkscape:window-y="0"
inkscape:current-layer="g9491"
inkscape:cx="59.68833"
units="px"
inkscape:pagecheckerboard="1"
inkscape:document-units="px"
inkscape:deskcolor="#505050"><inkscape:grid
id="grid10"
type="xygrid" /></sodipodi:namedview><defs
id="defs2" /><g
style="display:inline"
id="layer2"
inkscape:groupmode="layer"
inkscape:label="ref"><image
inkscape:label="blobfox"
y="0"
height="127.99999"
width="127.99999"
style="display:none;fill:#313131;fill-opacity:1;stroke:none;image-rendering:optimizeQuality"
x="0"
preserveAspectRatio="none"
xlink:href="../original/blobfox.png"
id="image80" /><image
width="221.0103"
height="221.0103"
preserveAspectRatio="none"
style="image-rendering:optimizeSpeed"
xlink:href="../original/blobfoxsnuggle.png"
id="image545"
x="-93.369904"
y="-106.29223"
inkscape:label="blobfoxsnuggle" /></g><g
inkscape:label="Base"
id="layer1"
inkscape:groupmode="layer"
style="display:inline"><path
sodipodi:nodetypes="cssscc"
style="display:inline;fill:#313131;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 12.87231,40.818966 C -2.8719971,22.749014 -4.3550533,3.1367661 -2.9535978,0.24374984 -1.8493312,-2.0357805 -0.73468479,-3.6569086 1.9073133,-2.7558898 5.0260913,-1.6922712 17.794722,2.5508374 34.540893,8.7120265 c 7.146252,2.6292225 3.161601,9.4167855 1.87439,12.1261315 -2.856028,5.37387 -17.877727,10.71439 -23.542973,19.980808 z"
id="path20678"
inkscape:label="left-ear" /><path
id="path117-7"
inkscape:label="hair"
sodipodi:nodetypes="cccsssccc"
style="display:inline;fill:#ff8702;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.4501"
d="m 23.769283,6.3584073 c 2.991294,4.2455057 4.569848,7.4475027 8.895201,10.9888377 -12.070861,-0.541455 -15.260708,1.69375 -19.417541,5.559159 7.973832,0.09289 11.531043,0.522786 15.204226,1.155863 12.215874,2.105419 38.518977,5.035068 47.371493,-2.486915 4.32997,-3.679173 3.410201,-7.265503 -2.316952,-10.378575 C 67.610927,7.9925869 55.645095,4.8584742 51.222049,3.8438904 52.004054,6.0173862 53.35612,8.7380188 54.213344,11.268754 45.9815,7.6883395 37.893549,5.4027147 23.769283,6.3584073 Z" /><path
style="display:inline;fill:#ff8702;fill-opacity:1;stroke:none;stroke-width:2.64567;stroke-linecap:square;stroke-miterlimit:3"
d="m 6.0599734,91.649344 c 0,-8.73002 2.525321,-5.814368 2.6883167,-12.346173 C 8.9112858,72.771365 3.4640017,66.503444 3.0623888,53.796109 2.608823,39.444946 14.618107,15.642023 46.64457,15.642023 c 33.541601,0 57.93714,1.121349 70.19416,23.200939 12.45071,22.428499 12.91096,52.067554 6.38429,59.723645 -6.52667,7.656093 -22.73142,15.897513 -56.529275,15.897513 -33.797859,0 -60.6337716,-12.74755 -60.6337716,-22.814776 z"
id="path8285"
sodipodi:nodetypes="cssssssc"
inkscape:label="body" /><path
d="m 109.65875,-11.393576 c -9.0534,1.6065407 -29.38453,9.7496393 -44.524796,30.678621 0.210609,4.044387 7.117027,6.692013 15.231023,5.036186 8.412314,-1.716705 21.594053,-22.0009765 26.970613,-27.4823623 1.00114,9.5497778 -12.65173,31.3913833 -4.57278,40.2752173 3.27823,3.604628 6.48404,-4.729898 7.41016,-7.69531 3.7465,-12.049663 6.22482,-21.0511645 6.3764,-27.8592275 0.0598,-2.686778 1.92218,-14.5169695 -6.89062,-12.9531245 z"
id="path14428"
style="display:inline;fill:#313131;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.4501"
inkscape:label="right-ear"
sodipodi:nodetypes="scscccss" /><path
id="path18942"
style="display:inline;fill:#ebdccc;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
inkscape:label="right-ear-fluff"
d="M 109.7442,-7.9162135 C 101.79383,-4.5766893 95.432201,2.858004 93.256387,11.957451 92.338149,15.7976 86.384544,21.344383 83.242552,23.116446 c 3.859188,-0.486555 8.080586,-0.662358 9.623136,-0.582576 -0.593528,2.540618 -1.150986,5.084216 -1.270641,7.589149 2.095641,-2.400784 6.129327,-4.663902 8.596323,-5.42458 -0.870477,4.814098 -0.218062,7.843034 1.18668,10.437489 0.3855,-11.747093 7.77479,-14.345491 10.05823,-21.014332 1.71162,-4.9987961 4.75466,-15.8385462 1.25678,-21.4073931 -0.64101,-1.0205338 -2.00455,-1.0270711 -2.94886,-0.6304164 z"
sodipodi:nodetypes="sscccccsss" /></g><g
id="layer3"
inkscape:groupmode="layer"
inkscape:label="Features"
style="display:inline" transform="rotate(-10 45 75)"><path
d="m 33.65228,80.15316 c -1.84571,-3.806604 -1.872556,-7.54518 0.527665,-10.056125 2.400221,-2.510944 6.697437,-10.228997 6.681819,-14.101173"
sodipodi:nodetypes="csc"
inkscape:label="nose-outline"
id="path27175"
style="fill:none;fill-opacity:1;stroke:#313131;stroke-width:4.40315;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /><path
id="path28517"
style="fill:#313131;fill-opacity:1;stroke:none;stroke-width:0.999999px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
sodipodi:nodetypes="ssss"
d="m 40.349664,72.473149 c 3.611758,-1.970317 14.548618,-2.669295 17.071165,2.5758 0.40334,0.838655 -6.115736,7.143991 -10.106864,7.135136 -3.859049,-0.0087 -7.870186,-9.216748 -6.964301,-9.710936 z"
inkscape:label="nose" /><path
inkscape:label="mouth"
id="path29369"
style="fill:none;stroke:#313131;stroke-width:4.40315;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 30.329986,85.466434 c 2.073506,6.374978 4.020579,10.401679 5.959243,12.940501 3.750744,-3.131043 8.538975,-7.475595 10.436736,-9.373813 2.962201,4.818455 7.017346,10.375135 8.671615,12.645698 4.859078,-3.873207 9.518861,-7.995561 11.43575,-10.162866"
sodipodi:nodetypes="ccccc" /><path
style="display:inline;fill:#313131;fill-opacity:1;stroke-width:4.45223;stroke-linecap:round"
sodipodi:nodetypes="ssssss"
d="m 24.641601,45.918665 c -4.075919,0.730189 -6.501377,3.925039 -6.273966,5.603347 0.227411,1.678308 1.347515,2.224702 3.8872,1.666965 2.397981,-0.52641 9.098771,-2.404689 17.303469,3.65651 2.148157,1.586937 4.650972,-0.810343 2.741314,-2.795355 -7.65445,-7.956509 -12.747952,-9.01107 -17.658017,-8.131467 z"
inkscape:label="left-eye"
id="left-eye" /><path
sodipodi:nodetypes="ssssss"
style="display:inline;fill:#313131;fill-opacity:1;stroke-width:4.41528;stroke-linecap:round"
d="m 83.448042,49.242516 c 3.928913,1.194349 5.945041,4.623512 5.525393,6.249833 -0.419635,1.626313 -1.586453,2.033818 -4.022559,1.188423 -2.300205,-0.798011 -8.680288,-3.429088 -17.467185,1.58346 -2.300578,1.312383 -4.485891,-1.340335 -2.373777,-3.072561 8.46599,-6.943249 13.605145,-7.38791 18.338128,-5.949155 z"
id="path456-3"
inkscape:label="right-eye" /></g><metadata
id="metadata30760"><rdf:RDF><cc:Work
rdf:about=""><dc:rights><cc:Agent><dc:title>Blobfox team (https://git.shadamethyst.xyz/adri326/blobfox), licensed under the Apache 2.0 License</dc:title></cc:Agent></dc:rights><dc:title>blobfox</dc:title><dc:creator><cc:Agent><dc:title>Feuerfuchs</dc:title></cc:Agent></dc:creator><dc:source>https://git.shadamethyst.xyz/adri326/blobfox</dc:source><dc:contributor><cc:Agent><dc:title>Shad Amethyst</dc:title></cc:Agent></dc:contributor></cc:Work></rdf:RDF></metadata></svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

Loading…
Cancel
Save