chore: generate all SVG assets via rust builders
This commit is contained in:
parent
15287674f1
commit
cb66f64896
10 changed files with 1042 additions and 405 deletions
53
assets/logo.svg
Normal file
53
assets/logo.svg
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="320" viewBox="0 0 640 320" role="img" aria-labelledby="title desc">
|
||||
<title id="title">LPKG Logo</title>
|
||||
<desc id="desc">Stylised package icon with circuitry and the letters LPKG.</desc>
|
||||
<defs>
|
||||
<linearGradient id="bgGradient" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#0f172a" />
|
||||
<stop offset="100%" stop-color="#1e293b" />
|
||||
</linearGradient>
|
||||
<linearGradient id="cubeGradient" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#38bdf8" />
|
||||
<stop offset="100%" stop-color="#0ea5e9" />
|
||||
</linearGradient>
|
||||
<linearGradient id="cubeShadow" x1="0" y1="1" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#0ea5e9" stop-opacity="0.4" />
|
||||
<stop offset="100%" stop-color="#38bdf8" stop-opacity="0.1" />
|
||||
</linearGradient>
|
||||
<linearGradient id="textGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#f8fafc" />
|
||||
<stop offset="100%" stop-color="#cbd5f5" />
|
||||
</linearGradient>
|
||||
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur stdDeviation="8" result="blur" />
|
||||
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="640" height="320" rx="28" fill="url(#bgGradient)" />
|
||||
<g transform="translate(100 60)">
|
||||
<g filter="url(#glow)">
|
||||
<path d="M222 86l86-42 86 42v96l-86 42-86-42z" fill="url(#cubeGradient)" />
|
||||
<path d="M308 44v182l86-42V86z" fill="url(#cubeShadow)" />
|
||||
<path d="M262 96l46-22 46 22v48l-46 22-46-22z" fill="#0f172a" opacity="0.85" />
|
||||
<path d="M308 74l32 15v32l-32 15-32-15v-32z" fill="none" stroke="#38bdf8" stroke-width="4" stroke-linejoin="round" />
|
||||
<path d="M308 122l-32-15" stroke="#38bdf8" stroke-width="4" stroke-linecap="round" opacity="0.6" />
|
||||
<path d="M308 122l32-15" stroke="#38bdf8" stroke-width="4" stroke-linecap="round" opacity="0.6" />
|
||||
<circle cx="276" cy="107" r="5" fill="#38bdf8" />
|
||||
<circle cx="340" cy="107" r="5" fill="#38bdf8" />
|
||||
</g>
|
||||
</g>
|
||||
<g fill="none" stroke="#38bdf8" stroke-width="3" stroke-linecap="round" opacity="0.55">
|
||||
<path d="M120 78h72" />
|
||||
<path d="M120 110h48" />
|
||||
<path d="M120 142h64" />
|
||||
<path d="M448 110h72" />
|
||||
<path d="M472 142h88" />
|
||||
<path d="M448 174h96" />
|
||||
</g>
|
||||
<g font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-weight="600" font-size="90" letter-spacing="6">
|
||||
<text x="120" y="246" fill="url(#textGradient)">LPKG</text>
|
||||
</g>
|
||||
<g font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-size="22" fill="#94a3b8">
|
||||
<text x="122" y="278">Lightweight Package Manager</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
33
assets/nixette-logo.svg
Normal file
33
assets/nixette-logo.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="200" viewBox="0 0 640 200" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Nixette Logo</title>
|
||||
<desc id="desc">Wordmark combining Nix and Gentoo motifs with trans pride colours.</desc>
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#55CDFC" />
|
||||
<stop offset="100%" stop-color="#F7A8B8" />
|
||||
</linearGradient>
|
||||
<linearGradient id="text" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#FFFFFF" />
|
||||
<stop offset="100%" stop-color="#E5E7FF" />
|
||||
</linearGradient>
|
||||
<filter id="softShadow" x="-10%" y="-10%" width="120%" height="120%">
|
||||
<feDropShadow dx="0" dy="6" stdDeviation="12" flood-color="#7C3AED" flood-opacity="0.3" />
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="640" height="200" rx="36" fill="#0F172A" />
|
||||
<g transform="translate(100 60)">
|
||||
<g filter="url(#softShadow)">
|
||||
<path d="M40 40 L72 0 L144 0 L176 40 L144 80 L72 80 Z" fill="url(#bg)" />
|
||||
<path d="M72 0 L144 80" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity="0.55" />
|
||||
<path d="M144 0 L72 80" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity="0.55" />
|
||||
<circle cx="108" cy="40" r="22" fill="#0F172A" stroke="#FFFFFF" stroke-width="6" opacity="0.85" />
|
||||
<path d="M108 24c8 0 14 6 14 16s-6 16-14 16" stroke="#F7A8B8" stroke-width="4" stroke-linecap="round" fill="none" />
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(220 126)" font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-weight="700" font-size="72" letter-spacing="4" fill="url(#text)">
|
||||
<text>NIXETTE</text>
|
||||
</g>
|
||||
<g transform="translate(220 160)" font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-size="22" fill="#A5B4FC">
|
||||
<text>Declarative · Sourceful · Herself</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
50
assets/nixette-mascot.svg
Normal file
50
assets/nixette-mascot.svg
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="480" height="520" viewBox="0 0 480 520" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Nixette Mascot Badge</title>
|
||||
<desc id="desc">Chibi penguin mascot with trans flag hair, blending Nix and Gentoo motifs.</desc>
|
||||
<defs>
|
||||
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#312E81" />
|
||||
<stop offset="100%" stop-color="#1E1B4B" />
|
||||
</linearGradient>
|
||||
<linearGradient id="hairLeft" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#55CDFC" />
|
||||
<stop offset="100%" stop-color="#0EA5E9" />
|
||||
</linearGradient>
|
||||
<linearGradient id="hairRight" x1="1" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#F7A8B8" />
|
||||
<stop offset="100%" stop-color="#FB7185" />
|
||||
</linearGradient>
|
||||
<linearGradient id="bellyGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#FFFFFF" />
|
||||
<stop offset="100%" stop-color="#E2E8F0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="480" height="520" rx="48" fill="url(#bgGrad)" />
|
||||
<g transform="translate(240 220)">
|
||||
<path d="M-160 -20 C-140 -160 140 -160 160 -20 C180 140 60 220 0 220 C-60 220 -180 140 -160 -20" fill="#0F172A" />
|
||||
<ellipse cx="0" cy="40" rx="120" ry="140" fill="#1E293B" />
|
||||
<path d="M-88 -80 Q-40 -140 0 -120 Q40 -140 88 -80" fill="#1E293B" />
|
||||
<path d="M-96 -84 Q-60 -160 -8 -132 L-8 -40 Z" fill="url(#hairLeft)" />
|
||||
<path d="M96 -84 Q60 -160 8 -132 L8 -40 Z" fill="url(#hairRight)" />
|
||||
<ellipse cx="-44" cy="-8" rx="26" ry="32" fill="#FFFFFF" />
|
||||
<ellipse cx="44" cy="-8" rx="26" ry="32" fill="#FFFFFF" />
|
||||
<circle cx="-44" cy="-4" r="14" fill="#0F172A" />
|
||||
<circle cx="44" cy="-4" r="14" fill="#0F172A" />
|
||||
<circle cx="-40" cy="-8" r="6" fill="#FFFFFF" opacity="0.7" />
|
||||
<circle cx="48" cy="-10" r="6" fill="#FFFFFF" opacity="0.7" />
|
||||
<path d="M0 12 L-18 32 Q0 44 18 32 Z" fill="#F472B6" />
|
||||
<path d="M0 32 L-16 52 Q0 60 16 52 Z" fill="#FBEAED" />
|
||||
<path d="M0 46 Q-32 78 0 86 Q32 78 0 46" fill="#FCA5A5" />
|
||||
<ellipse cx="0" cy="74" rx="70" ry="82" fill="url(#bellyGrad)" />
|
||||
<path d="M-128 48 Q-176 56 -176 120 Q-128 112 -104 80" fill="#F7A8B8" />
|
||||
<path d="M128 48 Q176 56 176 120 Q128 112 104 80" fill="#55CDFC" />
|
||||
<circle cx="-100" cy="94" r="18" fill="#FDE68A" opacity="0.85" />
|
||||
<circle cx="100" cy="94" r="18" fill="#FDE68A" opacity="0.85" />
|
||||
</g>
|
||||
<g transform="translate(90 420)" font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-size="42" fill="#E0E7FF" letter-spacing="6">
|
||||
<text>NIXIE</text>
|
||||
</g>
|
||||
<g transform="translate(90 468)" font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-size="20" fill="#A5B4FC">
|
||||
<text>Declarative · Sourceful · Herself</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
42
assets/nixette-wallpaper.svg
Normal file
42
assets/nixette-wallpaper.svg
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="3840" height="2160" viewBox="0 0 3840 2160" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Nixette Wallpaper</title>
|
||||
<desc id="desc">Gradient wallpaper combining trans flag waves with Nix and Gentoo motifs.</desc>
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#0f172a" />
|
||||
<stop offset="100%" stop-color="#1e1b4b" />
|
||||
</linearGradient>
|
||||
<linearGradient id="wave1" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0%" stop-color="#55CDFC" stop-opacity="0" />
|
||||
<stop offset="50%" stop-color="#55CDFC" stop-opacity="0.5" />
|
||||
<stop offset="100%" stop-color="#55CDFC" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="wave2" x1="1" y1="0" x2="0" y2="0">
|
||||
<stop offset="0%" stop-color="#F7A8B8" stop-opacity="0" />
|
||||
<stop offset="50%" stop-color="#F7A8B8" stop-opacity="0.55" />
|
||||
<stop offset="100%" stop-color="#F7A8B8" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<radialGradient id="halo" cx="0.5" cy="0.5" r="0.7">
|
||||
<stop offset="0%" stop-color="#FDE68A" stop-opacity="0.8" />
|
||||
<stop offset="100%" stop-color="#FDE68A" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width="3840" height="2160" fill="url(#sky)" />
|
||||
<rect x="0" y="0" width="3840" height="2160" fill="url(#halo)" opacity="0.4" />
|
||||
<path d="M0 1430 C640 1320 1280 1580 1860 1500 C2440 1420 3040 1660 3840 1500 L3840 2160 L0 2160 Z" fill="url(#wave1)" />
|
||||
<path d="M0 1700 C500 1580 1200 1880 1900 1760 C2600 1640 3200 1920 3840 1800 L3840 2160 L0 2160 Z" fill="url(#wave2)" />
|
||||
<g opacity="0.08" fill="none" stroke="#FFFFFF" stroke-width="24">
|
||||
<path d="M600 360 l220 -220 h360 l220 220 l-220 220 h-360 z" />
|
||||
<path d="M600 360 l220 -220" />
|
||||
<path d="M820 140 l220 220" />
|
||||
</g>
|
||||
<g opacity="0.12" fill="none" stroke="#FFFFFF" stroke-width="22" transform="translate(2820 320) scale(0.9)">
|
||||
<path d="M0 0 C120 -40 220 40 220 160 C220 260 160 320 60 320" />
|
||||
</g>
|
||||
<g transform="translate(940 1320)" font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-size="220" font-weight="700" letter-spacing="18" fill="#FFFFFF" opacity="0.95">
|
||||
<text>NIXETTE</text>
|
||||
</g>
|
||||
<g transform="translate(960 1500)" font-family="'Fira Sans', 'Inter', 'Segoe UI', sans-serif" font-size="64" fill="#F7A8B8" opacity="0.9">
|
||||
<text>Declarative · Sourceful · Herself</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -1,52 +1,41 @@
|
|||
use anyhow::Result;
|
||||
use package_management::svg_builder::{Defs, Document, Element, Filter, Gradient, Group, path};
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let logo_svg = build_logo_svg();
|
||||
let svg = build_logo_svg();
|
||||
fs::create_dir_all("assets")?;
|
||||
fs::write("assets/logo.svg", logo_svg)?;
|
||||
fs::write("assets/logo.svg", svg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_logo_svg() -> String {
|
||||
use svg::*;
|
||||
|
||||
let mut doc = Document::new(640, 320)
|
||||
.view_box("0 0 640 320")
|
||||
.role("img")
|
||||
.aria_label("title", "desc")
|
||||
.title("LPKG Logo")
|
||||
.desc("Stylised package icon with circuitry and the letters LPKG.");
|
||||
|
||||
let mut defs = Defs::new();
|
||||
defs = defs.linear_gradient(
|
||||
let defs = Defs::new()
|
||||
.linear_gradient(
|
||||
"bgGradient",
|
||||
Gradient::new("0", "0", "1", "1")
|
||||
.stop("0%", &[("stop-color", "#0f172a")])
|
||||
.stop("100%", &[("stop-color", "#1e293b")]),
|
||||
);
|
||||
defs = defs.linear_gradient(
|
||||
)
|
||||
.linear_gradient(
|
||||
"cubeGradient",
|
||||
Gradient::new("0", "0", "1", "1")
|
||||
.stop("0%", &[("stop-color", "#38bdf8")])
|
||||
.stop("100%", &[("stop-color", "#0ea5e9")]),
|
||||
);
|
||||
defs = defs.linear_gradient(
|
||||
)
|
||||
.linear_gradient(
|
||||
"cubeShadow",
|
||||
Gradient::new("0", "1", "1", "0")
|
||||
.stop("0%", &[("stop-color", "#0ea5e9"), ("stop-opacity", "0.4")])
|
||||
.stop(
|
||||
"100%",
|
||||
&[("stop-color", "#38bdf8"), ("stop-opacity", "0.1")],
|
||||
),
|
||||
);
|
||||
defs = defs.linear_gradient(
|
||||
.stop("100%", &[("stop-color", "#38bdf8"), ("stop-opacity", "0.1")]),
|
||||
)
|
||||
.linear_gradient(
|
||||
"textGradient",
|
||||
Gradient::new("0", "0", "0", "1")
|
||||
.stop("0%", &[("stop-color", "#f8fafc")])
|
||||
.stop("100%", &[("stop-color", "#cbd5f5")]),
|
||||
);
|
||||
defs = defs.filter(
|
||||
)
|
||||
.filter(
|
||||
"glow",
|
||||
Filter::new()
|
||||
.attr("x", "-20%")
|
||||
|
|
@ -57,18 +46,7 @@ fn build_logo_svg() -> String {
|
|||
.raw("<feMerge><feMergeNode in=\"blur\" /><feMergeNode in=\"SourceGraphic\" /></feMerge>"),
|
||||
);
|
||||
|
||||
doc = doc.add_defs(defs);
|
||||
|
||||
doc = doc.add_element(
|
||||
Element::new("rect")
|
||||
.attr("width", "640")
|
||||
.attr("height", "320")
|
||||
.attr("rx", "28")
|
||||
.attr("fill", "url(#bgGradient)")
|
||||
.empty(),
|
||||
);
|
||||
|
||||
let cube_group = Group::new()
|
||||
let cube_inner = Group::new()
|
||||
.attr("filter", "url(#glow)")
|
||||
.child(
|
||||
Element::new("path")
|
||||
|
|
@ -132,7 +110,10 @@ fn build_logo_svg() -> String {
|
|||
.attr("fill", "#38bdf8")
|
||||
.empty(),
|
||||
);
|
||||
doc = doc.add_element(cube_group);
|
||||
|
||||
let cube = Group::new()
|
||||
.attr("transform", "translate(100 60)")
|
||||
.child(cube_inner);
|
||||
|
||||
let circuits = Group::new()
|
||||
.attr("fill", "none")
|
||||
|
|
@ -146,9 +127,8 @@ fn build_logo_svg() -> String {
|
|||
.child(path("M448 110h72"))
|
||||
.child(path("M472 142h88"))
|
||||
.child(path("M448 174h96"));
|
||||
doc = doc.add_element(circuits);
|
||||
|
||||
let text_group = Group::new()
|
||||
let title_text = Group::new()
|
||||
.attr(
|
||||
"font-family",
|
||||
"'Fira Sans', 'Inter', 'Segoe UI', sans-serif",
|
||||
|
|
@ -163,7 +143,6 @@ fn build_logo_svg() -> String {
|
|||
.attr("fill", "url(#textGradient)")
|
||||
.text("LPKG"),
|
||||
);
|
||||
doc = doc.add_element(text_group);
|
||||
|
||||
let tagline_group = Group::new()
|
||||
.attr(
|
||||
|
|
@ -178,345 +157,25 @@ fn build_logo_svg() -> String {
|
|||
.attr("y", "278")
|
||||
.text("Lightweight Package Manager"),
|
||||
);
|
||||
doc = doc.add_element(tagline_group);
|
||||
|
||||
doc.finish()
|
||||
}
|
||||
|
||||
mod svg {
|
||||
#[derive(Default)]
|
||||
pub struct Document {
|
||||
width: u32,
|
||||
height: u32,
|
||||
view_box: Option<String>,
|
||||
role: Option<String>,
|
||||
aria_label: Option<(String, String)>,
|
||||
title: Option<String>,
|
||||
desc: Option<String>,
|
||||
defs: Vec<String>,
|
||||
elements: Vec<String>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_box(mut self, value: &str) -> Self {
|
||||
self.view_box = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn role(mut self, value: &str) -> Self {
|
||||
self.role = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn aria_label(mut self, title_id: &str, desc_id: &str) -> Self {
|
||||
self.aria_label = Some((title_id.to_string(), desc_id.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn title(mut self, value: &str) -> Self {
|
||||
self.title = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn desc(mut self, value: &str) -> Self {
|
||||
self.desc = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_defs(mut self, defs: Defs) -> Self {
|
||||
self.defs.push(defs.finish());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_element(mut self, element: impl Into<String>) -> Self {
|
||||
self.elements.push(element.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finish(self) -> String {
|
||||
let Document {
|
||||
width,
|
||||
height,
|
||||
view_box,
|
||||
role,
|
||||
aria_label,
|
||||
title,
|
||||
desc,
|
||||
defs,
|
||||
elements,
|
||||
} = self;
|
||||
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{}\" height=\"{}\"",
|
||||
width, height
|
||||
));
|
||||
if let Some(view_box) = view_box {
|
||||
out.push_str(&format!(" viewBox=\"{}\"", view_box));
|
||||
}
|
||||
if let Some(role) = role {
|
||||
out.push_str(&format!(" role=\"{}\"", role));
|
||||
}
|
||||
if let Some((title_id, desc_id)) = &aria_label {
|
||||
out.push_str(&format!(" aria-labelledby=\"{} {}\"", title_id, desc_id));
|
||||
}
|
||||
out.push_str(">");
|
||||
out.push('\n');
|
||||
|
||||
let (title_id, desc_id) = (
|
||||
aria_label
|
||||
.as_ref()
|
||||
.map(|ids| ids.0.as_str())
|
||||
.unwrap_or("title"),
|
||||
aria_label
|
||||
.as_ref()
|
||||
.map(|ids| ids.1.as_str())
|
||||
.unwrap_or("desc"),
|
||||
);
|
||||
|
||||
if let Some(title) = title {
|
||||
out.push_str(&format!(" <title id=\"{}\">{}</title>\n", title_id, title));
|
||||
}
|
||||
if let Some(desc) = desc {
|
||||
out.push_str(&format!(" <desc id=\"{}\">{}</desc>\n", desc_id, desc));
|
||||
}
|
||||
|
||||
if !defs.is_empty() {
|
||||
out.push_str(" <defs>\n");
|
||||
for block in &defs {
|
||||
out.push_str(block);
|
||||
}
|
||||
out.push_str(" </defs>\n");
|
||||
}
|
||||
|
||||
for element in elements {
|
||||
out.push_str(&element);
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
out.push_str("</svg>\n");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Defs {
|
||||
content: Vec<String>,
|
||||
}
|
||||
|
||||
impl Defs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
content: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn linear_gradient(mut self, id: &str, gradient: Gradient) -> Self {
|
||||
self.content.push(format!(" {}\n", gradient.render(id)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn filter(mut self, id: &str, filter: Filter) -> Self {
|
||||
self.content.push(format!(" {}\n", filter.render(id)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finish(self) -> String {
|
||||
self.content.concat()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Gradient {
|
||||
x1: String,
|
||||
y1: String,
|
||||
x2: String,
|
||||
y2: String,
|
||||
stops: Vec<String>,
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
pub fn new(x1: &str, y1: &str, x2: &str, y2: &str) -> Self {
|
||||
Self {
|
||||
x1: x1.to_string(),
|
||||
y1: y1.to_string(),
|
||||
x2: x2.to_string(),
|
||||
y2: y2.to_string(),
|
||||
stops: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(mut self, offset: &str, attrs: &[(&str, &str)]) -> Self {
|
||||
let mut tag = format!("<stop offset=\"{}\"", offset);
|
||||
for (k, v) in attrs {
|
||||
tag.push_str(&format!(" {}=\"{}\"", k, v));
|
||||
}
|
||||
tag.push_str(" />");
|
||||
self.stops.push(tag);
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&self, id: &str) -> String {
|
||||
let mut out = format!(
|
||||
"<linearGradient id=\"{}\" x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\">\n",
|
||||
id, self.x1, self.y1, self.x2, self.y2
|
||||
);
|
||||
for stop in &self.stops {
|
||||
out.push_str(" ");
|
||||
out.push_str(stop);
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(" </linearGradient>");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Filter {
|
||||
attrs: Vec<(String, String)>,
|
||||
content: Vec<String>,
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
attrs: Vec::new(),
|
||||
content: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attr(mut self, key: &str, value: &str) -> Self {
|
||||
self.attrs.push((key.to_string(), value.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn raw(mut self, markup: &str) -> Self {
|
||||
self.content.push(format!(" {}\n", markup));
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&self, id: &str) -> String {
|
||||
let attrs = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
|
||||
.collect::<String>();
|
||||
let mut out = format!("<filter id=\"{}\"{}>\n", id, attrs);
|
||||
for child in &self.content {
|
||||
out.push_str(child);
|
||||
}
|
||||
out.push_str(" </filter>");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Element {
|
||||
tag: String,
|
||||
attrs: Vec<(String, String)>,
|
||||
content: Option<String>,
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn new(tag: &str) -> Self {
|
||||
Self {
|
||||
tag: tag.to_string(),
|
||||
attrs: Vec::new(),
|
||||
content: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attr(mut self, key: &str, value: &str) -> Self {
|
||||
self.attrs.push((key.to_string(), value.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text(mut self, text: &str) -> String {
|
||||
let content = format!("{}", text);
|
||||
self.content = Some(content);
|
||||
self.render()
|
||||
}
|
||||
|
||||
pub fn empty(mut self) -> String {
|
||||
self.content = None;
|
||||
self.render()
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
let attrs = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
|
||||
.collect::<String>();
|
||||
if let Some(content) = &self.content {
|
||||
format!(
|
||||
" <{tag}{attrs}>{content}</{tag}>",
|
||||
tag = self.tag,
|
||||
attrs = attrs,
|
||||
content = content
|
||||
Document::new(640, 320)
|
||||
.view_box("0 0 640 320")
|
||||
.role("img")
|
||||
.aria_label("title", "desc")
|
||||
.title("LPKG Logo")
|
||||
.desc("Stylised package icon with circuitry and the letters LPKG.")
|
||||
.add_defs(defs)
|
||||
.add_element(
|
||||
Element::new("rect")
|
||||
.attr("width", "640")
|
||||
.attr("height", "320")
|
||||
.attr("rx", "28")
|
||||
.attr("fill", "url(#bgGradient)")
|
||||
.empty(),
|
||||
)
|
||||
} else {
|
||||
format!(" <{tag}{attrs} />", tag = self.tag, attrs = attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Group {
|
||||
attrs: Vec<(String, String)>,
|
||||
children: Vec<String>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
attrs: Vec::new(),
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attr(mut self, key: &str, value: &str) -> Self {
|
||||
self.attrs.push((key.to_string(), value.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn child(mut self, element: String) -> Self {
|
||||
self.children.push(element);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render(&self) -> String {
|
||||
let attrs = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
|
||||
.collect::<String>();
|
||||
let mut out = format!(" <g{}>\n", attrs);
|
||||
for child in &self.children {
|
||||
out.push_str(child);
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(" </g>");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Group> for String {
|
||||
fn from(group: Group) -> Self {
|
||||
group.render()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Element> for String {
|
||||
fn from(element: Element) -> Self {
|
||||
element.render()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(d: &str) -> String {
|
||||
Element::new("path").attr("d", d).empty()
|
||||
}
|
||||
.add_element(cube)
|
||||
.add_element(circuits)
|
||||
.add_element(title_text)
|
||||
.add_element(tagline_group)
|
||||
.finish()
|
||||
}
|
||||
|
|
|
|||
126
src/bin/nixette_logo_gen.rs
Normal file
126
src/bin/nixette_logo_gen.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use anyhow::Result;
|
||||
use package_management::svg_builder::{Defs, Document, Element, Filter, Gradient, Group};
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let svg = build_nixette_logo();
|
||||
fs::create_dir_all("assets")?;
|
||||
fs::write("assets/nixette-logo.svg", svg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_nixette_logo() -> String {
|
||||
let defs = Defs::new()
|
||||
.linear_gradient(
|
||||
"bg",
|
||||
Gradient::new("0", "0", "1", "1")
|
||||
.stop("0%", &[("stop-color", "#55CDFC")])
|
||||
.stop("100%", &[("stop-color", "#F7A8B8")]),
|
||||
)
|
||||
.linear_gradient(
|
||||
"text",
|
||||
Gradient::new("0", "0", "0", "1")
|
||||
.stop("0%", &[("stop-color", "#FFFFFF")])
|
||||
.stop("100%", &[("stop-color", "#E5E7FF")]),
|
||||
)
|
||||
.filter(
|
||||
"softShadow",
|
||||
Filter::new()
|
||||
.attr("x", "-10%")
|
||||
.attr("y", "-10%")
|
||||
.attr("width", "120%")
|
||||
.attr("height", "120%")
|
||||
.raw("<feDropShadow dx=\"0\" dy=\"6\" stdDeviation=\"12\" flood-color=\"#7C3AED\" flood-opacity=\"0.3\" />"),
|
||||
);
|
||||
|
||||
let emblem = Group::new().attr("transform", "translate(100 60)").child(
|
||||
Group::new()
|
||||
.attr("filter", "url(#softShadow)")
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M40 40 L72 0 L144 0 L176 40 L144 80 L72 80 Z")
|
||||
.attr("fill", "url(#bg)")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M72 0 L144 80")
|
||||
.attr("stroke", "#FFFFFF")
|
||||
.attr("stroke-width", "6")
|
||||
.attr("stroke-linecap", "round")
|
||||
.attr("opacity", "0.55")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M144 0 L72 80")
|
||||
.attr("stroke", "#FFFFFF")
|
||||
.attr("stroke-width", "6")
|
||||
.attr("stroke-linecap", "round")
|
||||
.attr("opacity", "0.55")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("circle")
|
||||
.attr("cx", "108")
|
||||
.attr("cy", "40")
|
||||
.attr("r", "22")
|
||||
.attr("fill", "#0F172A")
|
||||
.attr("stroke", "#FFFFFF")
|
||||
.attr("stroke-width", "6")
|
||||
.attr("opacity", "0.85")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M108 24c8 0 14 6 14 16s-6 16-14 16")
|
||||
.attr("stroke", "#F7A8B8")
|
||||
.attr("stroke-width", "4")
|
||||
.attr("stroke-linecap", "round")
|
||||
.attr("fill", "none")
|
||||
.empty(),
|
||||
),
|
||||
);
|
||||
|
||||
let wordmark = Group::new()
|
||||
.attr("transform", "translate(220 126)")
|
||||
.attr(
|
||||
"font-family",
|
||||
"'Fira Sans', 'Inter', 'Segoe UI', sans-serif",
|
||||
)
|
||||
.attr("font-weight", "700")
|
||||
.attr("font-size", "72")
|
||||
.attr("letter-spacing", "4")
|
||||
.attr("fill", "url(#text)")
|
||||
.child(Element::new("text").text("NIXETTE"));
|
||||
|
||||
let subtitle = Group::new()
|
||||
.attr("transform", "translate(220 160)")
|
||||
.attr(
|
||||
"font-family",
|
||||
"'Fira Sans', 'Inter', 'Segoe UI', sans-serif",
|
||||
)
|
||||
.attr("font-size", "22")
|
||||
.attr("fill", "#A5B4FC")
|
||||
.child(Element::new("text").text("Declarative · Sourceful · Herself"));
|
||||
|
||||
Document::new(640, 200)
|
||||
.view_box("0 0 640 200")
|
||||
.role("img")
|
||||
.aria_label("title", "desc")
|
||||
.title("Nixette Logo")
|
||||
.desc("Wordmark combining Nix and Gentoo motifs with trans pride colours.")
|
||||
.add_defs(defs)
|
||||
.add_element(
|
||||
Element::new("rect")
|
||||
.attr("width", "640")
|
||||
.attr("height", "200")
|
||||
.attr("rx", "36")
|
||||
.attr("fill", "#0F172A")
|
||||
.empty(),
|
||||
)
|
||||
.add_element(emblem)
|
||||
.add_element(wordmark)
|
||||
.add_element(subtitle)
|
||||
.finish()
|
||||
}
|
||||
170
src/bin/nixette_mascot_gen.rs
Normal file
170
src/bin/nixette_mascot_gen.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use anyhow::Result;
|
||||
use package_management::svg_builder::{Defs, Document, Element, Gradient, Group};
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let svg = build_mascot_svg();
|
||||
fs::create_dir_all("assets")?;
|
||||
fs::write("assets/nixette-mascot.svg", svg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_mascot_svg() -> String {
|
||||
let defs = Defs::new()
|
||||
.linear_gradient(
|
||||
"bgGrad",
|
||||
Gradient::new("0", "0", "0", "1")
|
||||
.stop("0%", &[("stop-color", "#312E81")])
|
||||
.stop("100%", &[("stop-color", "#1E1B4B")]),
|
||||
)
|
||||
.linear_gradient(
|
||||
"hairLeft",
|
||||
Gradient::new("0", "0", "1", "1")
|
||||
.stop("0%", &[("stop-color", "#55CDFC")])
|
||||
.stop("100%", &[("stop-color", "#0EA5E9")]),
|
||||
)
|
||||
.linear_gradient(
|
||||
"hairRight",
|
||||
Gradient::new("1", "0", "0", "1")
|
||||
.stop("0%", &[("stop-color", "#F7A8B8")])
|
||||
.stop("100%", &[("stop-color", "#FB7185")]),
|
||||
)
|
||||
.linear_gradient(
|
||||
"bellyGrad",
|
||||
Gradient::new("0", "0", "0", "1")
|
||||
.stop("0%", &[("stop-color", "#FFFFFF")])
|
||||
.stop("100%", &[("stop-color", "#E2E8F0")]),
|
||||
);
|
||||
|
||||
let body = Group::new()
|
||||
.attr("transform", "translate(240 220)")
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M-160 -20 C-140 -160 140 -160 160 -20 C180 140 60 220 0 220 C-60 220 -180 140 -160 -20")
|
||||
.attr("fill", "#0F172A")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("ellipse")
|
||||
.attr("cx", "0")
|
||||
.attr("cy", "40")
|
||||
.attr("rx", "120")
|
||||
.attr("ry", "140")
|
||||
.attr("fill", "#1E293B")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M-88 -80 Q-40 -140 0 -120 Q40 -140 88 -80")
|
||||
.attr("fill", "#1E293B")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M-96 -84 Q-60 -160 -8 -132 L-8 -40 Z")
|
||||
.attr("fill", "url(#hairLeft)")
|
||||
.empty(),
|
||||
)
|
||||
.child(
|
||||
Element::new("path")
|
||||
.attr("d", "M96 -84 Q60 -160 8 -132 L8 -40 Z")
|
||||
.attr("fill", "url(#hairRight)")
|
||||
.empty(),
|
||||
)
|
||||
.child(ellipse(-44.0, -8.0, 26.0, 32.0, "#FFFFFF"))
|
||||
.child(ellipse(44.0, -8.0, 26.0, 32.0, "#FFFFFF"))
|
||||
.child(circle(-44.0, -4.0, 14.0, "#0F172A"))
|
||||
.child(circle(44.0, -4.0, 14.0, "#0F172A"))
|
||||
.child(circle_with_opacity(-40.0, -8.0, 6.0, "#FFFFFF", 0.7))
|
||||
.child(circle_with_opacity(48.0, -10.0, 6.0, "#FFFFFF", 0.7))
|
||||
.child(path_with_fill("M0 12 L-18 32 Q0 44 18 32 Z", "#F472B6"))
|
||||
.child(path_with_fill("M0 32 L-16 52 Q0 60 16 52 Z", "#FBEAED"))
|
||||
.child(path_with_fill("M0 46 Q-32 78 0 86 Q32 78 0 46", "#FCA5A5"))
|
||||
.child(
|
||||
Element::new("ellipse")
|
||||
.attr("cx", "0")
|
||||
.attr("cy", "74")
|
||||
.attr("rx", "70")
|
||||
.attr("ry", "82")
|
||||
.attr("fill", "url(#bellyGrad)")
|
||||
.empty(),
|
||||
)
|
||||
.child(path_with_fill("M-128 48 Q-176 56 -176 120 Q-128 112 -104 80", "#F7A8B8"))
|
||||
.child(path_with_fill("M128 48 Q176 56 176 120 Q128 112 104 80", "#55CDFC"))
|
||||
.child(circle_with_opacity(-100.0, 94.0, 18.0, "#FDE68A", 0.85))
|
||||
.child(circle_with_opacity(100.0, 94.0, 18.0, "#FDE68A", 0.85));
|
||||
|
||||
Document::new(480, 520)
|
||||
.view_box("0 0 480 520")
|
||||
.role("img")
|
||||
.aria_label("title", "desc")
|
||||
.title("Nixette Mascot Badge")
|
||||
.desc("Chibi penguin mascot with trans flag hair, blending Nix and Gentoo motifs.")
|
||||
.add_defs(defs)
|
||||
.add_element(
|
||||
Element::new("rect")
|
||||
.attr("width", "480")
|
||||
.attr("height", "520")
|
||||
.attr("rx", "48")
|
||||
.attr("fill", "url(#bgGrad)")
|
||||
.empty(),
|
||||
)
|
||||
.add_element(body)
|
||||
.add_element(
|
||||
Group::new()
|
||||
.attr("transform", "translate(90 420)")
|
||||
.attr(
|
||||
"font-family",
|
||||
"'Fira Sans', 'Inter', 'Segoe UI', sans-serif",
|
||||
)
|
||||
.attr("font-size", "42")
|
||||
.attr("fill", "#E0E7FF")
|
||||
.attr("letter-spacing", "6")
|
||||
.child(Element::new("text").text("NIXIE")),
|
||||
)
|
||||
.add_element(
|
||||
Group::new()
|
||||
.attr("transform", "translate(90 468)")
|
||||
.attr(
|
||||
"font-family",
|
||||
"'Fira Sans', 'Inter', 'Segoe UI', sans-serif",
|
||||
)
|
||||
.attr("font-size", "20")
|
||||
.attr("fill", "#A5B4FC")
|
||||
.child(Element::new("text").text("Declarative · Sourceful · Herself")),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn ellipse(cx: f64, cy: f64, rx: f64, ry: f64, fill: &str) -> String {
|
||||
Element::new("ellipse")
|
||||
.attr("cx", &format!("{}", cx))
|
||||
.attr("cy", &format!("{}", cy))
|
||||
.attr("rx", &format!("{}", rx))
|
||||
.attr("ry", &format!("{}", ry))
|
||||
.attr("fill", fill)
|
||||
.empty()
|
||||
}
|
||||
|
||||
fn circle(cx: f64, cy: f64, r: f64, fill: &str) -> String {
|
||||
Element::new("circle")
|
||||
.attr("cx", &format!("{}", cx))
|
||||
.attr("cy", &format!("{}", cy))
|
||||
.attr("r", &format!("{}", r))
|
||||
.attr("fill", fill)
|
||||
.empty()
|
||||
}
|
||||
|
||||
fn circle_with_opacity(cx: f64, cy: f64, r: f64, fill: &str, opacity: f64) -> String {
|
||||
Element::new("circle")
|
||||
.attr("cx", &format!("{}", cx))
|
||||
.attr("cy", &format!("{}", cy))
|
||||
.attr("r", &format!("{}", r))
|
||||
.attr("fill", fill)
|
||||
.attr("opacity", &format!("{}", opacity))
|
||||
.empty()
|
||||
}
|
||||
|
||||
fn path_with_fill(d: &str, fill: &str) -> String {
|
||||
Element::new("path").attr("d", d).attr("fill", fill).empty()
|
||||
}
|
||||
128
src/bin/nixette_wallpaper_gen.rs
Normal file
128
src/bin/nixette_wallpaper_gen.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
use anyhow::Result;
|
||||
use package_management::svg_builder::{
|
||||
Defs, Document, Element, Gradient, Group, RadialGradient, path,
|
||||
};
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let svg = build_wallpaper_svg();
|
||||
fs::create_dir_all("assets")?;
|
||||
fs::write("assets/nixette-wallpaper.svg", svg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_wallpaper_svg() -> String {
|
||||
let defs = Defs::new()
|
||||
.linear_gradient(
|
||||
"sky",
|
||||
Gradient::new("0", "0", "1", "1")
|
||||
.stop("0%", &[("stop-color", "#0f172a")])
|
||||
.stop("100%", &[("stop-color", "#1e1b4b")]),
|
||||
)
|
||||
.linear_gradient(
|
||||
"wave1",
|
||||
Gradient::new("0", "0", "1", "0")
|
||||
.stop("0%", &[("stop-color", "#55CDFC"), ("stop-opacity", "0")])
|
||||
.stop("50%", &[("stop-color", "#55CDFC"), ("stop-opacity", "0.5")])
|
||||
.stop("100%", &[("stop-color", "#55CDFC"), ("stop-opacity", "0")]),
|
||||
)
|
||||
.linear_gradient(
|
||||
"wave2",
|
||||
Gradient::new("1", "0", "0", "0")
|
||||
.stop("0%", &[("stop-color", "#F7A8B8"), ("stop-opacity", "0")])
|
||||
.stop(
|
||||
"50%",
|
||||
&[("stop-color", "#F7A8B8"), ("stop-opacity", "0.55")],
|
||||
)
|
||||
.stop("100%", &[("stop-color", "#F7A8B8"), ("stop-opacity", "0")]),
|
||||
)
|
||||
.radial_gradient(
|
||||
"halo",
|
||||
RadialGradient::new("0.5", "0.5", "0.7")
|
||||
.stop("0%", &[("stop-color", "#FDE68A"), ("stop-opacity", "0.8")])
|
||||
.stop("100%", &[("stop-color", "#FDE68A"), ("stop-opacity", "0")]),
|
||||
);
|
||||
|
||||
let text = Group::new()
|
||||
.attr("transform", "translate(940 1320)")
|
||||
.attr(
|
||||
"font-family",
|
||||
"'Fira Sans', 'Inter', 'Segoe UI', sans-serif",
|
||||
)
|
||||
.attr("font-size", "220")
|
||||
.attr("font-weight", "700")
|
||||
.attr("letter-spacing", "18")
|
||||
.attr("fill", "#FFFFFF")
|
||||
.attr("opacity", "0.95")
|
||||
.child(Element::new("text").text("NIXETTE"));
|
||||
|
||||
let subtitle = Group::new()
|
||||
.attr("transform", "translate(960 1500)")
|
||||
.attr(
|
||||
"font-family",
|
||||
"'Fira Sans', 'Inter', 'Segoe UI', sans-serif",
|
||||
)
|
||||
.attr("font-size", "64")
|
||||
.attr("fill", "#F7A8B8")
|
||||
.attr("opacity", "0.9")
|
||||
.child(Element::new("text").text("Declarative · Sourceful · Herself"));
|
||||
|
||||
Document::new(3840, 2160)
|
||||
.view_box("0 0 3840 2160")
|
||||
.role("img")
|
||||
.aria_label("title", "desc")
|
||||
.title("Nixette Wallpaper")
|
||||
.desc("Gradient wallpaper combining trans flag waves with Nix and Gentoo motifs.")
|
||||
.add_defs(defs)
|
||||
.add_element(
|
||||
Element::new("rect")
|
||||
.attr("width", "3840")
|
||||
.attr("height", "2160")
|
||||
.attr("fill", "url(#sky)")
|
||||
.empty(),
|
||||
)
|
||||
.add_element(
|
||||
Element::new("rect")
|
||||
.attr("x", "0")
|
||||
.attr("y", "0")
|
||||
.attr("width", "3840")
|
||||
.attr("height", "2160")
|
||||
.attr("fill", "url(#halo)")
|
||||
.attr("opacity", "0.4")
|
||||
.empty(),
|
||||
)
|
||||
.add_element(
|
||||
Element::new("path")
|
||||
.attr("d", "M0 1430 C640 1320 1280 1580 1860 1500 C2440 1420 3040 1660 3840 1500 L3840 2160 L0 2160 Z")
|
||||
.attr("fill", "url(#wave1)")
|
||||
.empty(),
|
||||
)
|
||||
.add_element(
|
||||
Element::new("path")
|
||||
.attr("d", "M0 1700 C500 1580 1200 1880 1900 1760 C2600 1640 3200 1920 3840 1800 L3840 2160 L0 2160 Z")
|
||||
.attr("fill", "url(#wave2)")
|
||||
.empty(),
|
||||
)
|
||||
.add_element(
|
||||
Group::new()
|
||||
.attr("opacity", "0.08")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "#FFFFFF")
|
||||
.attr("stroke-width", "24")
|
||||
.child(path("M600 360 l220 -220 h360 l220 220 l-220 220 h-360 z"))
|
||||
.child(path("M600 360 l220 -220"))
|
||||
.child(path("M820 140 l220 220")),
|
||||
)
|
||||
.add_element(
|
||||
Group::new()
|
||||
.attr("opacity", "0.12")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "#FFFFFF")
|
||||
.attr("stroke-width", "22")
|
||||
.attr("transform", "translate(2820 320) scale(0.9)")
|
||||
.child(path("M0 0 C120 -40 220 40 220 160 C220 260 160 320 60 320")),
|
||||
)
|
||||
.add_element(text)
|
||||
.add_element(subtitle)
|
||||
.finish()
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ pub mod ingest;
|
|||
pub mod md5_utils;
|
||||
pub mod mirrors;
|
||||
pub mod pkgs;
|
||||
pub mod svg_builder;
|
||||
pub mod version_check;
|
||||
pub mod wget_list;
|
||||
|
||||
|
|
|
|||
375
src/svg_builder.rs
Normal file
375
src/svg_builder.rs
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
#[derive(Default)]
|
||||
pub struct Document {
|
||||
width: u32,
|
||||
height: u32,
|
||||
view_box: Option<String>,
|
||||
role: Option<String>,
|
||||
aria_label: Option<(String, String)>,
|
||||
title: Option<String>,
|
||||
desc: Option<String>,
|
||||
defs: Vec<String>,
|
||||
elements: Vec<String>,
|
||||
}
|
||||
|
||||
impl Document {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_box(mut self, value: &str) -> Self {
|
||||
self.view_box = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn role(mut self, value: &str) -> Self {
|
||||
self.role = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn aria_label(mut self, title_id: &str, desc_id: &str) -> Self {
|
||||
self.aria_label = Some((title_id.to_string(), desc_id.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn title(mut self, value: &str) -> Self {
|
||||
self.title = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn desc(mut self, value: &str) -> Self {
|
||||
self.desc = Some(value.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_defs(mut self, defs: Defs) -> Self {
|
||||
self.defs.push(defs.finish());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_element(mut self, element: impl Into<String>) -> Self {
|
||||
self.elements.push(element.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finish(self) -> String {
|
||||
let Document {
|
||||
width,
|
||||
height,
|
||||
view_box,
|
||||
role,
|
||||
aria_label,
|
||||
title,
|
||||
desc,
|
||||
defs,
|
||||
elements,
|
||||
} = self;
|
||||
|
||||
let mut out = String::new();
|
||||
out.push_str(&format!(
|
||||
"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{}\" height=\"{}\"",
|
||||
width, height
|
||||
));
|
||||
if let Some(view_box) = view_box {
|
||||
out.push_str(&format!(" viewBox=\"{}\"", view_box));
|
||||
}
|
||||
if let Some(role) = role {
|
||||
out.push_str(&format!(" role=\"{}\"", role));
|
||||
}
|
||||
let (title_id, desc_id) = aria_label
|
||||
.as_ref()
|
||||
.map(|ids| (ids.0.as_str(), ids.1.as_str()))
|
||||
.unwrap_or(("title", "desc"));
|
||||
if aria_label.is_some() {
|
||||
out.push_str(&format!(" aria-labelledby=\"{} {}\"", title_id, desc_id));
|
||||
}
|
||||
out.push_str(">");
|
||||
out.push('\n');
|
||||
|
||||
if let Some(title) = title {
|
||||
out.push_str(&format!(" <title id=\"{}\">{}</title>\n", title_id, title));
|
||||
}
|
||||
if let Some(desc) = desc {
|
||||
out.push_str(&format!(" <desc id=\"{}\">{}</desc>\n", desc_id, desc));
|
||||
}
|
||||
|
||||
if !defs.is_empty() {
|
||||
out.push_str(" <defs>\n");
|
||||
for block in &defs {
|
||||
out.push_str(block);
|
||||
}
|
||||
out.push_str(" </defs>\n");
|
||||
}
|
||||
|
||||
for element in &elements {
|
||||
out.push_str(element);
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
out.push_str("</svg>\n");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Defs {
|
||||
content: Vec<String>,
|
||||
}
|
||||
|
||||
impl Defs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
content: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn linear_gradient(mut self, id: &str, gradient: Gradient) -> Self {
|
||||
self.content
|
||||
.push(format!(" {}\n", gradient.render_linear(id)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn radial_gradient(mut self, id: &str, gradient: RadialGradient) -> Self {
|
||||
self.content.push(format!(" {}\n", gradient.render(id)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn filter(mut self, id: &str, filter: Filter) -> Self {
|
||||
self.content.push(format!(" {}\n", filter.render(id)));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finish(self) -> String {
|
||||
self.content.concat()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Gradient {
|
||||
x1: String,
|
||||
y1: String,
|
||||
x2: String,
|
||||
y2: String,
|
||||
stops: Vec<String>,
|
||||
}
|
||||
|
||||
impl Gradient {
|
||||
pub fn new(x1: &str, y1: &str, x2: &str, y2: &str) -> Self {
|
||||
Self {
|
||||
x1: x1.to_string(),
|
||||
y1: y1.to_string(),
|
||||
x2: x2.to_string(),
|
||||
y2: y2.to_string(),
|
||||
stops: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(mut self, offset: &str, attrs: &[(&str, &str)]) -> Self {
|
||||
let mut tag = format!("<stop offset=\"{}\"", offset);
|
||||
for (k, v) in attrs {
|
||||
tag.push_str(&format!(" {}=\"{}\"", k, v));
|
||||
}
|
||||
tag.push_str(" />");
|
||||
self.stops.push(tag);
|
||||
self
|
||||
}
|
||||
|
||||
fn render_linear(&self, id: &str) -> String {
|
||||
let mut out = format!(
|
||||
"<linearGradient id=\"{}\" x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\">\n",
|
||||
id, self.x1, self.y1, self.x2, self.y2
|
||||
);
|
||||
for stop in &self.stops {
|
||||
out.push_str(" ");
|
||||
out.push_str(stop);
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(" </linearGradient>");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RadialGradient {
|
||||
cx: String,
|
||||
cy: String,
|
||||
r: String,
|
||||
stops: Vec<String>,
|
||||
}
|
||||
|
||||
impl RadialGradient {
|
||||
pub fn new(cx: &str, cy: &str, r: &str) -> Self {
|
||||
Self {
|
||||
cx: cx.to_string(),
|
||||
cy: cy.to_string(),
|
||||
r: r.to_string(),
|
||||
stops: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(mut self, offset: &str, attrs: &[(&str, &str)]) -> Self {
|
||||
let mut tag = format!("<stop offset=\"{}\"", offset);
|
||||
for (k, v) in attrs {
|
||||
tag.push_str(&format!(" {}=\"{}\"", k, v));
|
||||
}
|
||||
tag.push_str(" />");
|
||||
self.stops.push(tag);
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&self, id: &str) -> String {
|
||||
let mut out = format!(
|
||||
"<radialGradient id=\"{}\" cx=\"{}\" cy=\"{}\" r=\"{}\">\n",
|
||||
id, self.cx, self.cy, self.r
|
||||
);
|
||||
for stop in &self.stops {
|
||||
out.push_str(" ");
|
||||
out.push_str(stop);
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(" </radialGradient>");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Filter {
|
||||
attrs: Vec<(String, String)>,
|
||||
content: Vec<String>,
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
attrs: Vec::new(),
|
||||
content: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attr(mut self, key: &str, value: &str) -> Self {
|
||||
self.attrs.push((key.to_string(), value.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn raw(mut self, markup: &str) -> Self {
|
||||
self.content.push(format!(" {}\n", markup));
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&self, id: &str) -> String {
|
||||
let attrs = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
|
||||
.collect::<String>();
|
||||
let mut out = format!("<filter id=\"{}\"{}>\n", id, attrs);
|
||||
for child in &self.content {
|
||||
out.push_str(child);
|
||||
}
|
||||
out.push_str(" </filter>");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Element {
|
||||
tag: String,
|
||||
attrs: Vec<(String, String)>,
|
||||
content: Option<String>,
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn new(tag: &str) -> Self {
|
||||
Self {
|
||||
tag: tag.to_string(),
|
||||
attrs: Vec::new(),
|
||||
content: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attr(mut self, key: &str, value: &str) -> Self {
|
||||
self.attrs.push((key.to_string(), value.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn text(mut self, text: &str) -> String {
|
||||
self.content = Some(text.to_string());
|
||||
self.render()
|
||||
}
|
||||
|
||||
pub fn empty(mut self) -> String {
|
||||
self.content = None;
|
||||
self.render()
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
let attrs = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
|
||||
.collect::<String>();
|
||||
if let Some(content) = &self.content {
|
||||
format!(
|
||||
" <{tag}{attrs}>{content}</{tag}>",
|
||||
tag = self.tag,
|
||||
attrs = attrs,
|
||||
content = content
|
||||
)
|
||||
} else {
|
||||
format!(" <{tag}{attrs} />", tag = self.tag, attrs = attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Group {
|
||||
attrs: Vec<(String, String)>,
|
||||
children: Vec<String>,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
attrs: Vec::new(),
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attr(mut self, key: &str, value: &str) -> Self {
|
||||
self.attrs.push((key.to_string(), value.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn child(mut self, element: impl Into<String>) -> Self {
|
||||
self.children.push(element.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn render(&self) -> String {
|
||||
let attrs = self
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|(k, v)| format!(" {}=\"{}\"", k, v))
|
||||
.collect::<String>();
|
||||
let mut out = format!(" <g{}>\n", attrs);
|
||||
for child in &self.children {
|
||||
out.push_str(child);
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(" </g>");
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Group> for String {
|
||||
fn from(group: Group) -> Self {
|
||||
group.render()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Element> for String {
|
||||
fn from(element: Element) -> Self {
|
||||
element.render()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(d: &str) -> String {
|
||||
Element::new("path").attr("d", d).empty()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue