From 9022fb4ce9898bd305cb729dec57d0894294f15d Mon Sep 17 00:00:00 2001 From: liquidev Date: Sat, 23 Nov 2024 20:43:26 +0100 Subject: [PATCH] add image size metadata to the filesystem --- Cargo.lock | 413 +++++++++++++++++- crates/treehouse/Cargo.toml | 2 +- crates/treehouse/src/config.rs | 16 +- crates/treehouse/src/html/djot.rs | 44 +- crates/treehouse/src/main.rs | 5 +- crates/treehouse/src/vfs.rs | 22 + crates/treehouse/src/vfs/anchored.rs | 10 +- crates/treehouse/src/vfs/cd.rs | 6 +- .../src/vfs/content_version_cache.rs | 6 +- crates/treehouse/src/vfs/image_size_cache.rs | 89 ++++ crates/treehouse/src/vfs/mem_dir.rs | 13 +- crates/treehouse/src/vfs/overlay.rs | 8 +- crates/treehouse/tests/it/vfs/mount_points.rs | 2 - 13 files changed, 580 insertions(+), 56 deletions(-) create mode 100644 crates/treehouse/src/vfs/image_size_cache.rs diff --git a/Cargo.lock b/Cargo.lock index 80f5091..7f29534 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -96,6 +102,23 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -104,9 +127,9 @@ checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-trait" @@ -125,6 +148,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "av1-grain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +dependencies = [ + "arrayvec", +] + [[package]] name = "axum" version = "0.7.5" @@ -219,6 +265,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bitstream-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" + [[package]] name = "blake3" version = "1.5.3" @@ -241,6 +293,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "built" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" + [[package]] name = "bumpalo" version = "3.16.0" @@ -254,10 +312,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] -name = "byteorder" -version = "1.5.0" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" @@ -276,6 +334,16 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -795,22 +863,43 @@ dependencies = [ [[package]] name = "image" -version = "0.24.9" +version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imgref" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" + [[package]] name = "indexmap" version = "2.2.6" @@ -822,6 +911,17 @@ dependencies = [ "serde", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -839,6 +939,15 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -865,9 +974,6 @@ name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -890,6 +996,16 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libfuzzer-sys" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +dependencies = [ + "arbitrary", + "cc", +] + [[package]] name = "libgit2-sys" version = "0.17.0+1.8.1" @@ -930,12 +1046,31 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "matchit" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.7.2" @@ -948,6 +1083,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -969,6 +1110,69 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1026,6 +1230,12 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1143,6 +1353,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "qoi" version = "0.4.1" @@ -1152,6 +1381,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.36" @@ -1191,6 +1426,56 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1249,6 +1534,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1376,6 +1667,15 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1430,6 +1730,25 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "termcolor" version = "1.4.1" @@ -1515,6 +1834,18 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.16", +] + [[package]] name = "toml_datetime" version = "0.6.6" @@ -1534,7 +1865,20 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.20", ] [[package]] @@ -1611,7 +1955,7 @@ dependencies = [ "serde", "serde_json", "tokio", - "toml_edit", + "toml_edit 0.19.15", "treehouse-format", "ulid", "url", @@ -1692,12 +2036,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.4" @@ -1956,6 +2317,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -1964,3 +2340,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +dependencies = [ + "zune-core", +] diff --git a/crates/treehouse/Cargo.toml b/crates/treehouse/Cargo.toml index 81afda8..08f278c 100644 --- a/crates/treehouse/Cargo.toml +++ b/crates/treehouse/Cargo.toml @@ -20,7 +20,7 @@ env_logger = "0.10.0" git2 = { version = "0.19.0", default-features = false, features = ["vendored-libgit2"] } handlebars = "4.3.7" http-body = "1.0.0" -image = "0.24.8" +image = "0.25.5" indexmap = { version = "2.2.6", features = ["serde"] } jotdown = { version = "0.4.1", default-features = false } log = { workspace = true } diff --git a/crates/treehouse/src/config.rs b/crates/treehouse/src/config.rs index 651ec6a..945f497 100644 --- a/crates/treehouse/src/config.rs +++ b/crates/treehouse/src/config.rs @@ -10,7 +10,7 @@ use crate::{ Syntax, }, import_map::ImportRoot, - vfs::{self, Dir, VPath, VPathBuf}, + vfs::{self, Dir, ImageSize, VPath, VPathBuf}, }; #[derive(Debug, Clone, Deserialize, Serialize)] @@ -101,7 +101,7 @@ pub enum Markup { impl Config { pub fn autopopulate_emoji(&mut self, dir: &dyn Dir) -> anyhow::Result<()> { vfs::walk_dir_rec(dir, VPath::ROOT, &mut |path| { - if path.extension().is_some_and(is_emoji_file) { + if path.extension().is_some_and(is_image_file) { if let Some(emoji_name) = path.file_stem() { if !self.emoji.contains_key(emoji_name) { self.emoji.insert(emoji_name.to_owned(), path.to_owned()); @@ -117,7 +117,7 @@ impl Config { pub fn autopopulate_pics(&mut self, dir: &dyn Dir) -> anyhow::Result<()> { vfs::walk_dir_rec(dir, VPath::ROOT, &mut |path| { - if path.extension().is_some_and(is_pic_file) { + if path.extension().is_some_and(is_image_file) { if let Some(pic_name) = path.file_stem() { let pic_id = pic_name .split_once('-') @@ -151,6 +151,10 @@ impl Config { .expect("pics_dir is not anchored anywhere") } + pub fn pic_size(&self, pics_dir: &dyn Dir, id: &str) -> Option { + self.pics.get(id).and_then(|path| pics_dir.image_size(path)) + } + /// Loads all syntax definition files. pub fn load_syntaxes(&mut self, dir: &dyn Dir) -> anyhow::Result<()> { vfs::walk_dir_rec(dir, VPath::ROOT, &mut |path| { @@ -185,10 +189,6 @@ impl Config { } } -fn is_emoji_file(extension: &str) -> bool { - matches!(extension, "png" | "svg") -} - -fn is_pic_file(extension: &str) -> bool { +pub fn is_image_file(extension: &str) -> bool { matches!(extension, "png" | "svg" | "jpg" | "jpeg" | "webp") } diff --git a/crates/treehouse/src/html/djot.rs b/crates/treehouse/src/html/djot.rs index 4da9273..efbca09 100644 --- a/crates/treehouse/src/html/djot.rs +++ b/crates/treehouse/src/html/djot.rs @@ -379,19 +379,17 @@ impl<'a> Writer<'a> { write_attr(&pic_url, out); out.push('"'); - // TODO: Image size derivation. - // let image_size = filename.and_then(|filename| { - // self.renderer - // .config_derived_data - // .image_size(&format!("static/pic/{filename}")) - // }); - // if let Some(image_size) = image_size { - // write!( - // out, - // r#" width="{}" height="{}""#, - // image_size.width, image_size.height - // )?; - // } + if let Some(image_size) = self + .renderer + .config + .pic_size(&*self.renderer.dirs.pic, placeholder_pic_id) + { + write!( + out, + r#" width="{}" height="{}""#, + image_size.width, image_size.height + )?; + } out.push('>'); } @@ -480,6 +478,7 @@ impl<'a> Writer<'a> { if !src.is_empty() { out.push_str(r#"" src=""#); if let SpanLinkType::Unresolved = link_type { + // TODO: Image size. if let Some(resolved) = self.resolve_link(src) { write_attr(&resolved, out); } else { @@ -577,18 +576,13 @@ impl<'a> Writer<'a> { write_attr(&url, out); out.push('"'); - // TODO: Image size derivation. - // if let Some(image_size) = self - // .renderer - // .config_derived_data - // .image_size(&format!("static/emoji/{vpath}")) - // { - // write!( - // out, - // r#" width="{}" height="{}""#, - // image_size.width, image_size.height - // )?; - // } + if let Some(image_size) = self.renderer.dirs.emoji.image_size(vpath) { + write!( + out, + r#" width="{}" height="{}""#, + image_size.width, image_size.height + )?; + } out.push('>'); diff --git a/crates/treehouse/src/main.rs b/crates/treehouse/src/main.rs index 3741868..0fa55c4 100644 --- a/crates/treehouse/src/main.rs +++ b/crates/treehouse/src/main.rs @@ -9,7 +9,9 @@ use treehouse::cli::serve::serve; use treehouse::dirs::Dirs; use treehouse::generate::{self, Sources}; use treehouse::vfs::asynch::AsyncDir; -use treehouse::vfs::{AnchoredAtExt, Blake3ContentVersionCache, DynDir, ToDynDir, VPathBuf}; +use treehouse::vfs::{ + AnchoredAtExt, Blake3ContentVersionCache, DynDir, ImageSizeCache, ToDynDir, VPathBuf, +}; use treehouse::vfs::{Cd, PhysicalDir}; use treehouse::{ cli::{ @@ -43,6 +45,7 @@ fn vfs_sources() -> anyhow::Result { ); let root = Blake3ContentVersionCache::new(root); + let root = ImageSizeCache::new(root); Ok(root.to_dyn()) } diff --git a/crates/treehouse/src/vfs.rs b/crates/treehouse/src/vfs.rs index 0ae486d..124e730 100644 --- a/crates/treehouse/src/vfs.rs +++ b/crates/treehouse/src/vfs.rs @@ -56,6 +56,7 @@ mod content_version_cache; mod edit; mod empty; mod file; +mod image_size_cache; mod mem_dir; mod overlay; mod path; @@ -67,6 +68,7 @@ pub use content_version_cache::*; pub use edit::*; pub use empty::*; pub use file::*; +pub use image_size_cache::*; pub use mem_dir::*; pub use overlay::*; pub use path::*; @@ -77,6 +79,12 @@ pub struct DirEntry { pub path: VPathBuf, } +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ImageSize { + pub width: u32, + pub height: u32, +} + pub trait Dir: Debug { /// List all entries under the provided path. fn dir(&self, path: &VPath) -> Vec; @@ -90,6 +98,12 @@ pub trait Dir: Debug { /// Returns None if there is no content or no version string is available. fn content_version(&self, path: &VPath) -> Option; + /// Returns the size of the image at the given path, or `None` if the entry is not an image + /// (or its size cannot be known.) + fn image_size(&self, _path: &VPath) -> Option { + None + } + /// Returns a path relative to `config.site` indicating where the file will be available /// once served. /// @@ -123,6 +137,10 @@ where (**self).content_version(path) } + fn image_size(&self, path: &VPath) -> Option { + (**self).image_size(path) + } + fn anchor(&self, path: &VPath) -> Option { (**self).anchor(path) } @@ -150,6 +168,10 @@ impl Dir for DynDir { self.arc.content_version(path) } + fn image_size(&self, path: &VPath) -> Option { + self.arc.image_size(path) + } + fn anchor(&self, path: &VPath) -> Option { self.arc.anchor(path) } diff --git a/crates/treehouse/src/vfs/anchored.rs b/crates/treehouse/src/vfs/anchored.rs index c537068..6af0fe4 100644 --- a/crates/treehouse/src/vfs/anchored.rs +++ b/crates/treehouse/src/vfs/anchored.rs @@ -1,6 +1,6 @@ use std::fmt; -use super::{Dir, DirEntry, VPath, VPathBuf}; +use super::{Dir, DirEntry, EditPath, ImageSize, VPath, VPathBuf}; pub struct Anchored { inner: T, @@ -29,9 +29,17 @@ where self.inner.content_version(path) } + fn image_size(&self, path: &VPath) -> Option { + self.inner.image_size(path) + } + fn anchor(&self, path: &VPath) -> Option { Some(self.at.join(path)) } + + fn edit_path(&self, path: &VPath) -> Option { + self.inner.edit_path(path) + } } impl fmt::Debug for Anchored diff --git a/crates/treehouse/src/vfs/cd.rs b/crates/treehouse/src/vfs/cd.rs index 23b2259..baa61aa 100644 --- a/crates/treehouse/src/vfs/cd.rs +++ b/crates/treehouse/src/vfs/cd.rs @@ -1,6 +1,6 @@ use std::fmt; -use super::{Dir, DirEntry, EditPath, VPath, VPathBuf}; +use super::{Dir, DirEntry, EditPath, ImageSize, VPath, VPathBuf}; pub struct Cd { parent: T, @@ -39,6 +39,10 @@ where self.parent.content(&self.path.join(path)) } + fn image_size(&self, path: &VPath) -> Option { + self.parent.image_size(&self.path.join(path)) + } + fn anchor(&self, path: &VPath) -> Option { self.parent.anchor(&self.path.join(path)) } diff --git a/crates/treehouse/src/vfs/content_version_cache.rs b/crates/treehouse/src/vfs/content_version_cache.rs index 3dfe74b..69cd967 100644 --- a/crates/treehouse/src/vfs/content_version_cache.rs +++ b/crates/treehouse/src/vfs/content_version_cache.rs @@ -2,7 +2,7 @@ use std::fmt::{self, Debug}; use dashmap::DashMap; -use super::{Dir, DirEntry, EditPath, VPath, VPathBuf}; +use super::{Dir, DirEntry, EditPath, ImageSize, VPath, VPathBuf}; pub struct Blake3ContentVersionCache { inner: T, @@ -42,6 +42,10 @@ where .clone() } + fn image_size(&self, path: &VPath) -> Option { + self.inner.image_size(path) + } + fn anchor(&self, path: &VPath) -> Option { self.inner.anchor(path) } diff --git a/crates/treehouse/src/vfs/image_size_cache.rs b/crates/treehouse/src/vfs/image_size_cache.rs new file mode 100644 index 0000000..2255f64 --- /dev/null +++ b/crates/treehouse/src/vfs/image_size_cache.rs @@ -0,0 +1,89 @@ +use std::{fmt, io::Cursor}; + +use anyhow::Context; +use dashmap::DashMap; +use log::{debug, warn}; + +use crate::config; + +use super::{Dir, DirEntry, EditPath, ImageSize, VPath, VPathBuf}; + +pub struct ImageSizeCache { + inner: T, + cache: DashMap>, +} + +impl ImageSizeCache { + pub fn new(inner: T) -> Self { + Self { + inner, + cache: DashMap::new(), + } + } +} + +impl ImageSizeCache +where + T: Dir, +{ + fn compute_image_size(&self, path: &VPath) -> anyhow::Result> { + if path.extension().is_some_and(config::is_image_file) { + if let Some(content) = self.content(path) { + let reader = image::ImageReader::new(Cursor::new(content)) + .with_guessed_format() + .context("cannot guess image format")?; + let (width, height) = reader.into_dimensions()?; + debug!("image_size({path}) = ({width}, {height})"); + return Ok(Some(ImageSize { width, height })); + } + } + + Ok(None) + } +} + +impl Dir for ImageSizeCache +where + T: Dir, +{ + fn dir(&self, path: &VPath) -> Vec { + self.inner.dir(path) + } + + fn content(&self, path: &VPath) -> Option> { + self.inner.content(path) + } + + fn content_version(&self, path: &VPath) -> Option { + self.inner.content_version(path) + } + + fn image_size(&self, path: &VPath) -> Option { + self.cache + .entry(path.to_owned()) + .or_insert_with(|| { + self.compute_image_size(path) + .inspect_err(|err| warn!("compute_image_size({path}) failed: {err:?}")) + .ok() + .flatten() + }) + .clone() + } + + fn anchor(&self, path: &VPath) -> Option { + self.inner.anchor(path) + } + + fn edit_path(&self, path: &VPath) -> Option { + self.inner.edit_path(path) + } +} + +impl fmt::Debug for ImageSizeCache +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ImageSizeCache({:?})", self.inner) + } +} diff --git a/crates/treehouse/src/vfs/mem_dir.rs b/crates/treehouse/src/vfs/mem_dir.rs index 7bc5503..a799749 100644 --- a/crates/treehouse/src/vfs/mem_dir.rs +++ b/crates/treehouse/src/vfs/mem_dir.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, fmt}; -use super::{Dir, DirEntry, DynDir, EditPath, VPath, VPathBuf}; +use super::{Dir, DirEntry, DynDir, EditPath, ImageSize, VPath, VPathBuf}; pub struct MemDir { mount_points: HashMap, @@ -110,6 +110,17 @@ impl Dir for MemDir { } } + fn image_size(&self, path: &VPath) -> Option { + match self.resolve(path) { + Resolved::MountPoint { + fs, + fs_path: _, + subpath, + } => fs.image_size(subpath), + Resolved::Root | Resolved::None => None, + } + } + fn anchor(&self, path: &VPath) -> Option { match self.resolve(path) { Resolved::MountPoint { diff --git a/crates/treehouse/src/vfs/overlay.rs b/crates/treehouse/src/vfs/overlay.rs index 695114c..b65fc4e 100644 --- a/crates/treehouse/src/vfs/overlay.rs +++ b/crates/treehouse/src/vfs/overlay.rs @@ -1,6 +1,6 @@ use std::fmt; -use super::{Dir, DirEntry, DynDir, EditPath, VPath, VPathBuf}; +use super::{Dir, DirEntry, DynDir, EditPath, ImageSize, VPath, VPathBuf}; pub struct Overlay { base: DynDir, @@ -34,6 +34,12 @@ impl Dir for Overlay { .or_else(|| self.base.content_version(path)) } + fn image_size(&self, path: &VPath) -> Option { + self.overlay + .image_size(path) + .or_else(|| self.base.image_size(path)) + } + fn anchor(&self, path: &VPath) -> Option { self.overlay.anchor(path).or_else(|| self.base.anchor(path)) } diff --git a/crates/treehouse/tests/it/vfs/mount_points.rs b/crates/treehouse/tests/it/vfs/mount_points.rs index c7aec49..22a1a59 100644 --- a/crates/treehouse/tests/it/vfs/mount_points.rs +++ b/crates/treehouse/tests/it/vfs/mount_points.rs @@ -1,5 +1,3 @@ -use std::sync::Arc; - use treehouse::vfs::{BufferedFile, Dir, DirEntry, MemDir, ToDynDir, VPath, VPathBuf}; const HEWWO: &[u8] = b"hewwo :3";