Media

SVG to PNG

SVG drawn to PNG at the size you ask, text included with a built-in font: link previews, email images, icons and charts.

Version 1.0.0 · 3 functions · 2 crates

#images#svg#email#charts

Functions

What use svg_png.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use svg_to_png, svg_file_to_png from svg_png (Quillo 0.6).

svg_to_png(svg: str, out: str, width: int) → anyor an error

Draws the SVG text svg into a PNG at out (folders made), width pixels wide with the height to keep its shape (0 for its own size); transparent where the SVG draws nothing. { width, height }. Up to 8,000 pixels a side; pictures inside it load only from data: URLs.

svg_file_to_png(from: str, out: str, width: int) → anyor an error

The same from an SVG file at from.

svg_size(svg: str) → anyor an error

The SVG's own size, { width, height } in pixels, from its width and height or its viewBox.

Get it

$ quillo install svg_png               # svg_png.qwer into this folder$ quillo install svg_png --example     # and its example, svg_png.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/svg_png.qwer$ curl -O https://quillolang.org/plugins/files/svg_png.qwe

Then, in your program, all of it or only what you call:

main.qwe
use svg_png.qweruse svg_to_png, svg_file_to_png from svg_png      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
resvg = "0.45"dejavu = "2.37"

Example

svg_png.qwe
// A badge drawn as SVG and served as PNG, for an email that cannot show SVG.////   quillo run svg_png.qwe      then open http://localhost:8080/badge.pnguse "svg_png.qwer"fn badge(label, value, color) = `<svg xmlns="http://www.w3.org/2000/svg" width="180" height="28" viewBox="0 0 180 28">  <rect width="180" height="28" rx="6" fill="#333"/>  <rect x="90" width="90" height="28" rx="6" fill="${color}"/>  <rect x="90" width="10" height="28" fill="${color}"/>  <text x="45" y="19" font-size="14" font-family="sans-serif" fill="#fff" text-anchor="middle">${escape(label)}</text>  <text x="135" y="19" font-size="14" font-family="sans-serif" fill="#fff" text-anchor="middle" font-weight="bold">${escape(value)}</text></svg>`route GET /badge.png ? { label: str = "tests", value: str = "passing" } {    svg_to_png(badge(label, value, "#2e9e5b"), "data/badges/badge.png", 360)?    file("data/badges/badge.png")}route GET /sizes {    let svg = badge("build", "ok", "#2e9e5b")    { own: svg_size(svg)?, png: svg_to_png(svg, "data/badges/build.png", 0)?, double: svg_to_png(svg, "data/badges/build@2x.png", 360)? }}
$ quillo run svg_png.qwe$ curl 'localhost:8080/sizes'$ curl 'localhost:8080/badge.png'

Source

svg_png.qwer
//! SVG drawn to PNG at the size you ask, text included (a font is built in): link previews, email images, icons and charts for places that take no SVG.//! version: 1.0.0//!//! [dependencies]//! resvg = "0.45"//! dejavu = "2.37"use resvg::tiny_skia;use resvg::usvg;use serde_json::{json, Value};use std::sync::{Arc, OnceLock};const MAX_SIDE: u32 = 8000;const MAX_SVG: usize = 32 * 1024 * 1024;// The system's fonts, and DejaVu Sans built in for servers that have none: loaded once.fn fonts() -> Arc<usvg::fontdb::Database> {    static DB: OnceLock<Arc<usvg::fontdb::Database>> = OnceLock::new();    DB.get_or_init(|| {        let mut db = usvg::fontdb::Database::new();        db.load_system_fonts();        db.load_font_data(dejavu::sans::regular().to_vec());        db.load_font_data(dejavu::sans::bold().to_vec());        db.load_font_data(dejavu::sans::oblique().to_vec());        db.load_font_data(dejavu::sans_mono::regular().to_vec());        db.set_sans_serif_family("DejaVu Sans");        db.set_monospace_family("DejaVu Sans Mono");        Arc::new(db)    })    .clone()}fn tree(svg: &str) -> Result<usvg::Tree, String> {    if svg.len() > MAX_SVG {        return Err("the SVG is larger than 32 MB".into());    }    let mut opt = usvg::Options::default();    opt.fontdb = fonts();    opt.font_family = "DejaVu Sans".into();    // Pictures inside the SVG come only from data: URLs, never from files or the network.    opt.resources_dir = None;    opt.image_href_resolver = usvg::ImageHrefResolver { resolve_data: usvg::ImageHrefResolver::default_data_resolver(), resolve_string: Box::new(|_, _| None) };    usvg::Tree::from_str(svg, &opt).map_err(|e| format!("the SVG cannot be read: {}", e))}fn render(svg: &str, out: &str, width: i64) -> Result<Value, String> {    let t = tree(svg)?;    let size = t.size();    let (sw, sh) = (size.width(), size.height());    let w = if width > 0 { width as f32 } else { sw };    let scale = w / sw;    let (pw, ph) = (w.round() as u32, (sh * scale).round() as u32);    if pw == 0 || ph == 0 || pw > MAX_SIDE || ph > MAX_SIDE {        return Err(format!("{}×{} is outside 1 to {} pixels a side", pw, ph, MAX_SIDE));    }    let mut pixmap = tiny_skia::Pixmap::new(pw, ph).ok_or("cannot make the picture")?;    resvg::render(&t, tiny_skia::Transform::from_scale(scale, scale), &mut pixmap.as_mut());    if let Some(dir) = std::path::Path::new(out).parent().filter(|d| !d.as_os_str().is_empty()) {        std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?;    }    pixmap.save_png(out).map_err(|e| format!("{}: {}", out, e))?;    Ok(json!({ "width": pw, "height": ph }))}/// Draws the SVG text `svg` into a PNG at `out` (folders made), `width` pixels wide with the height to keep its shape (0 for its own size); transparent where the SVG draws nothing./// `{ width, height }`. Up to 8,000 pixels a side; pictures inside it load only from data: URLs.pub fn svg_to_png(svg: &str, out: &str, width: i64) -> Result<Value, String> {    render(svg, out, width)}/// The same from an SVG file at `from`.pub fn svg_file_to_png(from: &str, out: &str, width: i64) -> Result<Value, String> {    let svg = std::fs::read_to_string(from).map_err(|e| format!("{}: {}", from, e))?;    render(&svg, out, width)}/// The SVG's own size, `{ width, height }` in pixels, from its width and height or its viewBox.pub fn svg_size(svg: &str) -> Result<Value, String> {    let s = tree(svg)?.size();    Ok(json!({ "width": s.width(), "height": s.height() }))}
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On SVG to PNG: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.