1
0
mirror of https://github.com/mfontanini/presenterm.git synced 2026-09-01 23:33:21 -06:00

feat: add svg image support

This commit is contained in:
Markus Sagen 2026-03-17 08:18:23 +01:00
parent 09eb82f7f5
commit 62b8569868
5 changed files with 391 additions and 3 deletions

326
Cargo.lock generated
View File

@ -73,6 +73,12 @@ version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.7.6"
@ -252,6 +258,15 @@ dependencies = [
"unicode_categories",
]
[[package]]
name = "core_maths"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
dependencies = [
"libm",
]
[[package]]
name = "crc32fast"
version = "1.5.0"
@ -328,6 +343,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "data-url"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]]
name = "deranged"
version = "0.5.5"
@ -413,6 +434,15 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "euclid"
version = "0.22.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63"
dependencies = [
"num-traits",
]
[[package]]
name = "fast-srgb8"
version = "1.0.0"
@ -461,12 +491,41 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fontconfig-parser"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
dependencies = [
"roxmltree",
]
[[package]]
name = "fontdb"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"
dependencies = [
"fontconfig-parser",
"log",
"memmap2",
"slotmap",
"tinyvec",
"ttf-parser",
]
[[package]]
name = "fragile"
version = "2.0.1"
@ -546,11 +605,27 @@ dependencies = [
"gif",
"moxcms",
"num-traits",
"png",
"png 0.18.0",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "image-webp"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
dependencies = [
"byteorder-lite",
"quick-error",
]
[[package]]
name = "imagesize"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "indexmap"
version = "2.12.0"
@ -588,6 +663,17 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e"
[[package]]
name = "kurbo"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
dependencies = [
"arrayvec",
"euclid",
"smallvec",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -655,6 +741,15 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memmap2"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
dependencies = [
"libc",
]
[[package]]
name = "merge-struct"
version = "0.1.0"
@ -866,6 +961,12 @@ dependencies = [
"windows-link",
]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pkg-config"
version = "0.3.32"
@ -885,6 +986,19 @@ dependencies = [
"time",
]
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "png"
version = "0.18.0"
@ -973,6 +1087,7 @@ dependencies = [
"once_cell",
"os_pipe",
"portable-pty",
"resvg",
"rstest",
"schemars",
"serde",
@ -1028,6 +1143,12 @@ dependencies = [
"wide",
]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.38.3"
@ -1171,6 +1292,38 @@ version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "resvg"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"
dependencies = [
"gif",
"image-webp",
"log",
"pico-args",
"rgb",
"svgtypes",
"tiny-skia",
"usvg",
"zune-jpeg",
]
[[package]]
name = "rgb"
version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
dependencies = [
"bytemuck",
]
[[package]]
name = "roxmltree"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "rstest"
version = "0.26.1"
@ -1232,6 +1385,24 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rustybuzz"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
dependencies = [
"bitflags 2.10.0",
"bytemuck",
"core_maths",
"log",
"smallvec",
"ttf-parser",
"unicode-bidi-mirroring",
"unicode-ccc",
"unicode-properties",
"unicode-script",
]
[[package]]
name = "ryu"
version = "1.0.20"
@ -1428,6 +1599,15 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simplecss"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
dependencies = [
"log",
]
[[package]]
name = "simplelog"
version = "0.12.2"
@ -1439,6 +1619,21 @@ dependencies = [
"time",
]
[[package]]
name = "siphasher"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slotmap"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
dependencies = [
"version_check",
]
[[package]]
name = "smallvec"
version = "1.15.1"
@ -1455,6 +1650,15 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]]
name = "strsim"
version = "0.11.1"
@ -1482,6 +1686,16 @@ dependencies = [
"syn",
]
[[package]]
name = "svgtypes"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
dependencies = [
"kurbo",
"siphasher",
]
[[package]]
name = "syn"
version = "2.0.108"
@ -1634,6 +1848,32 @@ dependencies = [
"time-core",
]
[[package]]
name = "tiny-skia"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
dependencies = [
"arrayref",
"arrayvec",
"bytemuck",
"cfg-if",
"log",
"png 0.17.16",
"tiny-skia-path",
]
[[package]]
name = "tiny-skia-path"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
dependencies = [
"arrayref",
"bytemuck",
"strict-num",
]
[[package]]
name = "tinyvec"
version = "1.10.0"
@ -1655,12 +1895,39 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b130bd8a58c163224b44e217b4239ca7b927d82bf6cc2fea1fc561d15056e3f7"
[[package]]
name = "ttf-parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
dependencies = [
"core_maths",
]
[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-bidi-mirroring"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
[[package]]
name = "unicode-ccc"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
[[package]]
name = "unicode-ident"
version = "1.0.22"
@ -1676,6 +1943,24 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "unicode-script"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee"
[[package]]
name = "unicode-vo"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
[[package]]
name = "unicode-width"
version = "0.2.2"
@ -1694,12 +1979,45 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "usvg"
version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef"
dependencies = [
"base64",
"data-url",
"flate2",
"fontdb",
"imagesize",
"kurbo",
"log",
"pico-args",
"roxmltree",
"rustybuzz",
"simplecss",
"siphasher",
"strict-num",
"svgtypes",
"tiny-skia-path",
"unicode-bidi",
"unicode-script",
"unicode-vo",
"xmlwriter",
]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vt100"
version = "0.16.2"
@ -1964,6 +2282,12 @@ dependencies = [
"tap",
]
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "zune-core"
version = "0.4.12"

View File

@ -36,6 +36,7 @@ tl = "0.7"
thiserror = "2"
unicode-width = "0.2"
os_pipe = "1.1.5"
resvg = "0.45"
libc = "0.2"
vte = "0.15"
termbg = "0.6.2"

View File

@ -761,6 +761,13 @@ hola
.expect("failed to encode png");
}
fn write_test_svg(dir: &std::path::Path, name: &str) {
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<rect width="100" height="100" fill="blue"/>
</svg>"#;
fs::write(dir.join(name), svg).unwrap();
}
#[test]
fn bg_image_comment_loads_png() {
let dir = tempdir().expect("failed to create tempdir");
@ -770,6 +777,15 @@ hola
Test::new(input).resources_path(dir.path()).build();
}
#[test]
fn bg_image_comment_loads_svg() {
let dir = tempdir().expect("failed to create tempdir");
write_test_svg(dir.path(), "bg.svg");
let input = "<!-- bg_image: {path: bg.svg, fit: stretch} -->\nhello";
Test::new(input).resources_path(dir.path()).build();
}
#[test]
fn bg_image_comment_cover_mode() {
let dir = tempdir().expect("failed to create tempdir");
@ -788,6 +804,15 @@ hola
Test::new(input).resources_path(dir.path()).build();
}
#[test]
fn bg_image_comment_svg_cover() {
let dir = tempdir().expect("failed to create tempdir");
write_test_svg(dir.path(), "bg.svg");
let input = "<!-- bg_image: {path: bg.svg, fit: cover} -->\nhello";
Test::new(input).resources_path(dir.path()).build();
}
#[test]
fn bg_image_comment_missing_file() {
let dir = tempdir().expect("failed to create tempdir");
@ -805,6 +830,15 @@ hola
Test::new(input).resources_path(dir.path()).build();
}
#[test]
fn theme_background_image_svg() {
let dir = tempdir().expect("failed to create tempdir");
write_test_svg(dir.path(), "bg.svg");
let input = "---\ntheme:\n override:\n default:\n background_image:\n path: bg.svg\n fit: stretch\n---\nhello";
Test::new(input).resources_path(dir.path()).build();
}
#[test]
fn include() {
let dir = tempdir().expect("failed to created tempdir");

View File

@ -103,6 +103,9 @@ impl Resources {
}
pub(crate) fn load_dynamic_image(&self, path: &Path) -> Result<image::DynamicImage, RegisterImageError> {
if path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("svg")) {
return crate::terminal::image::printer::rasterize_svg(path);
}
image::open(path).map_err(RegisterImageError::Image)
}

View File

@ -23,8 +23,8 @@ use image::{DynamicImage, ImageError};
use std::{
borrow::Cow,
collections::HashMap,
fmt, io,
path::PathBuf,
fmt, fs, io,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
@ -187,6 +187,14 @@ impl ImageRegistry {
(ImageSource::Filesystem(path.clone()), Some(path.clone()))
}
};
let spec = match spec {
ImageSpec::Filesystem(ref path)
if path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("svg")) =>
{
ImageSpec::Generated(rasterize_svg(path)?)
}
other => other,
};
let resource = self.printer.register(spec)?;
let image = Image::new(resource, source);
if let Some(key) = cache_key {
@ -205,6 +213,21 @@ pub(crate) enum ImageSpec {
Filesystem(PathBuf),
}
pub(crate) fn rasterize_svg(path: &Path) -> Result<DynamicImage, RegisterImageError> {
use resvg::{tiny_skia, usvg};
let data = fs::read(path)?;
let tree = usvg::Tree::from_data(&data, &usvg::Options::default())
.map_err(|e| RegisterImageError::Svg(e.to_string()))?;
let size = tree.size();
let mut pixmap = tiny_skia::Pixmap::new(size.width() as u32, size.height() as u32)
.ok_or_else(|| RegisterImageError::Svg("invalid SVG dimensions".into()))?;
resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
let rgba = image::RgbaImage::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
.ok_or_else(|| RegisterImageError::Svg("rasterization failed".into()))?;
Ok(DynamicImage::from(rgba))
}
#[derive(Debug, thiserror::Error)]
pub(crate) enum CreatePrinterError {
#[error("io: {0}")]
@ -249,6 +272,9 @@ pub(crate) enum RegisterImageError {
#[error("image decoding: {0}")]
Image(#[from] ImageError),
#[error("svg: {0}")]
Svg(String),
#[error("printer can't register images")]
Unsupported,
}