diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | Cargo.lock | 279 | ||||
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/errors.rs | 6 | ||||
-rw-r--r-- | src/image/image_view.rs | 3 | ||||
-rw-r--r-- | src/image/mod.rs | 26 | ||||
-rw-r--r-- | src/image/svg.rs | 71 |
7 files changed, 383 insertions, 7 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 642fc97..d32d0ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### next - unless overriden, `/proc` is now no-enter - See #639 +- SVG files now rendered as images in the preview panel - preview can now be opened on directories, showing their first level - Fix #405 - new version of the nushell function. You should be prompted for an update - "no-hide" special paths - Thanks @Avlllo @@ -89,6 +89,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "149f75bbec1827618262e0855a68f0f9a7f2edc13faebf33c4f16d6725edb6a9" [[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -175,6 +187,7 @@ dependencies = [ "phf", "rayon", "regex", + "resvg", "secular", "serde", "smallvec", @@ -185,10 +198,13 @@ dependencies = [ "termimad", "terminal-clipboard", "terminal-light", + "tiny-skia", "toml", "umask", "unicode-width", "users", + "usvg", + "usvg-text-layout", "xterm-query 0.1.0", ] @@ -585,6 +601,12 @@ dependencies = [ ] [[package]] +name = "data-url" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" + +[[package]] name = "deser-hjson" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -666,7 +688,7 @@ dependencies = [ "flume", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.6.2", "smallvec", "threadpool", ] @@ -705,10 +727,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.6.2", ] [[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] name = "flume" version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -728,6 +756,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "fontconfig-parser" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be17a530a842f8a7a60f4397a08e8f08872849a5e31b20c7bd7301dac483296" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8131752b3f3b876a20f42b3d08233ad177d6e7ec6d18aaa6954489a201071be5" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "ttf-parser", +] + +[[package]] name = "form_urlencoded" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -928,6 +977,12 @@ dependencies = [ ] [[package]] +name = "imagesize" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df19da1e92fbfec043ca97d622955381b1f3ee72a180ec999912df31b1ccd951" + +[[package]] name = "include_dir" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1014,6 +1069,15 @@ dependencies = [ ] [[package]] +name = "kurbo" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +dependencies = [ + "arrayvec", +] + +[[package]] name = "lazy-regex" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1187,6 +1251,15 @@ dependencies = [ [[package]] name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" @@ -1402,6 +1475,12 @@ dependencies = [ ] [[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.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1443,14 +1522,14 @@ dependencies = [ [[package]] name = "png" -version = "0.17.7" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +checksum = "8f0e7f4c94ec26ff209cee506314212639d6c91b80afb82984819fafce9df01c" dependencies = [ "bitflags", "crc32fast", "flate2", - "miniz_oxide", + "miniz_oxide 0.5.4", ] [[package]] @@ -1578,6 +1657,12 @@ dependencies = [ ] [[package]] +name = "rctree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" + +[[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1630,6 +1715,25 @@ dependencies = [ ] [[package]] +name = "resvg" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c115863f2d3621999cf187e318bc92b16402dfeff6a48c74df700d77381394c1" +dependencies = [ + "gif", + "jpeg-decoder", + "log", + "pico-args", + "png", + "rgb", + "svgfilters", + "svgtypes", + "tiny-skia", + "usvg", + "usvg-text-layout", +] + +[[package]] name = "rgb" version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1639,6 +1743,15 @@ dependencies = [ ] [[package]] +name = "roxmltree" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9de9831a129b122e7e61f242db509fa9d0838008bf0b29bb0624669edfe48a" +dependencies = [ + "xmlparser", +] + +[[package]] name = "rusqlite" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1654,6 +1767,22 @@ dependencies = [ ] [[package]] +name = "rustybuzz" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9e34ecf6900625412355a61bda0bd68099fe674de707c67e5e4aed2c05e489" +dependencies = [ + "bitflags", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + +[[package]] name = "ryu" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1763,6 +1892,15 @@ dependencies = [ ] [[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] name = "siphasher" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1824,6 +1962,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "991af58f8bd0512b0c76abc87f8f6a8a492c314ebcd13189b426c00c95f6f0ee" [[package]] +name = "strict-num" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1" +dependencies = [ + "float-cmp", +] + +[[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1842,6 +1989,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e72d8b19ab05827afefcca66bf47040c1e66a0901eb814784c77d4ec118bd309" [[package]] +name = "svgfilters" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" +dependencies = [ + "float-cmp", + "rgb", +] + +[[package]] +name = "svgtypes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" +dependencies = [ + "siphasher", +] + +[[package]] name = "syn" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2026,6 +2192,31 @@ dependencies = [ ] [[package]] +name = "tiny-skia" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae12c22601b6853f4d93abb178e13bf0e1cc8e2454100c85d4d3a59ac71b3f7" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd665853ce64402daabef6edda442dbb4f8ee93ea80957b66ba1af419f11a104" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2050,6 +2241,12 @@ dependencies = [ ] [[package]] +name = "ttf-parser" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" + +[[package]] name = "umask" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2062,6 +2259,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + +[[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2077,6 +2292,18 @@ dependencies = [ ] [[package]] +name = "unicode-script" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d817255e1bed6dfd4ca47258685d14d2bdcfbc64fdc9e3819bd5848057b8ecc" + +[[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.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2104,6 +2331,42 @@ dependencies = [ ] [[package]] +name = "usvg" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5b7c2b30845b3348c067ca3d09e20cc6e327c288f0ca4c48698712abf432e9" +dependencies = [ + "base64", + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "rctree", + "roxmltree", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", +] + +[[package]] +name = "usvg-text-layout" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9550670848028641bf976b06f5c520ffdcd6f00ee7ee7eb0853f78e2c249d7" +dependencies = [ + "fontdb", + "kurbo", + "log", + "rustybuzz", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg", +] + +[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2313,6 +2576,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" [[package]] +name = "xmlparser" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" + +[[package]] name = "xterm-query" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -50,6 +50,7 @@ pathdiff = "0.2" phf = { version = "0.10", features = ["macros"] } rayon = "1.5" regex = "1.5" +resvg = "0.28.0" secular = { version = "1.0", features = ["normalization"] } serde = { version = "1.0", features = ["derive"] } smallvec = "1.9" @@ -60,9 +61,12 @@ tempfile = "3.2" termimad = "0.20.6" terminal-clipboard = { version = "0.3.1", optional = true } terminal-light = "1.1" +tiny-skia = "0.8.2" toml = "0.5" umask = "2.0.0" unicode-width = "0.1.8" +usvg = "0.28.0" +usvg-text-layout = { version = "0.28.0", default-features = false } xterm-query = { version = "0.1", optional = true } [dev-dependencies] diff --git a/src/errors.rs b/src/errors.rs index 5ae94d5..eb475b9 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -21,6 +21,7 @@ custom_error! {pub ProgramError NetError {source: NetError} = "{source}", OpenError { source: opener::OpenError } = "Open error: {source}", ShelInstall { source: ShellInstallError } = "{source}", + Svg {source: SvgError} = "SVG error: {source}", SyntectCrashed { details: String } = "Syntect crashed on {details:?}", Termimad {source: termimad::Error} = "Termimad Error : {source}", TreeBuild {source: TreeBuildError} = "{source}", @@ -115,3 +116,8 @@ custom_error! {pub NetError InvalidMessage = "invalid message received", } +custom_error! {pub SvgError + Io {source: io::Error} = "IO Error : {source}", + Internal { message: &'static str } = "Internal error : {message}", + Svg {source: usvg::Error} = "SVG Error: {source}", +} diff --git a/src/image/image_view.rs b/src/image/image_view.rs index 9a5c793..21e4285 100644 --- a/src/image/image_view.rs +++ b/src/image/image_view.rs @@ -16,7 +16,6 @@ use { QueueableCommand, }, image::{ - io::Reader, DynamicImage, GenericImageView, imageops::FilterType, @@ -62,7 +61,7 @@ impl ImageView { let source_img = time!( "decode image", path, - Reader::open(path)?.decode()? + super::load(path)? ); Ok(Self { path: path.to_path_buf(), diff --git a/src/image/mod.rs b/src/image/mod.rs index caf5c13..7672ec1 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -1,7 +1,33 @@ mod double_line; mod image_view; +mod svg; pub use { image_view::ImageView, }; + +use { + crate::errors::ProgramError, + image::{ + io::Reader, + DynamicImage, + }, + std::path::Path, +}; + +// Max dimensions of the SVG image to render. A bigger size just makes it need +// a little more memory and takes more time to render. There's no quality gain +// in having this bigger than your screen +pub const MAX_SVG_BITMAP_WIDTH: u32 = 1000; +pub const MAX_SVG_BITMAP_HEIGHT: u32 = 1000; + +pub fn load(path: &Path) -> Result<DynamicImage, ProgramError> { + let is_svg = matches!(path.extension(), Some(ext) if ext == "svg" || ext == "SVG"); + let img = if is_svg { + svg::render(path, MAX_SVG_BITMAP_WIDTH, MAX_SVG_BITMAP_HEIGHT)? + } else { + Reader::open(path)?.decode()? + }; + Ok(img) +} diff --git a/src/image/svg.rs b/src/image/svg.rs new file mode 100644 index 0000000..c2347a0 --- /dev/null +++ b/src/image/svg.rs @@ -0,0 +1,71 @@ +use { + crate::{ + errors::SvgError, + }, + image::{ + DynamicImage, + RgbaImage, + }, + std::path::PathBuf, + usvg::ScreenSize, + usvg_text_layout::{fontdb, TreeTextToPath}, +}; + +fn compute_zoom(width:u32, height:u32, max_width:u32, max_height:u32) -> Result<f32, SvgError> { + let w: f32 = width as f32; + let h: f32 = height as f32; + let mw: f32 = max_width.max(2) as f32; + let mh: f32 = max_height.max(2) as f32; + let zoom = 1.0f32 + .min(mw / w) + .min(mh / h); + if zoom > 0.0f32 { + Ok(zoom) + } else { + Err(SvgError::Internal { message: "invalid SVG dimensions" }) + } +} + +/// Generate a bitmap at the natural dimensions of the SVG unless it's too big +/// in which case a smaller one is generated to fit into (max_width x max_height). +pub fn render<P: Into<PathBuf>>( + path: P, + max_width: u32, + max_height: u32, +) -> Result<DynamicImage, SvgError> { + let path: PathBuf = path.into(); + let mut opt = usvg::Options::default(); + opt.resources_dir = Some(path.clone()); + let mut fontdb = fontdb::Database::new(); + fontdb.load_system_fonts(); + let svg_data = std::fs::read(path)?; + let mut tree = usvg::Tree::from_data(&svg_data, &opt)?; + debug!("SVG natural size: {} x {}", tree.size.width(), tree.size.height()); + let px_size = tree.size.to_screen_size(); + let zoom = compute_zoom(px_size.width(), px_size.height(), max_width, max_height)?; + debug!("svg rendering zoom: {zoom}"); + let Some(px_size) = ScreenSize::new( + (px_size.width() as f32 * zoom) as u32, + (px_size.height() as f32 * zoom) as u32, + ) else { + return Err(SvgError::Internal { message: "invalid SVG dimensions" }); + }; + debug!("px_size: {px_size:?}"); + tree.convert_text(&fontdb, opt.keep_named_groups); + let mut pixmap = tiny_skia::Pixmap::new( + px_size.width(), + px_size.height(), + ).ok_or(SvgError::Internal { message: "unable to create pixmap buffer" })?; + resvg::render( + &tree, + usvg::FitTo::Zoom(zoom), + tiny_skia::Transform::default(), + pixmap.as_mut(), + ).ok_or(SvgError::Internal { message: "resvg doesn't look happy (not sure)" })?; + let image_buffer = RgbaImage::from_vec( + pixmap.width(), + pixmap.height(), + pixmap.take(), + ).ok_or(SvgError::Internal { message: "wrong image buffer size" })?; + Ok(DynamicImage::ImageRgba8(image_buffer)) +} |