feat: svg support

This commit is contained in:
electria 2026-08-14 10:25:46 -07:00
commit a87a7bc0a1
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
5 changed files with 301 additions and 8 deletions

245
Cargo.lock generated
View file

@ -403,6 +403,12 @@ dependencies = [
"arrayvec",
]
[[package]]
name = "base64"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "bit-set"
version = "0.8.0"
@ -841,8 +847,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173852283a9a57a3cbe365d86e74dc428a09c50421477d5ad6fe9d9509e37737"
dependencies = [
"bitflags 2.13.0",
"fontdb",
"harfrust",
"fontdb 0.23.0",
"harfrust 0.3.2",
"linebender_resource_handle",
"log",
"rangemap",
@ -959,6 +965,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
[[package]]
name = "data-url"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]]
name = "dav1d"
version = "0.11.1"
@ -1264,6 +1276,12 @@ dependencies = [
"zlib-rs",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
[[package]]
name = "foldhash"
version = "0.1.5"
@ -1294,13 +1312,22 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "font-types"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75382bc7392ef10aad10935f92fc3db36d2d4dad0e5d96d8d65e04f89a07ec39"
dependencies = [
"bytemuck",
]
[[package]]
name = "fontconfig-parser"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
dependencies = [
"roxmltree",
"roxmltree 0.20.0",
]
[[package]]
@ -1317,6 +1344,19 @@ dependencies = [
"ttf-parser",
]
[[package]]
name = "fontdb"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2660c5e9157bf76d2db1294e4a9feba604ef610819a3b591088d0d8392a3290f"
dependencies = [
"fontconfig-parser",
"log",
"memmap2",
"slotmap",
"tinyvec",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@ -1625,6 +1665,18 @@ dependencies = [
"smallvec",
]
[[package]]
name = "harfrust"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9"
dependencies = [
"bitflags 2.13.0",
"bytemuck",
"read-fonts 0.41.0",
"smallvec",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
@ -1827,11 +1879,11 @@ dependencies = [
"cosmic-text",
"iced_debug",
"iced_graphics",
"kurbo",
"kurbo 0.10.4",
"log",
"rustc-hash 2.1.2",
"softbuffer",
"tiny-skia",
"tiny-skia 0.11.4",
]
[[package]]
@ -1923,6 +1975,12 @@ dependencies = [
"quick-error",
]
[[package]]
name = "imagesize"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65b27460c2c92b037f3f94c538ed9a3342f3fdf923606781629ccb35f82d042a"
[[package]]
name = "imagey"
version = "0.1.0"
@ -1933,6 +1991,7 @@ dependencies = [
"image",
"jxl-oxide",
"kra-image-integration",
"resvg",
"rfd",
"size",
"zip",
@ -2288,6 +2347,18 @@ dependencies = [
"smallvec",
]
[[package]]
name = "kurbo"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2"
dependencies = [
"arrayvec",
"euclid",
"polycool",
"smallvec",
]
[[package]]
name = "lebe"
version = "0.5.3"
@ -3191,6 +3262,12 @@ dependencies = [
"indexmap",
]
[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
version = "1.1.13"
@ -3279,6 +3356,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]]
name = "polycool"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6"
dependencies = [
"arrayvec",
]
[[package]]
name = "portable-atomic"
version = "1.13.1"
@ -3547,6 +3633,17 @@ dependencies = [
"font-types 0.11.3",
]
[[package]]
name = "read-fonts"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
dependencies = [
"bytemuck",
"font-types 0.12.3",
"once_cell",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@ -3591,6 +3688,24 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]]
name = "resvg"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67e3803f97b999e80cbf7c6ecdd07a8102204d92e1633cf48783720c521196bd"
dependencies = [
"bytemuck",
"gif",
"image-webp",
"log",
"pico-args",
"rgb",
"svgtypes",
"tiny-skia 0.12.0",
"usvg",
"zune-jpeg",
]
[[package]]
name = "rfd"
version = "0.17.2"
@ -3623,6 +3738,9 @@ name = "rgb"
version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
dependencies = [
"bytemuck",
]
[[package]]
name = "roxmltree"
@ -3630,6 +3748,15 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
[[package]]
name = "roxmltree"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb"
dependencies = [
"memchr",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -3714,7 +3841,7 @@ dependencies = [
"log",
"memmap2",
"smithay-client-toolkit 0.19.2",
"tiny-skia",
"tiny-skia 0.11.4",
]
[[package]]
@ -3848,6 +3975,21 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "simplecss"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
dependencies = [
"log",
]
[[package]]
name = "siphasher"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "size"
version = "0.5.0"
@ -3874,6 +4016,16 @@ dependencies = [
"read-fonts 0.39.2",
]
[[package]]
name = "skrifa"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
dependencies = [
"bytemuck",
"read-fonts 0.41.0",
]
[[package]]
name = "slab"
version = "0.4.12"
@ -4024,6 +4176,9 @@ name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
dependencies = [
"float-cmp",
]
[[package]]
name = "svg_fmt"
@ -4031,6 +4186,16 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
[[package]]
name = "svgtypes"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d"
dependencies = [
"kurbo 0.13.1",
"siphasher",
]
[[package]]
name = "swash"
version = "0.2.9"
@ -4188,7 +4353,22 @@ dependencies = [
"bytemuck",
"cfg-if",
"log",
"tiny-skia-path",
"tiny-skia-path 0.11.4",
]
[[package]]
name = "tiny-skia"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
dependencies = [
"arrayref",
"arrayvec",
"bytemuck",
"cfg-if",
"log",
"png",
"tiny-skia-path 0.12.0",
]
[[package]]
@ -4202,6 +4382,17 @@ dependencies = [
"strict-num",
]
[[package]]
name = "tiny-skia-path"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9"
dependencies = [
"arrayref",
"bytemuck",
"strict-num",
]
[[package]]
name = "tiny-xlib"
version = "0.2.5"
@ -4386,12 +4577,46 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "usvg"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "977d0a4abdef933f424a99fe09f95576e089b90aebc6f016a3bc813762493e91"
dependencies = [
"base64",
"data-url",
"flate2",
"fontdb 0.24.0",
"harfrust 0.12.0",
"imagesize",
"kurbo 0.13.1",
"log",
"pico-args",
"roxmltree 0.21.1",
"simplecss",
"siphasher",
"skrifa 0.44.0",
"strict-num",
"svgtypes",
"tiny-skia-path 0.12.0",
"unicode-bidi",
"unicode-script",
"unicode-vo",
"xmlwriter",
]
[[package]]
name = "uuid"
version = "1.23.4"
@ -5268,6 +5493,12 @@ version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "y4m"
version = "0.8.0"

View file

@ -7,6 +7,7 @@ license = "AGPL-3.0-or-later"
[dependencies]
dirs = "6.0.0"
kra-image-integration = "0.1.0"
resvg = "0.48.1"
rfd = "0.17.2"
size = "0.5.0"
zip = "8.6.0"

View file

@ -15,6 +15,7 @@ use iced::{
use image::{EncodableLayout, RgbaImage, imageops};
use size::Size;
mod svg;
mod utils;
#[derive(Clone, Debug)]
@ -302,6 +303,7 @@ impl State {
fn main() -> Result<(), iced::Error> {
jxl_oxide::integration::register_image_decoding_hook();
kra_image_integration::register();
svg::register();
iced::application(State::new, State::update, State::view)
.subscription(State::subscription)

59
src/svg.rs Normal file
View file

@ -0,0 +1,59 @@
use std::io::Read;
use image::{
ImageDecoder, ImageError, ImageResult,
error::DecodingError,
hooks::{self, GenericReader},
};
use resvg::usvg::{Options, Transform, Tree};
pub fn register() -> bool {
hooks::register_decoding_hook("svg".into(), Box::new(hook))
&& hooks::register_decoding_hook("svgz".into(), Box::new(hook))
}
fn hook<'a>(mut reader: GenericReader<'a>) -> ImageResult<Box<dyn ImageDecoder + 'a>> {
let mut buf = Vec::new();
reader.read_to_end(&mut buf)?;
let tree = Tree::from_data(&buf, &Options::default()).map_err(to_image_error)?;
Ok(Box::new(SvgDecoder::new(tree)))
}
struct SvgDecoder {
tree: Tree,
}
impl SvgDecoder {
fn new(tree: Tree) -> Self {
Self { tree }
}
}
impl ImageDecoder for SvgDecoder {
fn color_type(&self) -> image::ColorType {
image::ColorType::Rgba8
}
fn dimensions(&self) -> (u32, u32) {
self.tree.size().to_int_size().dimensions()
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()>
where
Self: Sized,
{
let (width, height) = self.dimensions();
let mut pixmap = resvg::tiny_skia::PixmapMut::from_bytes(buf, width, height).unwrap();
resvg::render(&self.tree, Transform::default(), &mut pixmap);
Ok(())
}
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
(*self).read_image(buf)
}
}
fn to_image_error(
e: impl Into<Box<dyn std::error::Error + std::marker::Send + Sync + 'static>>,
) -> ImageError {
ImageError::Decoding(DecodingError::new(
image::error::ImageFormatHint::PathExtension("svg".into()),
e,
))
}

View file

@ -40,7 +40,7 @@ pub async fn pick_image() -> Result<PathBuf, String> {
"image",
&[
"png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "avif", "jxl", "bmp", "exr", "ff",
"gif", "hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp", "kra",
"gif", "hdr", "ico", "pnm", "qoi", "tga", "tiff", "webp", "kra", "svg", "svgz",
],
)
.pick_file()