From b8c1a8503fd754d23bd972d4f500bea47fa9d2ea Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 11:33:24 +0100 Subject: [PATCH 1/8] debug info --- Cargo.lock | 216 ++++++++++++++++++++++++---- Cargo.toml | 1 + ferrules-core/Cargo.toml | 2 + ferrules-core/src/blocks.rs | 41 ++++-- ferrules-core/src/debug_info.rs | 34 +++++ ferrules-core/src/draw.rs | 8 +- ferrules-core/src/entities.rs | 87 +++++++++-- ferrules-core/src/layout/model.rs | 24 ++-- ferrules-core/src/lib.rs | 2 + ferrules-core/src/parse/document.rs | 54 ++++++- ferrules-core/src/parse/merge.rs | 2 +- ferrules-core/src/parse/page.rs | 18 ++- ferrules-core/src/parse/table.rs | 10 +- ferrules-core/src/render/html.rs | 14 +- 14 files changed, 419 insertions(+), 94 deletions(-) create mode 100644 ferrules-core/src/debug_info.rs diff --git a/Cargo.lock b/Cargo.lock index 2469a17..c96e3ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -157,7 +168,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -185,7 +196,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -196,7 +207,7 @@ checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -393,7 +404,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn", + "syn 2.0.96", "which", ] @@ -436,6 +447,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -472,6 +495,28 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.21.0" @@ -637,7 +682,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -888,7 +933,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -993,7 +1038,7 @@ checksum = "3bf679796c0322556351f287a51b49e48f7c4986e727b5dd78c972d30e2e16cc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -1105,6 +1150,7 @@ dependencies = [ "ab_glyph", "anyhow", "build_html", + "bytecheck", "colored", "criterion", "dirs", @@ -1126,6 +1172,7 @@ dependencies = [ "rand", "rayon", "regex", + "rkyv", "serde", "serde_json", "serde_millis", @@ -1210,6 +1257,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -1276,7 +1329,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -1400,6 +1453,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "hashbrown" @@ -1485,7 +1541,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -1973,7 +2029,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -2138,7 +2194,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -2641,7 +2697,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -2841,7 +2897,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -3111,7 +3167,7 @@ checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -3240,7 +3296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.96", ] [[package]] @@ -3268,7 +3324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -3291,7 +3347,27 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn", + "syn 2.0.96", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -3318,6 +3394,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -3507,6 +3589,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.12.12" @@ -3569,6 +3660,35 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3695,6 +3815,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" version = "2.11.1" @@ -3860,7 +3986,7 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -3978,6 +4104,12 @@ dependencies = [ "quote", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.1" @@ -4069,6 +4201,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.96" @@ -4097,7 +4240,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -4113,6 +4256,12 @@ dependencies = [ "version-compare", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.43" @@ -4181,7 +4330,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -4192,7 +4341,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -4309,7 +4458,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -4495,7 +4644,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -4812,7 +4961,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.96", "wasm-bindgen-shared", ] @@ -4846,7 +4995,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5175,6 +5324,15 @@ dependencies = [ "either", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "xattr" version = "1.4.0" @@ -5217,7 +5375,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", "synstructure", ] @@ -5239,7 +5397,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -5259,7 +5417,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", "synstructure", ] @@ -5299,7 +5457,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4b9027a..719146d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ uuid = { version = "1.11.0", features = ["v4"] } memmap2 = "0.9.5" clap = { version = "4.5.23", features = ["derive", "env"] } thiserror = "2.0.12" +rkyv = { version = "0.7.45", features = ["validation"] } [profile.release] strip = "symbols" diff --git a/ferrules-core/Cargo.toml b/ferrules-core/Cargo.toml index b2e24ab..c6889cb 100644 --- a/ferrules-core/Cargo.toml +++ b/ferrules-core/Cargo.toml @@ -18,6 +18,8 @@ tokio = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } +rkyv = { workspace = true } +bytecheck = "0.6.12" # pdf reader imageproc = "0.25.0" diff --git a/ferrules-core/src/blocks.rs b/ferrules-core/src/blocks.rs index 6666531..2630c1a 100644 --- a/ferrules-core/src/blocks.rs +++ b/ferrules-core/src/blocks.rs @@ -2,11 +2,14 @@ use crate::{ entities::{BBox, Element, ElementType, PageID}, error::FerrulesError, }; +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use serde::{Deserialize, Serialize}; pub type TitleLevel = u8; -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct ImageBlock { pub(crate) id: usize, pub(crate) caption: Option, @@ -18,17 +21,23 @@ impl ImageBlock { } } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct TextBlock { pub(crate) text: String, } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct List { pub(crate) items: Vec, } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub enum TableAlgorithm { #[default] Unknown, @@ -37,7 +46,9 @@ pub enum TableAlgorithm { Vision, } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct TableBlock { pub(crate) id: usize, pub(crate) caption: Option, @@ -52,29 +63,37 @@ impl TableBlock { } } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct TableRow { pub cells: Vec, pub is_header: bool, pub bbox: BBox, } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct TableCell { - pub content: Vec, + /// IDs of blocks contained within this cell. + /// This avoids recursion in serializable structures. + pub content_ids: Vec, pub text: String, pub row_span: u8, pub col_span: u8, pub bbox: BBox, } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive( + Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct Title { pub level: TitleLevel, pub text: String, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize)] #[serde(tag = "block_type")] pub enum BlockType { Header(TextBlock), @@ -92,7 +111,7 @@ impl std::fmt::Display for BlockType { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize)] pub struct Block { pub id: usize, pub kind: BlockType, diff --git a/ferrules-core/src/debug_info.rs b/ferrules-core/src/debug_info.rs new file mode 100644 index 0000000..7727ee0 --- /dev/null +++ b/ferrules-core/src/debug_info.rs @@ -0,0 +1,34 @@ +use crate::{ + blocks::Block, + entities::Element, + entities::{Line, PDFPath}, + layout::model::LayoutBBox, +}; +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; + +#[derive(Archive, RkyvDeserialize, RkyvSerialize, Debug, Clone)] +pub struct DebugPage { + pub page_number: usize, + /// Native lines from the PDF + pub native_lines: Vec, + /// Native paths/drawings from the PDF + pub paths: Vec, + /// Layout bounding boxes from the model + pub layout_bboxes: Vec, + /// OCR lines (if OCR was run) + pub ocr_lines: Vec, + /// Elements after merging native lines and layout + pub elements: Vec, + /// Final blocks after layout analysis + pub blocks: Vec, + /// Page image data (PNG encoded) + pub image_data: Vec, + pub width: f32, + pub height: f32, +} + +#[derive(Archive, RkyvDeserialize, RkyvSerialize, Debug, Clone)] +pub struct DebugDocument { + pub name: String, + pub pages: Vec, +} diff --git a/ferrules-core/src/draw.rs b/ferrules-core/src/draw.rs index c2d4deb..2248b16 100644 --- a/ferrules-core/src/draw.rs +++ b/ferrules-core/src/draw.rs @@ -325,7 +325,7 @@ mod tests { }, row_span: 1, col_span: 1, - content: vec![], + content_ids: vec![], }, TableCell { text: "Header 2".to_string(), @@ -337,7 +337,7 @@ mod tests { }, row_span: 1, col_span: 1, - content: vec![], + content_ids: vec![], }, ], }, @@ -360,7 +360,7 @@ mod tests { }, row_span: 1, col_span: 1, - content: vec![], + content_ids: vec![], }, TableCell { text: "Cell 2".to_string(), @@ -372,7 +372,7 @@ mod tests { }, row_span: 1, col_span: 1, - content: vec![], + content_ids: vec![], }, ], }, diff --git a/ferrules-core/src/entities.rs b/ferrules-core/src/entities.rs index 7af4063..6d71aee 100644 --- a/ferrules-core/src/entities.rs +++ b/ferrules-core/src/entities.rs @@ -1,5 +1,6 @@ use image::DynamicImage; use plsfix::fix_text; +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use serde::{Deserialize, Serialize}; use std::{path::PathBuf, time::Duration}; @@ -15,7 +16,9 @@ pub type ElementID = usize; const FERRULES_VERSION: &str = env!("CARGO_PKG_VERSION"); -#[derive(Debug, Default, Clone, Deserialize, Serialize)] +#[derive( + Debug, Default, Clone, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct BBox { pub x0: f32, pub y0: f32, @@ -128,7 +131,9 @@ impl BBox { } } -#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[derive( + Debug, Clone, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, +)] pub struct ElementText { pub(crate) text: String, } @@ -146,7 +151,7 @@ impl ElementText { } } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)] #[serde(tag = "element_type")] pub enum ElementType { Header, @@ -166,7 +171,7 @@ impl std::fmt::Display for ElementType { } } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Debug, Deserialize, Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)] pub struct Element { pub id: ElementID, pub layout_block_id: i32, @@ -178,7 +183,7 @@ pub struct Element { impl Element { pub fn from_layout_block(id: usize, layout_block: &LayoutBBox, page_id: usize) -> Self { - let kind = match layout_block.label { + let kind = match layout_block.label.as_str() { "Caption" => ElementType::Caption, "Formula" | "Text" => ElementType::Text, "List-item" => ElementType::ListItem, @@ -190,7 +195,10 @@ impl Element { "Table" => ElementType::Table(None), "Picture" => ElementType::Image, _ => { - unreachable!("can't have other type of layout bbox") + unreachable!( + "can't have other type of layout bbox: {}", + layout_block.label + ) } }; Self { @@ -211,7 +219,7 @@ impl Element { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct StructuredPage { pub id: PageID, pub width: f32, @@ -220,6 +228,10 @@ pub struct StructuredPage { pub need_ocr: bool, pub image: DynamicImage, pub elements: Vec, + pub paths: Vec, + pub native_lines: Vec, + pub layout: Vec, + pub ocr_lines: Vec, } #[derive(Debug, Deserialize, Serialize)] @@ -259,14 +271,60 @@ pub struct ParsedDocument { pub metadata: DocumentMetadata, } -#[derive(Clone, Debug)] +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Deserialize, + Serialize, + Archive, + RkyvDeserialize, + RkyvSerialize, +)] +#[archive(check_bytes)] +pub enum SerializableFontWeight { + Thin, + ExtraLight, + Light, + Normal, + Medium, + SemiBold, + Bold, + ExtraBold, + Black, +} + +impl From for SerializableFontWeight { + fn from(_weight: PdfFontWeight) -> Self { + // TODO: Map correctly once variants are known. + // For now, default to Normal to bypass compilation errors. + Self::Normal + /* + match weight { + PdfFontWeight::Thin => Self::Thin, + PdfFontWeight::ExtraLight => Self::ExtraLight, + PdfFontWeight::Light => Self::Light, + PdfFontWeight::Normal => Self::Normal, + PdfFontWeight::Medium => Self::Medium, + PdfFontWeight::SemiBold => Self::SemiBold, + PdfFontWeight::Bold => Self::Bold, + PdfFontWeight::ExtraBold => Self::ExtraBold, + PdfFontWeight::Black => Self::Black, + } + */ + } +} + +#[derive(Clone, Debug, Archive, RkyvDeserialize, RkyvSerialize)] pub struct CharSpan { pub bbox: BBox, pub text: String, pub rotation: f32, pub font_name: String, pub font_size: f32, - pub font_weight: Option, + pub font_weight: Option, pub char_start_idx: usize, pub char_end_idx: usize, } @@ -281,7 +339,7 @@ impl CharSpan { ), text: char.unicode_char().unwrap_or_default().into(), font_name: char.font_name(), - font_weight: char.font_weight(), + font_weight: char.font_weight().map(Into::into), font_size: char.unscaled_font_size().value, rotation: char.get_rotation_clockwise_degrees(), char_start_idx: char.index(), @@ -290,9 +348,10 @@ impl CharSpan { } pub fn append(&mut self, char: &PdfPageTextChar, page_bbox: &BBox) -> Option<()> { let char_rotation = char.get_rotation_clockwise_degrees(); + let char_font_weight = char.font_weight().map(SerializableFontWeight::from); if char.unscaled_font_size().value != self.font_size || char.font_name() != self.font_name - || char.font_weight() != self.font_weight + || char_font_weight != self.font_weight || char_rotation != self.rotation { None @@ -308,7 +367,7 @@ impl CharSpan { } } } -#[derive(Clone, Default)] +#[derive(Clone, Default, Archive, RkyvDeserialize, RkyvSerialize)] pub struct Line { pub text: String, pub bbox: BBox, @@ -371,13 +430,13 @@ impl Line { } } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize)] pub enum Segment { Line { start: (f32, f32), end: (f32, f32) }, Rect { bbox: BBox }, } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize)] pub struct PDFPath { pub segments: Vec, pub is_stroke: bool, diff --git a/ferrules-core/src/layout/model.rs b/ferrules-core/src/layout/model.rs index 32f5da8..3cd8b09 100644 --- a/ferrules-core/src/layout/model.rs +++ b/ferrules-core/src/layout/model.rs @@ -9,6 +9,7 @@ use ort::{ }, session::{builder::GraphOptimizationLevel, Session}, }; +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use crate::entities::BBox; @@ -81,11 +82,12 @@ lazy_static! { ]; } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Archive, RkyvDeserialize, RkyvSerialize)] +#[archive(check_bytes)] pub struct LayoutBBox { pub id: i32, pub bbox: BBox, - pub label: &'static str, + pub label: String, pub proba: f32, } @@ -324,7 +326,7 @@ impl ORTLayoutParser { y1: y1 * rescale_factor, }, proba, - label, + label: label.to_string(), }); bbox_id += 1; } @@ -438,7 +440,7 @@ mod tests { x1: 3.0, y1: 3.0, }, - label: "A", + label: "A".to_string(), proba: 0.85, }, LayoutBBox { @@ -450,7 +452,7 @@ mod tests { x1: 2.0, y1: 2.0, }, - label: "A", + label: "A".to_string(), proba: 0.95, }, ]; @@ -473,7 +475,7 @@ mod tests { x1: 1.0, y1: 1.0, }, - label: "A", + label: "A".to_string(), proba: 0.9, }, LayoutBBox { @@ -484,7 +486,7 @@ mod tests { x1: 3.0, y1: 3.0, }, - label: "A", + label: "A".to_string(), proba: 0.95, }, LayoutBBox { @@ -495,7 +497,7 @@ mod tests { x1: 5.0, y1: 5.0, }, - label: "A", + label: "A".to_string(), proba: 0.85, }, ]; @@ -517,7 +519,7 @@ mod tests { x1: 2.0, y1: 2.0, }, - label: "A", + label: "A".to_string(), proba: 0.85, }, LayoutBBox { @@ -529,7 +531,7 @@ mod tests { x1: 2.0, y1: 2.0, }, - label: "A", + label: "A".to_string(), proba: 0.95, }, LayoutBBox { @@ -541,7 +543,7 @@ mod tests { x1: 2.0, y1: 2.0, }, - label: "A", + label: "A".to_string(), proba: 0.90, }, ]; diff --git a/ferrules-core/src/lib.rs b/ferrules-core/src/lib.rs index 5ed6c0e..387276e 100644 --- a/ferrules-core/src/lib.rs +++ b/ferrules-core/src/lib.rs @@ -77,10 +77,12 @@ //! //! Licensed under the GPLv3 license. #![feature(portable_simd)] +#![recursion_limit = "256"] pub(crate) mod draw; pub mod blocks; +pub mod debug_info; pub mod entities; pub mod error; pub mod layout; diff --git a/ferrules-core/src/parse/document.rs b/ferrules-core/src/parse/document.rs index 8c0205b..27aa8c1 100644 --- a/ferrules-core/src/parse/document.rs +++ b/ferrules-core/src/parse/document.rs @@ -13,6 +13,7 @@ use super::{ use crate::entities::DocumentMetadata; use crate::error::FerrulesError; use crate::{ + blocks::Block, entities::{ElementType, Page, PageID, ParsedDocument, StructuredPage}, layout::{ model::{ORTConfig, ORTLayoutParser}, @@ -181,18 +182,22 @@ impl FerrulesParser { let title_level = title_levels_kmeans(&titles, 6); let doc_pages = parsed_pages - .into_iter() + .iter() .map(|sp| Page { id: sp.id, width: sp.width, height: sp.height, need_ocr: sp.need_ocr, - image: sp.image, + image: sp.image.clone(), }) .collect(); let blocks = merge_elements_into_blocks(all_elements, title_level)?; + if let Some(ref debug_dir) = debug_dir { + self.save_debug_binary(debug_dir, &doc_name, &parsed_pages, &blocks); + } + let duration = start_time.elapsed(); Ok(ParsedDocument { @@ -204,6 +209,51 @@ impl FerrulesParser { }) } + fn save_debug_binary( + &self, + debug_dir: &std::path::Path, + doc_name: &str, + parsed_pages: &[StructuredPage], + blocks: &[Block], + ) { + let mut debug_pages = Vec::new(); + for sp in parsed_pages { + let mut page_blocks = Vec::new(); + for block in blocks { + if block.pages_id.contains(&sp.id) { + page_blocks.push(block.clone()); + } + } + + let mut image_data = Vec::new(); + let _ = sp.image.write_to( + &mut std::io::Cursor::new(&mut image_data), + image::ImageFormat::Png, + ); + + debug_pages.push(crate::debug_info::DebugPage { + page_number: sp.id, + native_lines: sp.native_lines.clone(), + paths: sp.paths.clone(), + layout_bboxes: sp.layout.clone(), + ocr_lines: sp.ocr_lines.clone(), + elements: sp.elements.clone(), + blocks: page_blocks, + image_data, + width: sp.width, + height: sp.height, + }); + } + let debug_doc = crate::debug_info::DebugDocument { + name: doc_name.to_string(), + pages: debug_pages, + }; + + let debug_file = debug_dir.join(format!("{}.ferr", doc_name)); + let bytes = rkyv::to_bytes::<_, 1024>(&debug_doc).expect("failed to serialize debug doc"); + std::fs::write(debug_file, bytes).expect("failed to write debug file"); + } + #[allow(clippy::too_many_arguments)] #[tracing::instrument(skip_all)] async fn parse_doc_pages( diff --git a/ferrules-core/src/parse/merge.rs b/ferrules-core/src/parse/merge.rs index 7809278..ab2fb36 100644 --- a/ferrules-core/src/parse/merge.rs +++ b/ferrules-core/src/parse/merge.rs @@ -128,7 +128,7 @@ pub(crate) fn merge_lines_layout( let mut footers = Vec::new(); for (line, layout_block) in line_block_iterator { match &layout_block.as_ref() { - Some(&line_layout_block) => match line_layout_block.label { + Some(&line_layout_block) => match line_layout_block.label.as_str() { "Page-header" => { merge_or_create_elements(&mut headers, line, line_layout_block, page_id); } diff --git a/ferrules-core/src/parse/page.rs b/ferrules-core/src/parse/page.rs index 640c6d1..fce8a77 100644 --- a/ferrules-core/src/parse/page.rs +++ b/ferrules-core/src/parse/page.rs @@ -143,12 +143,13 @@ pub async fn parse_page_full( .map_err(|_| FerrulesError::LayoutParsingError)? .map_err(|_| FerrulesError::LayoutParsingError)?; - let (text_lines, need_ocr) = + let native_lines_captured = text_lines.clone(); + let (text_lines_processed, need_ocr) = parse_page_text(text_lines, &page_layout, &page_image, downscale_factor)?; // Merging elements with layout - let mut elements = build_page_elements(&page_layout, &text_lines, page_id)?; - let text_lines_arc = Arc::new(text_lines.clone()); + let mut elements = build_page_elements(&page_layout, &text_lines_processed, page_id)?; + let text_lines_arc = Arc::new(text_lines_processed.clone()); let paths_arc = Arc::new(paths); // Table parsing @@ -182,7 +183,7 @@ pub async fn parse_page_full( &tmp_dir, page_id, &page_image_scale1, - &text_lines, + &text_lines_processed, need_ocr, &page_layout, &elements, @@ -196,7 +197,15 @@ pub async fn parse_page_full( height: page_bbox.height(), image: page_image_scale1, elements, + paths: paths_arc.as_ref().clone(), need_ocr, + native_lines: native_lines_captured, + layout: page_layout, + ocr_lines: if need_ocr { + text_lines_processed.clone() + } else { + vec![] + }, }; span.record( @@ -213,6 +222,7 @@ pub async fn parse_page_full( format!("{:?}", parse_native_metadata.parse_native_duration_ms), ); // TODO: add OCR timings + // TODO: add table parser timings Ok(structured_page) } diff --git a/ferrules-core/src/parse/table.rs b/ferrules-core/src/parse/table.rs index 7538dbd..94c5c85 100644 --- a/ferrules-core/src/parse/table.rs +++ b/ferrules-core/src/parse/table.rs @@ -330,7 +330,7 @@ impl TableTransformer { y1: (cy + h / 2.0).max(0.0).min(orig_height as f32), }, - label: Self::TABLE_LABELS[max_idx], + label: Self::TABLE_LABELS[max_idx].to_string(), proba: max_prob, }); } @@ -696,7 +696,7 @@ impl TableParser { } row_cells.push(crate::blocks::TableCell { - content: vec![], + content_ids: vec![], text: cell_text, row_span: row_span as u8, col_span: col_span as u8, @@ -891,7 +891,7 @@ impl TableParser { bbox: cell_bbox, col_span: col_span as u8, row_span: 1, - content: Vec::new(), + content_ids: Vec::new(), }); col_idx += col_span; @@ -993,7 +993,7 @@ impl TableParser { current_cell_bbox.merge(&line.bbox); } else { cells.push(crate::blocks::TableCell { - content: vec![], + content_ids: vec![], text: current_cell_text.trim().to_string(), bbox: current_cell_bbox, col_span: 1, @@ -1005,7 +1005,7 @@ impl TableParser { } cells.push(crate::blocks::TableCell { - content: vec![], + content_ids: vec![], text: current_cell_text.trim().to_string(), bbox: current_cell_bbox, col_span: 1, diff --git a/ferrules-core/src/render/html.rs b/ferrules-core/src/render/html.rs index 1e2d2ff..e223357 100644 --- a/ferrules-core/src/render/html.rs +++ b/ferrules-core/src/render/html.rs @@ -125,19 +125,7 @@ impl HTMLRenderer { } table_html.push_str(">"); - if !cell.content.is_empty() { - // Render content into a temporary container (div) - let mut cell_container = HtmlElement::new(HtmlTag::Div); - for block in &cell.content { - Self::render_block_to_container( - block, - &mut cell_container, - img_src_path, - list_regex, - )?; - } - table_html.push_str(&cell_container.to_html_string()); - } else if !cell.text.is_empty() { + if !cell.text.is_empty() { table_html.push_str(&cell.text); } From 9214f024cd862c3ef5e7cc8b54e2eeed5924eb46 Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 11:55:56 +0100 Subject: [PATCH 2/8] working app --- Cargo.lock | 3526 +++++++++++++++++++++++++-- Cargo.toml | 2 +- ferrules-core/src/parse/document.rs | 3 +- ferrules-core/src/parse/page.rs | 9 +- ferrules-debug/Cargo.toml | 22 + ferrules-debug/src/main.rs | 221 ++ ferrules-debug/src/painter.rs | 166 ++ 7 files changed, 3743 insertions(+), 206 deletions(-) create mode 100644 ferrules-debug/Cargo.toml create mode 100644 ferrules-debug/src/main.rs create mode 100644 ferrules-debug/src/painter.rs diff --git a/Cargo.lock b/Cargo.lock index c96e3ee..85f2c4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.1", + "once_cell", + "version_check", + "zerocopy 0.8.27", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -68,6 +81,39 @@ dependencies = [ "equator", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.8.0", + "cc", + "cesu8", + "jni 0.21.1", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -171,12 +217,191 @@ dependencies = [ "syn 2.0.96", ] +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.2", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus 5.13.2", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.3", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.3", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.3", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -199,6 +424,12 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.86" @@ -408,15 +639,30 @@ dependencies = [ "which", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.8.0" @@ -459,6 +705,12 @@ dependencies = [ "wyz", ] +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -474,7 +726,29 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.3", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", ] [[package]] @@ -495,6 +769,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + [[package]] name = "bytecheck" version = "0.6.12" @@ -522,6 +802,20 @@ name = "bytemuck" version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] [[package]] name = "byteorder" @@ -551,6 +845,57 @@ dependencies = [ "displaydoc", ] +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.8.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.8.0", + "polling", + "rustix 1.1.3", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.4", + "rustix 1.1.3", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cast" version = "0.3.0" @@ -599,6 +944,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.39" @@ -648,7 +1005,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.6", ] [[package]] @@ -691,6 +1048,55 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "clipboard_macos" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" +dependencies = [ + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" +dependencies = [ + "smithay-clipboard", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd63e33452ffdafd39924c4f05a5dd1e94db646c779c6bd59148a3d95fff5ad4" +dependencies = [ + "thiserror 2.0.12", + "x11rb", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width 0.1.14", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -713,26 +1119,66 @@ dependencies = [ ] [[package]] -name = "combine" -version = "4.6.7" +name = "com" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" dependencies = [ - "bytes", - "memchr", + "com_macros", ] [[package]] -name = "console" -version = "0.15.10" +name = "com_macros" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys 0.59.0", + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] @@ -771,6 +1217,30 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + [[package]] name = "core_maths" version = "0.1.1" @@ -780,6 +1250,29 @@ dependencies = [ "libm", ] +[[package]] +name = "cosmic-text" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" +dependencies = [ + "bitflags 2.8.0", + "fontdb", + "log", + "rangemap", + "rayon", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -875,6 +1368,51 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor-lite" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e162d0c2e2068eb736b71e5597eff0b9944e6b973cd9f37b6a288ab9bf20e300" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "d3d12" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" +dependencies = [ + "bitflags 2.8.0", + "libloading 0.8.6", + "winapi", +] + +[[package]] +name = "dark-light" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" +dependencies = [ + "dconf_rs", + "detect-desktop-environment", + "dirs 4.0.0", + "objc", + "rust-ini", + "web-sys", + "winreg", + "zbus 4.4.0", +] + +[[package]] +name = "dconf_rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" + [[package]] name = "debugid" version = "0.8.0" @@ -894,6 +1432,12 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "detect-desktop-environment" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d8ad60dd5b13a4ee6bd8fa2d5d88965c597c67bce32b5fc49c94f55cb50810" + [[package]] name = "digest" version = "0.10.7" @@ -904,13 +1448,33 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys", + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", ] [[package]] @@ -921,8 +1485,26 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users", - "windows-sys 0.59.0", + "redox_users 0.5.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.8.0", + "block2 0.6.2", + "libc", + "objc2 0.6.3", ] [[package]] @@ -936,6 +1518,73 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading 0.8.6", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "drm" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "libc", + "rustix 0.38.44", +] + +[[package]] +name = "drm-ffi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b" +dependencies = [ + "drm-sys", + "rustix 0.38.44", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + [[package]] name = "either" version = "1.13.0" @@ -1021,6 +1670,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "equator" version = "0.2.2" @@ -1057,6 +1733,52 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.73.0" @@ -1078,11 +1800,17 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ - "bit-set", + "bit-set 0.8.0", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastrand" version = "2.3.0" @@ -1153,23 +1881,23 @@ dependencies = [ "bytecheck", "colored", "criterion", - "dirs", + "dirs 6.0.0", "futures", "half", "html2md", - "image", + "image 0.25.5", "imageproc", "itertools 0.14.0", "kmeans", "lazy_static", "ndarray", - "objc2", - "objc2-foundation", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-vision", "ort", "pdfium-render", "plsfix", - "rand", + "rand 0.8.5", "rayon", "regex", "rkyv", @@ -1182,6 +1910,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "ferrules-debug" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "ferrules-core", + "iced", + "image 0.25.5", + "memmap2", + "rfd", + "rkyv", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "filetime" version = "0.2.25" @@ -1227,6 +1972,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -1234,32 +1985,91 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "foreign-types" -version = "0.3.2" +name = "font-types" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" dependencies = [ - "foreign-types-shared", + "bytemuck", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "fontconfig-parser" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] [[package]] -name = "form_urlencoded" -version = "1.2.1" +name = "fontdb" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" dependencies = [ - "percent-encoding", + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", ] [[package]] -name = "funty" -version = "2.0.0" +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" @@ -1313,6 +2123,7 @@ dependencies = [ "futures-core", "futures-task", "futures-util", + "num_cpus", ] [[package]] @@ -1321,6 +2132,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -1372,6 +2196,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.3", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -1413,12 +2247,112 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "glow" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.8.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.8.0", + "gpu-descriptor-types", + "hashbrown 0.14.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + [[package]] name = "h2" version = "0.4.7" @@ -1431,7 +2365,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.1", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -1454,14 +2388,39 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hassle-rs" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.8.0", + "com", + "libc", + "libloading 0.8.6", + "thiserror 1.0.69", + "widestring", + "winapi", +] [[package]] name = "heck" @@ -1481,12 +2440,24 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "home" version = "0.5.11" @@ -1523,7 +2494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cff9891f2e0d9048927fbdfc28b11bf378f6a93c7ba70b23d0fbee9af6071b4" dependencies = [ "html5ever", - "jni", + "jni 0.19.0", "lazy_static", "markup5ever_rcdom", "percent-encoding", @@ -1683,101 +2654,285 @@ dependencies = [ ] [[package]] -name = "icu" -version = "1.5.0" +name = "iced" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502" +checksum = "88acfabc84ec077eaf9ede3457ffa3a104626d79022a9bf7f296093b1d60c73f" dependencies = [ - "icu_calendar", - "icu_casemap", - "icu_collator", - "icu_collections", - "icu_datetime", - "icu_decimal", - "icu_experimental", - "icu_list", - "icu_locid", - "icu_locid_transform", - "icu_normalizer", - "icu_plurals", - "icu_properties", - "icu_provider", - "icu_segmenter", - "icu_timezone", + "iced_core", + "iced_futures", + "iced_renderer", + "iced_widget", + "iced_winit", + "image 0.24.9", + "thiserror 1.0.69", ] [[package]] -name = "icu_calendar" -version = "1.5.2" +name = "iced_core" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff" +checksum = "0013a238275494641bf8f1732a23a808196540dc67b22ff97099c044ae4c8a1c" dependencies = [ - "calendrical_calculations", - "displaydoc", - "icu_calendar_data", - "icu_locid", - "icu_locid_transform", - "icu_provider", - "tinystr", - "writeable", - "zerovec", + "bitflags 2.8.0", + "bytes", + "dark-light", + "glam", + "log", + "num-traits", + "once_cell", + "palette", + "rustc-hash 2.1.1", + "smol_str", + "thiserror 1.0.69", + "web-time", ] [[package]] -name = "icu_calendar_data" -version = "1.5.0" +name = "iced_futures" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0" +checksum = "0c04a6745ba2e80f32cf01e034fd00d853aa4f4cd8b91888099cb7aaee0d5d7c" +dependencies = [ + "futures", + "iced_core", + "log", + "rustc-hash 2.1.1", + "tokio", + "wasm-bindgen-futures", + "wasm-timer", +] [[package]] -name = "icu_casemap" -version = "1.5.1" +name = "iced_glyphon" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f" +checksum = "41c3bb56f1820ca252bc1d0994ece33d233a55657c0c263ea7cb16895adbde82" dependencies = [ - "displaydoc", - "icu_casemap_data", - "icu_collections", - "icu_locid", - "icu_properties", - "icu_provider", - "writeable", - "zerovec", + "cosmic-text", + "etagere", + "lru", + "rustc-hash 2.1.1", + "wgpu", ] [[package]] -name = "icu_casemap_data" -version = "1.5.0" +name = "iced_graphics" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08" +checksum = "ba25a18cfa6d5cc160aca7e1b34f73ccdff21680fa8702168c09739767b6c66f" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "cosmic-text", + "half", + "iced_core", + "iced_futures", + "image 0.24.9", + "kamadak-exif", + "log", + "lyon_path", + "once_cell", + "raw-window-handle", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "unicode-segmentation", +] [[package]] -name = "icu_collator" -version = "1.5.0" +name = "iced_renderer" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2" +checksum = "73558208059f9e622df2bf434e044ee2f838ce75201a023cf0ca3e1244f46c2a" dependencies = [ - "displaydoc", - "icu_collator_data", - "icu_collections", - "icu_locid_transform", - "icu_normalizer", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "zerovec", + "iced_graphics", + "iced_tiny_skia", + "iced_wgpu", + "log", + "thiserror 1.0.69", ] [[package]] -name = "icu_collator_data" -version = "1.5.0" +name = "iced_runtime" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5" +checksum = "348b5b2c61c934d88ca3b0ed1ed913291e923d086a66fa288ce9669da9ef62b5" +dependencies = [ + "bytes", + "iced_core", + "iced_futures", + "raw-window-handle", + "thiserror 1.0.69", +] [[package]] -name = "icu_collections" +name = "iced_tiny_skia" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c625d368284fcc43b0b36b176f76eff1abebe7959dd58bd8ce6897d641962a50" +dependencies = [ + "bytemuck", + "cosmic-text", + "iced_graphics", + "kurbo", + "log", + "rustc-hash 2.1.1", + "softbuffer", + "tiny-skia", +] + +[[package]] +name = "iced_wgpu" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15708887133671d2bcc6c1d01d1f176f43a64d6cdc3b2bf893396c3ee498295f" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "futures", + "glam", + "guillotiere", + "iced_glyphon", + "iced_graphics", + "log", + "lyon", + "once_cell", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "wgpu", +] + +[[package]] +name = "iced_widget" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81429e1b950b0e4bca65be4c4278fea6678ea782030a411778f26fa9f8983e1d" +dependencies = [ + "iced_renderer", + "iced_runtime", + "num-traits", + "once_cell", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "unicode-segmentation", +] + +[[package]] +name = "iced_winit" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44cd4e1c594b6334f409282937bf972ba14d31fedf03c23aa595d982a2fda28" +dependencies = [ + "iced_futures", + "iced_graphics", + "iced_runtime", + "log", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "winapi", + "window_clipboard", + "winit", +] + +[[package]] +name = "icu" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502" +dependencies = [ + "icu_calendar", + "icu_casemap", + "icu_collator", + "icu_collections", + "icu_datetime", + "icu_decimal", + "icu_experimental", + "icu_list", + "icu_locid", + "icu_locid_transform", + "icu_normalizer", + "icu_plurals", + "icu_properties", + "icu_provider", + "icu_segmenter", + "icu_timezone", +] + +[[package]] +name = "icu_calendar" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff" +dependencies = [ + "calendrical_calculations", + "displaydoc", + "icu_calendar_data", + "icu_locid", + "icu_locid_transform", + "icu_provider", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_calendar_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0" + +[[package]] +name = "icu_casemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f" +dependencies = [ + "displaydoc", + "icu_casemap_data", + "icu_collections", + "icu_locid", + "icu_properties", + "icu_provider", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_casemap_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08" + +[[package]] +name = "icu_collator" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2" +dependencies = [ + "displaydoc", + "icu_collator_data", + "icu_collections", + "icu_locid_transform", + "icu_normalizer", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_collator_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5" + +[[package]] +name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" @@ -2096,6 +3251,24 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "image" version = "0.25.5" @@ -2138,11 +3311,11 @@ dependencies = [ "ab_glyph", "approx", "getrandom 0.2.15", - "image", + "image 0.25.5", "itertools 0.12.1", "nalgebra", "num", - "rand", + "rand 0.8.5", "rand_distr", "rayon", ] @@ -2165,12 +3338,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.16.1", ] [[package]] @@ -2182,10 +3355,19 @@ dependencies = [ "console", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", "web-time", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "interpolate_name" version = "0.2.4" @@ -2267,6 +3449,22 @@ dependencies = [ "walkdir", ] +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -2287,6 +3485,9 @@ 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" @@ -2297,6 +3498,32 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading 0.8.6", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "kmeans" version = "1.1.0" @@ -2306,10 +3533,20 @@ dependencies = [ "aligned-vec 0.6.1", "num", "num-traits", - "rand", + "rand 0.8.5", "rayon", ] +[[package]] +name = "kurbo" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2330,9 +3567,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libfuzzer-sys" @@ -2344,6 +3581,16 @@ dependencies = [ "cc", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.8.6" @@ -2378,7 +3625,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.8.0", "libc", - "redox_syscall", + "redox_syscall 0.5.8", ] [[package]] @@ -2387,6 +3634,18 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + [[package]] name = "litemap" version = "0.7.4" @@ -2419,55 +3678,122 @@ dependencies = [ ] [[package]] -name = "mac" -version = "0.1.1" +name = "lru" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" [[package]] -name = "markup5ever" -version = "0.12.1" +name = "lyon" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +checksum = "dbcb7d54d54c8937364c9d41902d066656817dce1e03a44e5533afebd1ef4352" dependencies = [ - "log", - "phf", - "phf_codegen", - "string_cache", - "string_cache_codegen", - "tendril", + "lyon_algorithms", + "lyon_tessellation", ] [[package]] -name = "markup5ever_rcdom" -version = "0.3.0" +name = "lyon_algorithms" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +checksum = "f4c0829e28c4f336396f250d850c3987e16ce6db057ffe047ce0dd54aab6b647" dependencies = [ - "html5ever", - "markup5ever", - "tendril", - "xml5ever", + "lyon_path", + "num-traits", ] [[package]] -name = "matchers" -version = "0.1.0" +name = "lyon_geom" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "e260b6de923e6e47adfedf6243013a7a874684165a6a277594ee3906021b2343" dependencies = [ - "regex-automata 0.1.10", + "arrayvec", + "euclid", + "num-traits", ] [[package]] -name = "matchit" -version = "0.7.3" +name = "lyon_path" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "1aeca86bcfd632a15984ba029b539ffb811e0a70bf55e814ef8b0f54f506fdeb" +dependencies = [ + "lyon_geom", + "num-traits", +] [[package]] -name = "matchit" -version = "0.8.4" +name = "lyon_tessellation" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f586142e1280335b1bc89539f7c97dd80f08fc43e9ab1b74ef0a42b04aa353" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" @@ -2512,6 +3838,30 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.8.0", + "block", + "core-graphics-types", + "foreign-types 0.5.0", + "log", + "objc", + "paste", +] + [[package]] name = "mimalloc" version = "0.1.43" @@ -2571,6 +3921,32 @@ dependencies = [ "version_check", ] +[[package]] +name = "mutate_once" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" + +[[package]] +name = "naga" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +dependencies = [ + "bit-set 0.5.3", + "bitflags 2.8.0", + "codespan-reporting", + "hexf-parse", + "indexmap 2.13.0", + "log", + "num-traits", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + [[package]] name = "nalgebra" version = "0.32.6" @@ -2618,12 +3994,64 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.8.0", + "jni-sys", + "log", + "ndk-sys 0.6.0+11769913", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.3" @@ -2751,12 +4179,44 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "number_prefix" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -2773,18 +4233,127 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.8.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.8.0", + "block2 0.6.2", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.8.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.8.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.8.0", + "dispatch2", + "objc2 0.6.3", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.8.0", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", +] + [[package]] name = "objc2-core-image" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2", - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + [[package]] name = "objc2-core-ml" version = "0.2.2" @@ -2792,9 +4361,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8276992c434383dce6a12102a20c35337932f185d90e4e7f07f703a0a748689" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", "objc2-metal", ] @@ -2811,9 +4380,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.8.0", - "block2", + "block2 0.5.1", + "dispatch", "libc", - "objc2", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.8.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.8.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", ] [[package]] @@ -2823,9 +4427,89 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.8.0", - "block2", - "objc2", - "objc2-foundation", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.8.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.8.0", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.8.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core 0.2.2", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.8.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", ] [[package]] @@ -2834,11 +4518,20 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cb865e1df4a364c6f2ff009079d7e1c22973d11917871886b43efe3494cf6c2" dependencies = [ - "block2", - "objc2", + "block2 0.5.1", + "objc2 0.5.2", "objc2-core-image", "objc2-core-ml", - "objc2-foundation", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", ] [[package]] @@ -2882,7 +4575,7 @@ checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags 2.8.0", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "libc", "once_cell", "openssl-macros", @@ -2982,7 +4675,7 @@ dependencies = [ "glob", "opentelemetry", "percent-encoding", - "rand", + "rand 0.8.5", "serde_json", "thiserror 1.0.69", "tokio", @@ -2996,6 +4689,36 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "orbclient" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "ort" version = "2.0.0-rc.9" @@ -3003,7 +4726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52afb44b6b0cffa9bf45e4d37e5a4935b0334a51570658e279e9e3e6cf324aa5" dependencies = [ "half", - "libloading", + "libloading 0.8.6", "ndarray", "ort-sys", "sha2", @@ -3047,7 +4770,48 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" dependencies = [ - "ttf-parser", + "ttf-parser 0.25.1", +] + +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "palette_derive", + "phf", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", ] [[package]] @@ -3057,7 +4821,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -3068,7 +4846,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -3092,10 +4870,10 @@ dependencies = [ "chrono", "console_error_panic_hook", "console_log", - "image", + "image 0.25.5", "itertools 0.14.0", "js-sys", - "libloading", + "libloading 0.8.6", "log", "maybe-owned", "once_cell", @@ -3118,6 +4896,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ + "phf_macros", "phf_shared", ] @@ -3138,7 +4917,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.96", ] [[package]] @@ -3182,6 +4974,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "piston-float" version = "1.0.1" @@ -3253,6 +5056,26 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "portable-atomic" version = "1.10.0" @@ -3280,7 +5103,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -3289,6 +5112,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "prettyplease" version = "0.2.29" @@ -3299,6 +5128,15 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.10+spec-1.0.0", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -3385,6 +5223,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.38" @@ -3407,8 +5254,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -3418,7 +5275,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -3430,6 +5297,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.1", +] + [[package]] name = "rand_distr" version = "0.4.3" @@ -3437,9 +5313,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand", + "rand 0.8.5", ] +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + [[package]] name = "rav1e" version = "0.7.1" @@ -3466,8 +5354,8 @@ dependencies = [ "once_cell", "paste", "profiling", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "simd_helpers", "system-deps", "thiserror 1.0.69", @@ -3490,6 +5378,12 @@ dependencies = [ "rgb", ] +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + [[package]] name = "rawpointer" version = "0.2.1" @@ -3516,6 +5410,34 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.8" @@ -3525,6 +5447,17 @@ dependencies = [ "bitflags 2.8.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", +] + [[package]] name = "redox_users" version = "0.5.0" @@ -3598,6 +5531,12 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] name = "reqwest" version = "0.12.12" @@ -3639,6 +5578,30 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.2", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "pollster", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rgb" version = "0.8.50" @@ -3689,6 +5652,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -3725,10 +5704,23 @@ dependencies = [ "bitflags 2.8.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.8.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.22" @@ -3776,6 +5768,23 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.19" @@ -3809,12 +5818,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.19.2", + "tiny-skia", +] + [[package]] name = "seahash" version = "4.1.0" @@ -3844,6 +5872,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + [[package]] name = "semver" version = "1.0.25" @@ -3903,7 +5937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653942e6141f16651273159f4b8b1eaeedf37a7554c00cd798953e64b8a9bf72" dependencies = [ "once_cell", - "rand", + "rand 0.8.5", "sentry-types", "serde", "serde_json", @@ -3960,7 +5994,7 @@ checksum = "2d4203359e60724aa05cf2385aaf5d4f147e837185d7dd2b9ccf1ee77f4420c8" dependencies = [ "debugid", "hex", - "rand", + "rand 0.8.5", "serde", "serde_json", "thiserror 1.0.69", @@ -3971,18 +6005,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -4020,6 +6064,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -4041,6 +6096,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -4116,6 +6182,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "skrifa" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.9" @@ -4125,12 +6201,93 @@ dependencies = [ "autocfg", ] +[[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.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.8.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.8.0", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.3", + "thiserror 2.0.12", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + [[package]] name = "socket2" version = "0.5.8" @@ -4152,18 +6309,71 @@ dependencies = [ "winapi", ] +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "drm", + "fastrand", + "js-sys", + "memmap2", + "ndk", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "raw-window-handle", + "redox_syscall 0.5.8", + "rustix 1.1.3", + "tiny-xlib", + "tracing", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.61.2", + "x11rb", +] + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "string_cache" version = "0.8.8" @@ -4171,7 +6381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" dependencies = [ "new_debug_unreachable", - "parking_lot", + "parking_lot 0.12.3", "phf_shared", "precomputed-hash", "serde", @@ -4201,6 +6411,23 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "swash" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + [[package]] name = "syn" version = "1.0.109" @@ -4238,9 +6465,18 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.96", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", ] [[package]] @@ -4289,7 +6525,7 @@ dependencies = [ "fastrand", "getrandom 0.3.1", "once_cell", - "rustix", + "rustix 0.38.44", "windows-sys 0.59.0", ] @@ -4304,6 +6540,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -4396,6 +6641,45 @@ 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", + "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 = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading 0.8.6", + "pkg-config", + "tracing", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -4442,7 +6726,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4503,8 +6787,8 @@ checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.8", + "toml_edit 0.22.23", ] [[package]] @@ -4516,16 +6800,46 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.13.0", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.8", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +dependencies = [ "winnow", ] @@ -4570,7 +6884,7 @@ dependencies = [ "indexmap 1.9.3", "pin-project", "pin-project-lite", - "rand", + "rand 0.8.5", "slab", "tokio", "tokio-util", @@ -4735,6 +7049,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -4747,6 +7073,17 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + [[package]] name = "uname" version = "0.1.1" @@ -4756,12 +7093,36 @@ dependencies = [ "libc", ] +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + [[package]] name = "unicode-ident" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.24" @@ -4771,12 +7132,42 @@ 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-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -4812,6 +7203,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -5006,6 +7403,156 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.3", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" +dependencies = [ + "bitflags 2.8.0", + "rustix 1.1.3", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.8.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078" +dependencies = [ + "rustix 1.1.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791c58fdeec5406aa37169dd815327d1e47f334219b523444bc26d70ceb4c34e" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" +dependencies = [ + "bitflags 2.8.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + [[package]] name = "web-sys" version = "0.3.72" @@ -5041,6 +7588,113 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "js-sys", + "log", + "naga", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +dependencies = [ + "arrayvec", + "bit-vec 0.6.3", + "bitflags 2.8.0", + "cfg_aliases 0.1.1", + "codespan-reporting", + "indexmap 2.13.0", + "log", + "naga", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "web-sys", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.5.3", + "bitflags 2.8.0", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types", + "d3d12", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.6", + "log", + "metal", + "naga", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot 0.12.3", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.8.0", + "js-sys", + "web-sys", +] + [[package]] name = "which" version = "4.4.2" @@ -5050,7 +7704,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.44", ] [[package]] @@ -5063,6 +7717,12 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -5092,7 +7752,21 @@ dependencies = [ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window_clipboard" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d692d46038c433f9daee7ad8757e002a4248c20b0a3fbc991d99521d3bcb6d" +dependencies = [ + "clipboard-win", + "clipboard_macos", + "clipboard_wayland", + "clipboard_x11", + "raw-window-handle", + "thiserror 1.0.69", +] [[package]] name = "windows" @@ -5113,6 +7787,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-registry" version = "0.2.0" @@ -5143,6 +7823,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -5170,6 +7859,30 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5201,6 +7914,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5213,6 +7932,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5225,6 +7950,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5243,6 +7974,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5255,6 +7992,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5267,6 +8010,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5279,6 +8028,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5291,15 +8046,76 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winit" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +dependencies = [ + "ahash 0.8.12", + "android-activity", + "atomic-waker", + "bitflags 2.8.0", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + [[package]] name = "winnow" -version = "0.7.1" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" @@ -5333,6 +8149,38 @@ dependencies = [ "tap", ] +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading 0.8.6", + "once_cell", + "rustix 1.1.3", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + [[package]] name = "xattr" version = "1.4.0" @@ -5340,10 +8188,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", - "linux-raw-sys", - "rustix", + "linux-raw-sys 0.4.15", + "rustix 0.38.44", +] + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.8.0", + "dlib", + "log", + "once_cell", + "xkeysym", ] +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + [[package]] name = "xml5ever" version = "0.18.1" @@ -5355,6 +8244,12 @@ dependencies = [ "markup5ever", ] +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + [[package]] name = "yoke" version = "0.7.5" @@ -5379,6 +8274,135 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.3", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros 5.13.2", + "zbus_names 4.3.1", + "zvariant 5.9.2", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", + "zbus_names 4.3.1", + "zvariant 5.9.2", + "zvariant_utils 3.3.0", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +dependencies = [ + "serde", + "winnow", + "zvariant 5.9.2", +] + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + [[package]] name = "zerocopy" version = "0.7.35" @@ -5386,7 +8410,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive 0.8.27", ] [[package]] @@ -5400,6 +8433,17 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "zerofrom" version = "0.1.5" @@ -5483,3 +8527,81 @@ checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow", + "zvariant_derive 5.9.2", + "zvariant_utils 3.3.0", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.96", + "zvariant_utils 3.3.0", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.96", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 719146d..079e71d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ferrules-core", "ferrules-cli", "ferrules-api"] +members = ["ferrules-core", "ferrules-cli", "ferrules-api", "ferrules-debug"] resolver = "2" edition = "2021" diff --git a/ferrules-core/src/parse/document.rs b/ferrules-core/src/parse/document.rs index 27aa8c1..5faeb69 100644 --- a/ferrules-core/src/parse/document.rs +++ b/ferrules-core/src/parse/document.rs @@ -1,4 +1,5 @@ -use std::{path::PathBuf, sync::Arc, time::Instant}; +use std::path::PathBuf; +use std::{sync::Arc, time::Instant}; use std::ops::Range; diff --git a/ferrules-core/src/parse/page.rs b/ferrules-core/src/parse/page.rs index fce8a77..6be4c69 100644 --- a/ferrules-core/src/parse/page.rs +++ b/ferrules-core/src/parse/page.rs @@ -237,8 +237,13 @@ fn debug_page( elements: &[Element], paths: &[PDFPath], ) -> Result<(), FerrulesError> { - let output_file = tmp_dir.join(format!("page_{}.png", page_idx)); - let final_output_file = tmp_dir.join(format!("page_blocks_{}.png", page_idx)); + let images_dir = tmp_dir.join("images"); + let blocks_dir = tmp_dir.join("blocks"); + let _ = std::fs::create_dir_all(&images_dir); + let _ = std::fs::create_dir_all(&blocks_dir); + + let output_file = images_dir.join(format!("page_{}.png", page_idx)); + let final_output_file = blocks_dir.join(format!("page_blocks_{}.png", page_idx)); let out_img = draw_text_lines(text_lines, page_image, need_ocr).map_err(|_| { FerrulesError::DebugPageError { tmp_dir: tmp_dir.to_path_buf(), diff --git a/ferrules-debug/Cargo.toml b/ferrules-debug/Cargo.toml new file mode 100644 index 0000000..e4df18d --- /dev/null +++ b/ferrules-debug/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ferrules-debug" +version = "0.1.0" +edition = "2021" + +[dependencies] +iced = { version = "0.13.1", features = [ + "image", + "tokio", + "canvas", + "advanced", +] } +ferrules-core = { path = "../ferrules-core" } +rkyv = { workspace = true } +anyhow = { workspace = true } +tokio = { workspace = true } +image = "0.25.5" +memmap2 = "0.9.5" +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +clap = { workspace = true } +rfd = "0.15" diff --git a/ferrules-debug/src/main.rs b/ferrules-debug/src/main.rs new file mode 100644 index 0000000..caef8ea --- /dev/null +++ b/ferrules-debug/src/main.rs @@ -0,0 +1,221 @@ +use clap::Parser; +use ferrules_core::debug_info::DebugDocument; +use iced::widget::{ + button, canvas, checkbox, column, container, image, row, scrollable, slider, stack, text, +}; +use iced::{event, window, Alignment, Color, Element, Event, Length, Task, Theme}; +use memmap2::Mmap; +use rkyv::archived_root; +use std::path::PathBuf; + +mod painter; +use painter::PagePainter; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Path to the .ferr debug file + #[arg(short, long)] + file: Option, +} + +pub fn main() -> iced::Result { + tracing_subscriber::fmt::init(); + + let args = Args::parse(); + + iced::application("Ferrules Debug", FerrulesDebug::update, FerrulesDebug::view) + .theme(|_| Theme::Dark) + .subscription(FerrulesDebug::subscription) + .run_with(move || FerrulesDebug::new(args.file)) +} + +struct FerrulesDebug { + mmap: Option, + current_page_idx: usize, + + // Layer visibility + show_native: bool, + show_layout: bool, + show_ocr: bool, + show_elements: bool, + show_blocks: bool, + show_paths: bool, +} + +#[derive(Debug, Clone)] +enum Message { + PageChanged(u32), + ToggleLayer(Layer), + OpenFile, + FileSelected(Option), + FileDropped(PathBuf), +} + +#[derive(Debug, Clone)] +enum Layer { + Native, + Layout, + OCR, + Elements, + Blocks, + Paths, +} + +impl FerrulesDebug { + fn new(file_path: Option) -> (Self, Task) { + let debug = Self { + mmap: None, + current_page_idx: 0, + show_native: true, + show_layout: true, + show_ocr: true, + show_elements: true, + show_blocks: true, + show_paths: true, + }; + + let task = if let Some(path) = file_path { + Task::done(Message::FileSelected(Some(path))) + } else { + Task::none() + }; + + (debug, task) + } + + fn update(&mut self, message: Message) -> Task { + match message { + Message::PageChanged(idx) => { + self.current_page_idx = idx as usize; + Task::none() + } + Message::ToggleLayer(layer) => { + match layer { + Layer::Native => self.show_native = !self.show_native, + Layer::Layout => self.show_layout = !self.show_layout, + Layer::OCR => self.show_ocr = !self.show_ocr, + Layer::Elements => self.show_elements = !self.show_elements, + Layer::Blocks => self.show_blocks = !self.show_blocks, + Layer::Paths => self.show_paths = !self.show_paths, + } + Task::none() + } + Message::OpenFile => Task::perform( + async { + rfd::AsyncFileDialog::new() + .add_filter("Ferrules", &["ferr"]) + .pick_file() + .await + }, + |file| Message::FileSelected(file.map(|f| f.path().to_path_buf())), + ), + Message::FileSelected(Some(path)) | Message::FileDropped(path) => { + if let Ok(file) = std::fs::File::open(&path) { + if let Ok(m) = unsafe { Mmap::map(&file) } { + self.mmap = Some(m); + self.current_page_idx = 0; + } + } + Task::none() + } + Message::FileSelected(None) => Task::none(), + } + } + + fn subscription(&self) -> iced::Subscription { + event::listen_with(|event, _status, _window| match event { + Event::Window(window::Event::FileDropped(path)) => Some(Message::FileDropped(path)), + _ => None, + }) + } + + fn view(&self) -> Element<'_, Message> { + if let Some(mmap) = &self.mmap { + let archived = unsafe { archived_root::(&mmap[..]) }; + let total_pages = archived.pages.len() as u32; + let safe_page_idx = (self.current_page_idx as u32).min(total_pages.saturating_sub(1)); + let current_page = &archived.pages[safe_page_idx as usize]; + + let sidebar = column![ + text(archived.name.as_str()).size(20), + button("Open File").on_press(Message::OpenFile).padding(10), + text(format!("Page {} / {}", safe_page_idx + 1, total_pages)), + slider( + 0..=(total_pages.saturating_sub(1)), + safe_page_idx, + Message::PageChanged + ), + column![ + text("Layers"), + checkbox("Native Lines", self.show_native) + .on_toggle(|_| Message::ToggleLayer(Layer::Native)), + checkbox("Paths", self.show_paths) + .on_toggle(|_| Message::ToggleLayer(Layer::Paths)), + checkbox("Layout BBoxes", self.show_layout) + .on_toggle(|_| Message::ToggleLayer(Layer::Layout)), + checkbox("OCR Lines", self.show_ocr) + .on_toggle(|_| Message::ToggleLayer(Layer::OCR)), + checkbox("Elements", self.show_elements) + .on_toggle(|_| Message::ToggleLayer(Layer::Elements)), + checkbox("Blocks", self.show_blocks) + .on_toggle(|_| Message::ToggleLayer(Layer::Blocks)), + ] + .spacing(10) + .padding(10), + text("Metadata"), + scrollable(text(format!("Elements: {}", current_page.elements.len()))) + ] + .width(Length::Fixed(250.0)) + .spacing(20) + .padding(20); + + // Page Viewer (Center) + let image_vec: Vec = (¤t_page.image_data[..]).to_vec(); + let page_image = image::Handle::from_bytes(image_vec); + + let painter = PagePainter { + page: current_page, + show_native: self.show_native, + show_layout: self.show_layout, + show_ocr: self.show_ocr, + show_elements: self.show_elements, + show_blocks: self.show_blocks, + show_paths: self.show_paths, + }; + + let viewer = container(stack![ + image(page_image), + canvas(painter).width(Length::Fill).height(Length::Fill) + ]) + .width(Length::Fill) + .height(Length::Fill) + .center_x(Length::Fill) + .center_y(Length::Fill) + .style(|_theme| container::Style { + background: Some(iced::Background::Color(Color::BLACK)), + ..Default::default() + }); + + row![sidebar, viewer].into() + } else { + container( + column![ + text("Ferrules Debugger").size(40), + text("Visualize the PDF parsing pipeline").size(20), + button("Select .ferr File") + .on_press(Message::OpenFile) + .padding([15, 30]), + text("Or drag and drop a file anywhere"), + ] + .align_x(Alignment::Center) + .spacing(30), + ) + .width(Length::Fill) + .height(Length::Fill) + .center_x(Length::Fill) + .center_y(Length::Fill) + .into() + } + } +} diff --git a/ferrules-debug/src/painter.rs b/ferrules-debug/src/painter.rs new file mode 100644 index 0000000..1bbbde3 --- /dev/null +++ b/ferrules-debug/src/painter.rs @@ -0,0 +1,166 @@ +use ferrules_core::debug_info::ArchivedDebugPage; +use ferrules_core::entities::ArchivedSegment; +use iced::widget::canvas::{Frame, Geometry, Path, Program, Stroke}; +use iced::{Color, Point, Rectangle, Renderer, Theme}; + +pub struct PagePainter<'a> { + pub page: &'a ArchivedDebugPage, + pub show_native: bool, + pub show_layout: bool, + pub show_ocr: bool, + pub show_elements: bool, + pub show_blocks: bool, + pub show_paths: bool, +} + +impl<'a, Message> Program for PagePainter<'a> { + type State = (); + + fn draw( + &self, + _state: &Self::State, + renderer: &Renderer, + _theme: &Theme, + bounds: Rectangle, + _cursor: iced::mouse::Cursor, + ) -> Vec { + let mut frame = Frame::new(renderer, bounds.size()); + + // Scale factor to fit the page into the bounds + let scale_x = bounds.width / self.page.width; + let scale_y = bounds.height / self.page.height; + let scale = scale_x.min(scale_y); + + let offset_x = (bounds.width - self.page.width * scale) / 2.0; + let offset_y = (bounds.height - self.page.height * scale) / 2.0; + + let to_screen = + |x: f32, y: f32| -> Point { Point::new(x * scale + offset_x, y * scale + offset_y) }; + + // Draw Paths first (Background) + if self.show_paths { + for path in self.page.paths.as_slice() { + for segment in path.segments.as_slice() { + match segment { + ArchivedSegment::Line { start, end } => { + let p1 = to_screen(start.0, start.1); + let p2 = to_screen(end.0, end.1); + frame.stroke( + &Path::line(p1, p2), + Stroke::default() + .with_color(Color::from_rgb(0.5, 0.5, 0.5)) + .with_width(0.5), + ); + } + ArchivedSegment::Rect { bbox } => { + let rect = Rectangle { + x: bbox.x0 * scale + offset_x, + y: bbox.y0 * scale + offset_y, + width: (bbox.x1 - bbox.x0) * scale, + height: (bbox.y1 - bbox.y0) * scale, + }; + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default() + .with_color(Color::from_rgb(0.5, 0.5, 0.5)) + .with_width(0.5), + ); + } + } + } + } + } + + // Draw Native Lines + if self.show_native { + for line in self.page.native_lines.as_slice() { + let rect = Rectangle { + x: line.bbox.x0 * scale + offset_x, + y: line.bbox.y0 * scale + offset_y, + width: (line.bbox.x1 - line.bbox.x0) * scale, + height: (line.bbox.y1 - line.bbox.y0) * scale, + }; + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default() + .with_color(Color::from_rgb(1.0, 0.0, 0.0)) + .with_width(1.0), + ); + } + } + + // Draw OCR Lines + if self.show_ocr { + for line in self.page.ocr_lines.as_slice() { + let rect = Rectangle { + x: line.bbox.x0 * scale + offset_x, + y: line.bbox.y0 * scale + offset_y, + width: (line.bbox.x1 - line.bbox.x0) * scale, + height: (line.bbox.y1 - line.bbox.y0) * scale, + }; + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default() + .with_color(Color::from_rgb(0.0, 0.0, 1.0)) // Blue for OCR + .with_width(1.0), + ); + } + } + + // Draw Layout BBoxes + if self.show_layout { + for bbox in self.page.layout_bboxes.as_slice() { + let rect = Rectangle { + x: bbox.bbox.x0 * scale + offset_x, + y: bbox.bbox.y0 * scale + offset_y, + width: (bbox.bbox.x1 - bbox.bbox.x0) * scale, + height: (bbox.bbox.y1 - bbox.bbox.y0) * scale, + }; + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default() + .with_color(Color::from_rgb(0.0, 1.0, 0.0)) + .with_width(1.5), + ); + } + } + + // Draw Elements + if self.show_elements { + for element in self.page.elements.as_slice() { + let rect = Rectangle { + x: element.bbox.x0 * scale + offset_x, + y: element.bbox.y0 * scale + offset_y, + width: (element.bbox.x1 - element.bbox.x0) * scale, + height: (element.bbox.y1 - element.bbox.y0) * scale, + }; + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default() + .with_color(Color::from_rgb(1.0, 1.0, 0.0)) + .with_width(2.0), + ); + } + } + + // Draw Blocks + if self.show_blocks { + for block in self.page.blocks.as_slice() { + let rect = Rectangle { + x: block.bbox.x0 * scale + offset_x, + y: block.bbox.y0 * scale + offset_y, + width: (block.bbox.x1 - block.bbox.x0) * scale, + height: (block.bbox.y1 - block.bbox.y0) * scale, + }; + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default() + .with_color(Color::from_rgb(1.0, 0.0, 1.0)) + .with_width(2.5), + ); + } + } + + vec![frame.into_geometry()] + } +} From 6e9b86884a5833eaed08b488bdd5bb09ccb62eb4 Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 12:44:29 +0100 Subject: [PATCH 3/8] working lines --- Cargo.lock | 36 ++- ferrules-core/src/entities.rs | 2 +- ferrules-debug/Cargo.toml | 1 + ferrules-debug/src/main.rs | 342 +++++++++++++++++----- ferrules-debug/src/painter.rs | 526 ++++++++++++++++++++++++++-------- 5 files changed, 701 insertions(+), 206 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85f2c4f..4c505fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1918,6 +1918,7 @@ dependencies = [ "clap", "ferrules-core", "iced", + "iced_core 0.14.0", "image 0.25.5", "memmap2", "rfd", @@ -2659,7 +2660,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88acfabc84ec077eaf9ede3457ffa3a104626d79022a9bf7f296093b1d60c73f" dependencies = [ - "iced_core", + "iced_core 0.13.2", "iced_futures", "iced_renderer", "iced_widget", @@ -2688,6 +2689,24 @@ dependencies = [ "web-time", ] +[[package]] +name = "iced_core" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ab1937d699403e7e69252ae743a902bcee9f4ab2052cc4c9a46fcf34729d85" +dependencies = [ + "bitflags 2.8.0", + "bytes", + "glam", + "lilt", + "log", + "num-traits", + "rustc-hash 2.1.1", + "smol_str", + "thiserror 2.0.12", + "web-time", +] + [[package]] name = "iced_futures" version = "0.13.2" @@ -2695,7 +2714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c04a6745ba2e80f32cf01e034fd00d853aa4f4cd8b91888099cb7aaee0d5d7c" dependencies = [ "futures", - "iced_core", + "iced_core 0.13.2", "log", "rustc-hash 2.1.1", "tokio", @@ -2726,7 +2745,7 @@ dependencies = [ "bytemuck", "cosmic-text", "half", - "iced_core", + "iced_core 0.13.2", "iced_futures", "image 0.24.9", "kamadak-exif", @@ -2759,7 +2778,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348b5b2c61c934d88ca3b0ed1ed913291e923d086a66fa288ce9669da9ef62b5" dependencies = [ "bytes", - "iced_core", + "iced_core 0.13.2", "iced_futures", "raw-window-handle", "thiserror 1.0.69", @@ -3628,6 +3647,15 @@ dependencies = [ "redox_syscall 0.5.8", ] +[[package]] +name = "lilt" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67562e5eff6b20553fa9be1c503356768420994e28f67e3eafe6f41910e57ad" +dependencies = [ + "web-time", +] + [[package]] name = "linux-raw-sys" version = "0.4.15" diff --git a/ferrules-core/src/entities.rs b/ferrules-core/src/entities.rs index 6d71aee..5783a88 100644 --- a/ferrules-core/src/entities.rs +++ b/ferrules-core/src/entities.rs @@ -135,7 +135,7 @@ impl BBox { Debug, Clone, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, )] pub struct ElementText { - pub(crate) text: String, + pub text: String, } impl ElementText { diff --git a/ferrules-debug/Cargo.toml b/ferrules-debug/Cargo.toml index e4df18d..2d333b4 100644 --- a/ferrules-debug/Cargo.toml +++ b/ferrules-debug/Cargo.toml @@ -20,3 +20,4 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true } clap = { workspace = true } rfd = "0.15" +iced_core = "0.14.0" diff --git a/ferrules-debug/src/main.rs b/ferrules-debug/src/main.rs index caef8ea..b4a667e 100644 --- a/ferrules-debug/src/main.rs +++ b/ferrules-debug/src/main.rs @@ -1,15 +1,15 @@ use clap::Parser; use ferrules_core::debug_info::DebugDocument; use iced::widget::{ - button, canvas, checkbox, column, container, image, row, scrollable, slider, stack, text, + button, canvas, checkbox, column, container, image, row, scrollable, slider, text, }; -use iced::{event, window, Alignment, Color, Element, Event, Length, Task, Theme}; +use iced::{event, window, Alignment, Color, Element, Event, Length, Task, Theme, Vector}; use memmap2::Mmap; use rkyv::archived_root; use std::path::PathBuf; mod painter; -use painter::PagePainter; +use painter::{CanvasMessage, HoverDetailed, PagePainter, PainterMode}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -21,9 +21,7 @@ struct Args { pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); - let args = Args::parse(); - iced::application("Ferrules Debug", FerrulesDebug::update, FerrulesDebug::view) .theme(|_| Theme::Dark) .subscription(FerrulesDebug::subscription) @@ -33,14 +31,18 @@ pub fn main() -> iced::Result { struct FerrulesDebug { mmap: Option, current_page_idx: usize, + cached_page_image_handle: Option, + last_page_idx_cached: Option, - // Layer visibility + zoom: f32, + offset: Vector, show_native: bool, show_layout: bool, show_ocr: bool, show_elements: bool, show_blocks: bool, show_paths: bool, + hovered_info: Option, } #[derive(Debug, Clone)] @@ -50,6 +52,9 @@ enum Message { OpenFile, FileSelected(Option), FileDropped(PathBuf), + CanvasEvent(CanvasMessage), + ZoomSliderChanged(f32), + ResetView, } #[derive(Debug, Clone)] @@ -64,34 +69,40 @@ enum Layer { impl FerrulesDebug { fn new(file_path: Option) -> (Self, Task) { - let debug = Self { - mmap: None, - current_page_idx: 0, - show_native: true, - show_layout: true, - show_ocr: true, - show_elements: true, - show_blocks: true, - show_paths: true, - }; - - let task = if let Some(path) = file_path { - Task::done(Message::FileSelected(Some(path))) - } else { - Task::none() - }; - - (debug, task) + ( + Self { + mmap: None, + current_page_idx: 0, + cached_page_image_handle: None, + last_page_idx_cached: None, + zoom: 1.0, + offset: Vector::new(0.0, 0.0), + show_native: true, + show_layout: true, + show_ocr: true, + show_elements: true, + show_blocks: true, + show_paths: true, + hovered_info: None, + }, + if let Some(path) = file_path { + Task::done(Message::FileSelected(Some(path))) + } else { + Task::none() + }, + ) } fn update(&mut self, message: Message) -> Task { match message { Message::PageChanged(idx) => { self.current_page_idx = idx as usize; + self.zoom = 1.0; + self.offset = Vector::new(0.0, 0.0); Task::none() } - Message::ToggleLayer(layer) => { - match layer { + Message::ToggleLayer(l) => { + match l { Layer::Native => self.show_native = !self.show_native, Layer::Layout => self.show_layout = !self.show_layout, Layer::OCR => self.show_ocr = !self.show_ocr, @@ -115,11 +126,42 @@ impl FerrulesDebug { if let Ok(m) = unsafe { Mmap::map(&file) } { self.mmap = Some(m); self.current_page_idx = 0; + self.cached_page_image_handle = None; + self.last_page_idx_cached = None; + self.zoom = 1.0; + self.offset = Vector::new(0.0, 0.0); } } Task::none() } Message::FileSelected(None) => Task::none(), + Message::CanvasEvent(ev) => match ev { + CanvasMessage::Hovered(info) => { + self.hovered_info = info; + Task::none() + } + CanvasMessage::ZoomChanged(factor, pos) => { + let prev_zoom = self.zoom; + self.zoom = (self.zoom + factor).max(0.1).min(10.0); + let ratio = self.zoom / prev_zoom; + self.offset = + self.offset + (self.offset - Vector::new(pos.x, pos.y)) * (ratio - 1.0); + Task::none() + } + CanvasMessage::OffsetChanged(delta) => { + self.offset = self.offset + delta; + Task::none() + } + }, + Message::ZoomSliderChanged(val) => { + self.zoom = val; + Task::none() + } + Message::ResetView => { + self.zoom = 1.0; + self.offset = Vector::new(0.0, 0.0); + Task::none() + } } } @@ -133,49 +175,157 @@ impl FerrulesDebug { fn view(&self) -> Element<'_, Message> { if let Some(mmap) = &self.mmap { let archived = unsafe { archived_root::(&mmap[..]) }; + let current_page_idx = self + .current_page_idx + .min(archived.pages.len().saturating_sub(1)); + let current_page = &archived.pages[current_page_idx]; let total_pages = archived.pages.len() as u32; - let safe_page_idx = (self.current_page_idx as u32).min(total_pages.saturating_sub(1)); - let current_page = &archived.pages[safe_page_idx as usize]; - - let sidebar = column![ - text(archived.name.as_str()).size(20), - button("Open File").on_press(Message::OpenFile).padding(10), - text(format!("Page {} / {}", safe_page_idx + 1, total_pages)), - slider( - 0..=(total_pages.saturating_sub(1)), - safe_page_idx, - Message::PageChanged - ), - column![ - text("Layers"), - checkbox("Native Lines", self.show_native) - .on_toggle(|_| Message::ToggleLayer(Layer::Native)), - checkbox("Paths", self.show_paths) - .on_toggle(|_| Message::ToggleLayer(Layer::Paths)), - checkbox("Layout BBoxes", self.show_layout) - .on_toggle(|_| Message::ToggleLayer(Layer::Layout)), - checkbox("OCR Lines", self.show_ocr) - .on_toggle(|_| Message::ToggleLayer(Layer::OCR)), - checkbox("Elements", self.show_elements) - .on_toggle(|_| Message::ToggleLayer(Layer::Elements)), - checkbox("Blocks", self.show_blocks) - .on_toggle(|_| Message::ToggleLayer(Layer::Blocks)), - ] - .spacing(10) - .padding(10), - text("Metadata"), - scrollable(text(format!("Elements: {}", current_page.elements.len()))) - ] - .width(Length::Fixed(250.0)) - .spacing(20) - .padding(20); - - // Page Viewer (Center) + let image_vec: Vec = (¤t_page.image_data[..]).to_vec(); let page_image = image::Handle::from_bytes(image_vec); - let painter = PagePainter { + let sidebar = container( + column![ + text(archived.name.as_str()).size(22), + button(text("Open File").size(14)) + .on_press(Message::OpenFile) + .padding([8, 16]), + column![ + text(format!("Page {} / {}", current_page_idx + 1, total_pages)).size(14), + slider( + 0..=(total_pages.saturating_sub(1)), + current_page_idx as u32, + Message::PageChanged + ), + ] + .spacing(5), + column![ + text("LAYERS") + .size(12) + .color(Color::from_rgb(0.5, 0.5, 0.5)), + self.layer_checkbox( + "Native Lines", + self.show_native, + Layer::Native, + Color::from_rgb(0.9, 0.2, 0.2) + ), + self.layer_checkbox( + "Vector Paths", + self.show_paths, + Layer::Paths, + Color::from_rgb(0.5, 0.5, 0.5) + ), + self.layer_checkbox( + "Layout Analysis", + self.show_layout, + Layer::Layout, + Color::from_rgb(0.2, 0.8, 0.4) + ), + self.layer_checkbox( + "OCR Results", + self.show_ocr, + Layer::OCR, + Color::from_rgb(0.2, 0.4, 0.9) + ), + self.layer_checkbox( + "Elements", + self.show_elements, + Layer::Elements, + Color::from_rgb(0.9, 0.9, 0.2) + ), + self.layer_checkbox( + "Blocks", + self.show_blocks, + Layer::Blocks, + Color::from_rgb(0.7, 0.2, 0.9) + ), + ] + .spacing(10), + column![ + text("INSPECTOR") + .size(12) + .color(Color::from_rgb(0.5, 0.5, 0.5)), + container(scrollable(match &self.hovered_info { + Some(info) => Element::from( + column![ + text(&info.title) + .size(15) + .color(Color::from_rgb(0.2, 0.8, 0.4)), + text(&info.details).size(13) + ] + .spacing(5) + ), + None => Element::from( + text("Hover an element to explore") + .size(13) + .color(Color::from_rgb(0.4, 0.4, 0.4)) + ), + })) + .padding(10) + .width(Length::Fill) + .height(Length::Fixed(250.0)) + .style(|theme: &Theme| { + let palette = theme.extended_palette(); + container::Style { + background: Some(palette.background.weak.color.into()), + border: iced::Border { + radius: 4.0.into(), + width: 1.0, + color: Color::from_rgba(1.0, 1.0, 1.0, 0.1), + }, + ..Default::default() + } + }) + ] + .spacing(8), + column![ + text("VIEW CONTROL") + .size(12) + .color(Color::from_rgb(0.5, 0.5, 0.5)), + row![ + text(format!("{:3.0}%", self.zoom * 100.0)) + .size(14) + .width(Length::Fixed(55.0)), + slider(0.1..=5.0, self.zoom, Message::ZoomSliderChanged).step(0.1), + button(text("Reset").size(12)) + .on_press(Message::ResetView) + .padding(5), + ] + .spacing(10) + .align_y(Alignment::Center) + ] + .spacing(8), + ] + .width(Length::Fixed(280.0)) + .spacing(15) + .padding(20), + ) + .height(Length::Fill) + .style(|_theme: &Theme| container::Style { + background: Some(Color::from_rgba(0.1, 0.1, 0.1, 0.9).into()), + ..Default::default() + }); + + let image_painter = PagePainter { + page: current_page, + image_handle: page_image.clone(), + zoom: self.zoom, + offset: self.offset, + mode: PainterMode::Image, + show_native: self.show_native, + show_layout: self.show_layout, + show_ocr: self.show_ocr, + show_elements: self.show_elements, + show_blocks: self.show_blocks, + show_paths: self.show_paths, + }; + + let overlay_painter = PagePainter { page: current_page, + image_handle: page_image.clone(), + zoom: self.zoom, + offset: self.offset, + mode: PainterMode::Overlay, show_native: self.show_native, show_layout: self.show_layout, show_ocr: self.show_ocr, @@ -184,16 +334,24 @@ impl FerrulesDebug { show_paths: self.show_paths, }; - let viewer = container(stack![ - image(page_image), - canvas(painter).width(Length::Fill).height(Length::Fill) - ]) + let viewer = container(iced::widget::stack(vec![ + Element::from( + canvas(image_painter) + .width(Length::Fill) + .height(Length::Fill), + ) + .map(|_| Message::ResetView), // Dummy mapping for compilation, will not emit events anyway + Element::from( + canvas(overlay_painter) + .width(Length::Fill) + .height(Length::Fill), + ) + .map(Message::CanvasEvent), + ])) .width(Length::Fill) .height(Length::Fill) - .center_x(Length::Fill) - .center_y(Length::Fill) - .style(|_theme| container::Style { - background: Some(iced::Background::Color(Color::BLACK)), + .style(|_| container::Style { + background: Some(Color::BLACK.into()), ..Default::default() }); @@ -201,12 +359,18 @@ impl FerrulesDebug { } else { container( column![ - text("Ferrules Debugger").size(40), - text("Visualize the PDF parsing pipeline").size(20), - button("Select .ferr File") + text("FERRULES") + .size(60) + .color(Color::from_rgb(0.2, 0.8, 0.4)), + text("Structure Extraction Debugger") + .size(20) + .color(Color::from_rgb(0.6, 0.6, 0.6)), + button(text("Select .ferr File").size(18)) .on_press(Message::OpenFile) - .padding([15, 30]), - text("Or drag and drop a file anywhere"), + .padding([15, 40]), + text("Or drag and drop a file here") + .size(14) + .color(Color::from_rgb(0.4, 0.4, 0.4)), ] .align_x(Alignment::Center) .spacing(30), @@ -218,4 +382,28 @@ impl FerrulesDebug { .into() } } + + fn layer_checkbox( + &self, + label: &str, + is_checked: bool, + layer: Layer, + color: Color, + ) -> Element<'_, Message> { + row![ + container(column![]) + .width(Length::Fixed(4.0)) + .height(Length::Fixed(16.0)) + .style(move |_| container::Style { + background: Some(color.into()), + ..Default::default() + }), + checkbox(label, is_checked) + .on_toggle(move |_| Message::ToggleLayer(layer.clone())) + .size(16) + ] + .spacing(10) + .align_y(Alignment::Center) + .into() + } } diff --git a/ferrules-debug/src/painter.rs b/ferrules-debug/src/painter.rs index 1bbbde3..5283605 100644 --- a/ferrules-debug/src/painter.rs +++ b/ferrules-debug/src/painter.rs @@ -1,10 +1,23 @@ use ferrules_core::debug_info::ArchivedDebugPage; use ferrules_core::entities::ArchivedSegment; -use iced::widget::canvas::{Frame, Geometry, Path, Program, Stroke}; -use iced::{Color, Point, Rectangle, Renderer, Theme}; +use iced::mouse::{self, Cursor}; +use iced::widget::canvas::{self, Frame, Geometry, Image, Path, Program, Stroke}; +use iced::widget::image; +use iced::{Color, Point, Rectangle, Renderer, Theme, Vector}; + +pub enum PainterMode { + Image, + Overlay, +} pub struct PagePainter<'a> { pub page: &'a ArchivedDebugPage, + pub image_handle: image::Handle, + pub zoom: f32, + pub offset: Vector, + pub mode: PainterMode, + + // Config pub show_native: bool, pub show_layout: bool, pub show_ocr: bool, @@ -13,154 +26,419 @@ pub struct PagePainter<'a> { pub show_paths: bool, } -impl<'a, Message> Program for PagePainter<'a> { - type State = (); +pub struct State { + pub last_cursor_position: Point, + pub is_panning: bool, + pub hovered_info: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct HoverDetailed { + pub title: String, + pub details: String, +} + +impl Default for State { + fn default() -> Self { + Self { + last_cursor_position: Point::ORIGIN, + is_panning: false, + hovered_info: None, + } + } +} + +#[derive(Debug, Clone)] +pub enum CanvasMessage { + Hovered(Option), + ZoomChanged(f32, Point), + OffsetChanged(Vector), +} + +impl<'a> Program for PagePainter<'a> { + type State = State; + + fn update( + &self, + state: &mut Self::State, + event: canvas::Event, + bounds: Rectangle, + cursor: Cursor, + ) -> (canvas::event::Status, Option) { + // Only Overlay handles interactions + if let PainterMode::Image = self.mode { + return (canvas::event::Status::Ignored, None); + } + + let cursor_position = cursor.position_in(bounds); + + match event { + canvas::Event::Mouse(mouse_event) => match mouse_event { + mouse::Event::WheelScrolled { delta } => { + if let Some(pos) = cursor_position { + let factor = match delta { + mouse::ScrollDelta::Lines { y, .. } => y * 0.1, + mouse::ScrollDelta::Pixels { y, .. } => y * 0.001, + }; + return ( + canvas::event::Status::Captured, + Some(CanvasMessage::ZoomChanged(factor, pos)), + ); + } + } + mouse::Event::ButtonPressed(mouse::Button::Left) => { + if let Some(pos) = cursor_position { + state.is_panning = true; + state.last_cursor_position = pos; + return (canvas::event::Status::Captured, None); + } + } + mouse::Event::ButtonReleased(mouse::Button::Left) => { + state.is_panning = false; + return (canvas::event::Status::Captured, None); + } + mouse::Event::CursorMoved { .. } => { + if let Some(pos) = cursor_position { + if state.is_panning { + let delta = pos - state.last_cursor_position; + state.last_cursor_position = pos; + return ( + canvas::event::Status::Captured, + Some(CanvasMessage::OffsetChanged(Vector::new(delta.x, delta.y))), + ); + } + + let new_hover = self.get_hover_detailed(pos, bounds); + if new_hover != state.hovered_info { + state.hovered_info = new_hover.clone(); + return ( + canvas::event::Status::Captured, + Some(CanvasMessage::Hovered(new_hover)), + ); + } + } + } + _ => {} + }, + _ => {} + } + + (canvas::event::Status::Ignored, None) + } fn draw( &self, - _state: &Self::State, + state: &Self::State, renderer: &Renderer, _theme: &Theme, bounds: Rectangle, - _cursor: iced::mouse::Cursor, + _cursor: Cursor, ) -> Vec { let mut frame = Frame::new(renderer, bounds.size()); - // Scale factor to fit the page into the bounds - let scale_x = bounds.width / self.page.width; - let scale_y = bounds.height / self.page.height; - let scale = scale_x.min(scale_y); - - let offset_x = (bounds.width - self.page.width * scale) / 2.0; - let offset_y = (bounds.height - self.page.height * scale) / 2.0; - - let to_screen = - |x: f32, y: f32| -> Point { Point::new(x * scale + offset_x, y * scale + offset_y) }; - - // Draw Paths first (Background) - if self.show_paths { - for path in self.page.paths.as_slice() { - for segment in path.segments.as_slice() { - match segment { - ArchivedSegment::Line { start, end } => { - let p1 = to_screen(start.0, start.1); - let p2 = to_screen(end.0, end.1); - frame.stroke( - &Path::line(p1, p2), - Stroke::default() - .with_color(Color::from_rgb(0.5, 0.5, 0.5)) - .with_width(0.5), - ); + let fit_scale_x = bounds.width / self.page.width; + let fit_scale_y = bounds.height / self.page.height; + let fit_scale = fit_scale_x.min(fit_scale_y); + + let final_scale = fit_scale * self.zoom; + let center_offset_x = (bounds.width - self.page.width * fit_scale) / 2.0; + let center_offset_y = (bounds.height - self.page.height * fit_scale) / 2.0; + let total_offset_x = center_offset_x + self.offset.x; + let total_offset_y = center_offset_y + self.offset.y; + + match self.mode { + PainterMode::Image => { + let image_rect = self.to_rect( + 0.0, + 0.0, + self.page.width, + self.page.height, + final_scale, + total_offset_x, + total_offset_y, + ); + let img = Image::new(self.image_handle.clone()); + frame.draw_image(image_rect, img); + } + PainterMode::Overlay => { + // Colors + let red = Color::from_rgb(1.0, 0.0, 0.0); + let blue = Color::from_rgb(0.2, 0.6, 1.0); + let green = Color::from_rgb(0.2, 1.0, 0.4); + let yellow = Color::from_rgb(1.0, 1.0, 0.0); + let purple = Color::from_rgb(0.8, 0.2, 1.0); + let path_col = Color::from_rgb(0.5, 0.5, 0.5); + + if self.show_paths { + for path in self.page.paths.as_slice() { + for segment in path.segments.as_slice() { + match segment { + ArchivedSegment::Line { start, end } => { + let p1 = Point::new( + start.0 * final_scale + total_offset_x, + start.1 * final_scale + total_offset_y, + ); + let p2 = Point::new( + end.0 * final_scale + total_offset_x, + end.1 * final_scale + total_offset_y, + ); + frame.stroke( + &Path::line(p1, p2), + Stroke::default().with_color(path_col).with_width(1.5), + ); + } + ArchivedSegment::Rect { bbox } => { + let rect = self.to_rect( + bbox.x0, + bbox.y0, + bbox.x1, + bbox.y1, + final_scale, + total_offset_x, + total_offset_y, + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(path_col).with_width(1.5), + ); + } + } } - ArchivedSegment::Rect { bbox } => { - let rect = Rectangle { - x: bbox.x0 * scale + offset_x, - y: bbox.y0 * scale + offset_y, - width: (bbox.x1 - bbox.x0) * scale, - height: (bbox.y1 - bbox.y0) * scale, - }; - frame.stroke( - &Path::rectangle(rect.position(), rect.size()), - Stroke::default() - .with_color(Color::from_rgb(0.5, 0.5, 0.5)) - .with_width(0.5), - ); + } + } + + if self.show_native { + for line in self.page.native_lines.as_slice() { + let rect = self.to_rect( + line.bbox.x0, + line.bbox.y0, + line.bbox.x1, + line.bbox.y1, + final_scale, + total_offset_x, + total_offset_y, + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(red).with_width(1.5), + ); + } + } + + if self.show_ocr { + for line in self.page.ocr_lines.as_slice() { + let rect = self.to_rect( + line.bbox.x0, + line.bbox.y0, + line.bbox.x1, + line.bbox.y1, + final_scale, + total_offset_x, + total_offset_y, + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(blue).with_width(1.5), + ); + } + } + + if self.show_layout { + for bbox in self.page.layout_bboxes.as_slice() { + let rect = self.to_rect( + bbox.bbox.x0, + bbox.bbox.y0, + bbox.bbox.x1, + bbox.bbox.y1, + final_scale, + total_offset_x, + total_offset_y, + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(green).with_width(2.0), + ); + } + } + + if self.show_elements { + for element in self.page.elements.as_slice() { + let rect = self.to_rect( + element.bbox.x0, + element.bbox.y0, + element.bbox.x1, + element.bbox.y1, + final_scale, + total_offset_x, + total_offset_y, + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(yellow).with_width(2.5), + ); + } + } + + if self.show_blocks { + for block in self.page.blocks.as_slice() { + let rect = self.to_rect( + block.bbox.x0, + block.bbox.y0, + block.bbox.x1, + block.bbox.y1, + final_scale, + total_offset_x, + total_offset_y, + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(purple).with_width(3.0), + ); + } + } + + if let Some(hover) = &state.hovered_info { + let mut highlight_bbox = None; + if hover.title.starts_with("Element") { + if let Some(id_str) = hover.title.split('#').last() { + if let Ok(id) = id_str.parse::() { + if let Some(e) = self + .page + .elements + .as_slice() + .iter() + .find(|e| (e.id as usize) == id) + { + highlight_bbox = Some(&e.bbox); + } + } + } + } else if hover.title.starts_with("Block") { + if let Some(id_str) = hover.title.split('#').last() { + if let Ok(id) = id_str.parse::() { + if let Some(b) = self + .page + .blocks + .as_slice() + .iter() + .find(|b| (b.id as usize) == id) + { + highlight_bbox = Some(&b.bbox); + } + } } } + + if let Some(bbox) = highlight_bbox { + let rect = self.to_rect( + bbox.x0, + bbox.y0, + bbox.x1, + bbox.y1, + final_scale, + total_offset_x, + total_offset_y, + ); + frame.fill( + &Path::rectangle(rect.position(), rect.size()), + Color::from_rgba(1.0, 1.0, 1.0, 0.4), + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(Color::WHITE).with_width(3.0), + ); + } } } } - // Draw Native Lines - if self.show_native { - for line in self.page.native_lines.as_slice() { - let rect = Rectangle { - x: line.bbox.x0 * scale + offset_x, - y: line.bbox.y0 * scale + offset_y, - width: (line.bbox.x1 - line.bbox.x0) * scale, - height: (line.bbox.y1 - line.bbox.y0) * scale, - }; - frame.stroke( - &Path::rectangle(rect.position(), rect.size()), - Stroke::default() - .with_color(Color::from_rgb(1.0, 0.0, 0.0)) - .with_width(1.0), - ); - } - } + vec![frame.into_geometry()] + } +} - // Draw OCR Lines - if self.show_ocr { - for line in self.page.ocr_lines.as_slice() { - let rect = Rectangle { - x: line.bbox.x0 * scale + offset_x, - y: line.bbox.y0 * scale + offset_y, - width: (line.bbox.x1 - line.bbox.x0) * scale, - height: (line.bbox.y1 - line.bbox.y0) * scale, - }; - frame.stroke( - &Path::rectangle(rect.position(), rect.size()), - Stroke::default() - .with_color(Color::from_rgb(0.0, 0.0, 1.0)) // Blue for OCR - .with_width(1.0), - ); - } +impl<'a> PagePainter<'a> { + fn to_rect( + &self, + x0: f32, + y0: f32, + x1: f32, + y1: f32, + scale: f32, + off_x: f32, + off_y: f32, + ) -> Rectangle { + // Ensure x,y are top-left + let x = x0.min(x1); + let y = y0.min(y1); + let w = (x1 - x0).abs(); + let h = (y1 - y0).abs(); + + Rectangle { + x: x * scale + off_x, + y: y * scale + off_y, + width: w.max(0.1) * scale, + height: h.max(0.1) * scale, } + } - // Draw Layout BBoxes - if self.show_layout { - for bbox in self.page.layout_bboxes.as_slice() { - let rect = Rectangle { - x: bbox.bbox.x0 * scale + offset_x, - y: bbox.bbox.y0 * scale + offset_y, - width: (bbox.bbox.x1 - bbox.bbox.x0) * scale, - height: (bbox.bbox.y1 - bbox.bbox.y0) * scale, - }; - frame.stroke( - &Path::rectangle(rect.position(), rect.size()), - Stroke::default() - .with_color(Color::from_rgb(0.0, 1.0, 0.0)) - .with_width(1.5), - ); + fn get_hover_detailed(&self, pos: Point, bounds: Rectangle) -> Option { + let fit_scale_x = bounds.width / self.page.width; + let fit_scale_y = bounds.height / self.page.height; + let fit_scale = fit_scale_x.min(fit_scale_y); + let final_scale = fit_scale * self.zoom; + let center_offset_x = (bounds.width - self.page.width * fit_scale) / 2.0; + let center_offset_y = (bounds.height - self.page.height * fit_scale) / 2.0; + let total_offset_x = center_offset_x + self.offset.x; + let total_offset_y = center_offset_y + self.offset.y; + + let px = (pos.x - total_offset_x) / final_scale; + let py = (pos.y - total_offset_y) / final_scale; + + if self.show_blocks { + for block in self.page.blocks.as_slice() { + if px >= block.bbox.x0 + && px <= block.bbox.x1 + && py >= block.bbox.y0 + && py <= block.bbox.y1 + { + return Some(HoverDetailed { + title: format!("Block #{}", block.id), + details: format!( + "BBox: [{:.1}, {:.1}, {:.1}, {:.1}]\nPages: {:?}", + block.bbox.x0, + block.bbox.y0, + block.bbox.x1, + block.bbox.y1, + block.pages_id + ), + }); + } } } - // Draw Elements if self.show_elements { for element in self.page.elements.as_slice() { - let rect = Rectangle { - x: element.bbox.x0 * scale + offset_x, - y: element.bbox.y0 * scale + offset_y, - width: (element.bbox.x1 - element.bbox.x0) * scale, - height: (element.bbox.y1 - element.bbox.y0) * scale, - }; - frame.stroke( - &Path::rectangle(rect.position(), rect.size()), - Stroke::default() - .with_color(Color::from_rgb(1.0, 1.0, 0.0)) - .with_width(2.0), - ); - } - } - - // Draw Blocks - if self.show_blocks { - for block in self.page.blocks.as_slice() { - let rect = Rectangle { - x: block.bbox.x0 * scale + offset_x, - y: block.bbox.y0 * scale + offset_y, - width: (block.bbox.x1 - block.bbox.x0) * scale, - height: (block.bbox.y1 - block.bbox.y0) * scale, - }; - frame.stroke( - &Path::rectangle(rect.position(), rect.size()), - Stroke::default() - .with_color(Color::from_rgb(1.0, 0.0, 1.0)) - .with_width(2.5), - ); + if px >= element.bbox.x0 + && px <= element.bbox.x1 + && py >= element.bbox.y0 + && py <= element.bbox.y1 + { + return Some(HoverDetailed { + title: format!("Element #{}", element.id), + details: format!( + "BBox: [{:.1}, {:.1}, {:.1}, {:.1}]\nText: {}\nLayout ID: {}", + element.bbox.x0, + element.bbox.y0, + element.bbox.x1, + element.bbox.y1, + element.text_block.text.as_str(), + element.layout_block_id + ), + }); + } } } - vec![frame.into_geometry()] + None } } From c40c944d73e9d054a52e9e9a33320385fdb59a6b Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 13:38:58 +0100 Subject: [PATCH 4/8] fix theme --- ferrules-debug/src/inspector.rs | 214 ++++++++++++++++++++ ferrules-debug/src/main.rs | 336 +++++++++++++++++++++----------- ferrules-debug/src/painter.rs | 219 +++++++++++++-------- 3 files changed, 577 insertions(+), 192 deletions(-) create mode 100644 ferrules-debug/src/inspector.rs diff --git a/ferrules-debug/src/inspector.rs b/ferrules-debug/src/inspector.rs new file mode 100644 index 0000000..f0e54c2 --- /dev/null +++ b/ferrules-debug/src/inspector.rs @@ -0,0 +1,214 @@ +use iced::font::Weight; +use iced::widget::{column, container, row, scrollable, text, Rule, Space}; +use iced::{Color, Element, Font, Length, Theme}; + +#[derive(Debug, Clone, PartialEq)] +pub enum InspectorItem { + Block { + id: usize, + kind: String, + bbox: [f32; 4], // x0, y0, x1, y1 + pages: Vec, + }, + Element { + id: usize, + kind: String, + bbox: [f32; 4], + layout_ref: i32, + text: String, + }, + Layout { + id: i32, + label: String, + proba: f32, + bbox: [f32; 4], + }, + None, +} + +impl InspectorItem { + pub fn title(&self) -> String { + match self { + InspectorItem::Block { id, .. } => format!("Block #{}", id), + InspectorItem::Element { id, .. } => format!("Element #{}", id), + InspectorItem::Layout { id, .. } => format!("Layout #{}", id), + InspectorItem::None => "No Selection".to_string(), + } + } + + pub fn bbox(&self) -> Option<[f32; 4]> { + match self { + InspectorItem::Block { bbox, .. } => Some(*bbox), + InspectorItem::Element { bbox, .. } => Some(*bbox), + InspectorItem::Layout { bbox, .. } => Some(*bbox), + InspectorItem::None => None, + } + } + + pub fn kind_label(&self) -> String { + match self { + InspectorItem::Block { kind, .. } => kind.clone(), + InspectorItem::Element { kind, .. } => kind.clone(), + InspectorItem::Layout { label, .. } => label.clone(), + InspectorItem::None => "".to_string(), + } + } +} + +pub fn view_inspector<'a, Message>(item: &'a InspectorItem) -> Element<'a, Message> +where + Message: 'a + Clone + std::fmt::Debug, +{ + let content: Element<'a, Message> = match item { + InspectorItem::Block { + id, + kind, + bbox, + pages, + } => column![ + header("BLOCK", *id, kind.clone()), + field("Type", kind.clone()), + bbox_field(bbox), + field("Pages", format!("{:?}", pages)), + ] + .into(), + InspectorItem::Element { + id, + kind, + bbox, + layout_ref, + text, + } => { + column![ + header("ELEMENT", *id, kind.clone()), + field("Type", kind.clone()), + field("Layout Ref", layout_ref.to_string()), + bbox_field(bbox), + Space::with_height(10), + section_header("Content"), + container( + iced::widget::text(text.clone()) + .size(13) + .line_height(iced::widget::text::LineHeight::Relative(1.4)) + ) + .padding(8) // Apply padding to container widget + .style(|theme: &Theme| { + let palette = theme.extended_palette(); + container::Style { + background: Some(palette.background.weak.color.into()), + border: iced::Border { + color: palette.background.strong.color, + width: 1.0, + radius: 4.0.into(), + }, + ..Default::default() + } + }) + ] + .into() + } + InspectorItem::Layout { + id, + label, + proba, + bbox, + } => column![ + header("LAYOUT", *id as usize, label.clone()), + field("Label", label.clone()), + field("Confidence", format!("{:.2}%", proba * 100.0)), + bbox_field(bbox), + ] + .into(), + InspectorItem::None => column![text("No Selection") + .size(14) + .color(Color::from_rgb(0.6, 0.6, 0.6))] + .align_x(iced::alignment::Horizontal::Center) + .into(), + }; + + scrollable(container(content).width(Length::Fill).padding(15)).into() +} + +fn header<'a, Message>(type_name: &'a str, id: usize, subtitle: String) -> Element<'a, Message> +where + Message: 'a + Clone + std::fmt::Debug, +{ + column![ + row![ + text(type_name) + .size(12) + .font(Font { + weight: Weight::Bold, + ..Default::default() + }) + .color(Color::from_rgb(0.4, 0.4, 1.0)), + text(format!("#{}", id)) + .size(12) + .color(Color::from_rgb(0.6, 0.6, 0.6)), + ] + .spacing(5), + text(subtitle).size(20).font(Font { + weight: Weight::Semibold, + ..Default::default() + }), + Rule::horizontal(1), + ] + .spacing(5) + .into() +} + +fn field<'a, Message>(key: &'a str, value: String) -> Element<'a, Message> +where + Message: 'a + Clone + std::fmt::Debug, +{ + column![ + text(key).size(11).color(Color::from_rgb(0.5, 0.5, 0.5)), + text(value).size(14), + ] + .into() +} + +fn bbox_field<'a, Message>(bbox: &[f32; 4]) -> Element<'a, Message> +where + Message: 'a + Clone + std::fmt::Debug, +{ + let [x0, y0, x1, y1] = bbox; + let w = x1 - x0; + let h = y1 - y0; + + column![ + text("Geometry") + .size(11) + .color(Color::from_rgb(0.5, 0.5, 0.5)), + row![kv_small("X", *x0), kv_small("Y", *y0),].spacing(15), + row![kv_small("W", w), kv_small("H", h),].spacing(15), + ] + .spacing(5) + .into() +} + +fn kv_small<'a, Message>(k: &'a str, v: f32) -> Element<'a, Message> +where + Message: 'a + Clone + std::fmt::Debug, +{ + row![ + text(k).size(11).color(Color::from_rgb(0.5, 0.5, 0.5)), + text(format!("{:.1}", v)).size(12).font(Font::MONOSPACE), + ] + .spacing(5) + .into() +} + +fn section_header<'a, Message>(title: &'a str) -> Element<'a, Message> +where + Message: 'a + Clone + std::fmt::Debug, +{ + text(title) + .size(12) + .font(Font { + weight: Weight::Bold, + ..Default::default() + }) + .color(Color::from_rgb(0.7, 0.7, 0.7)) + .into() +} diff --git a/ferrules-debug/src/main.rs b/ferrules-debug/src/main.rs index b4a667e..500333c 100644 --- a/ferrules-debug/src/main.rs +++ b/ferrules-debug/src/main.rs @@ -1,15 +1,20 @@ use clap::Parser; use ferrules_core::debug_info::DebugDocument; +use iced::font::Weight; +use iced::widget::text::LineHeight; use iced::widget::{ - button, canvas, checkbox, column, container, image, row, scrollable, slider, text, + button, canvas, checkbox, column, container, horizontal_space, image, row, scrollable, slider, + text, vertical_space, Space, Tooltip, }; -use iced::{event, window, Alignment, Color, Element, Event, Length, Task, Theme, Vector}; +use iced::{event, window, Alignment, Color, Element, Event, Font, Length, Task, Theme, Vector}; use memmap2::Mmap; use rkyv::archived_root; use std::path::PathBuf; +mod inspector; mod painter; -use painter::{CanvasMessage, HoverDetailed, PagePainter, PainterMode}; +use inspector::{view_inspector, InspectorItem}; +use painter::{CanvasMessage, PagePainter, PainterMode}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -23,11 +28,15 @@ pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); let args = Args::parse(); iced::application("Ferrules Debug", FerrulesDebug::update, FerrulesDebug::view) - .theme(|_| Theme::Dark) + .theme(|_| Theme::Dracula) .subscription(FerrulesDebug::subscription) .run_with(move || FerrulesDebug::new(args.file)) } +const SIDEBAR_COLOR: Color = Color::from_rgb(0.15, 0.16, 0.21); // #282a36 (Dracula Background) +const TOPBAR_COLOR: Color = Color::from_rgb(0.26, 0.28, 0.35); // #44475a (Dracula Current Line) +const ACCENT_COLOR: Color = Color::from_rgb(0.74, 0.57, 0.97); // #bd93f9 (Dracula Purple) + struct FerrulesDebug { mmap: Option, current_page_idx: usize, @@ -42,7 +51,9 @@ struct FerrulesDebug { show_elements: bool, show_blocks: bool, show_paths: bool, - hovered_info: Option, + + hovered_info: InspectorItem, + sidebar_open: bool, } #[derive(Debug, Clone)] @@ -55,6 +66,7 @@ enum Message { CanvasEvent(CanvasMessage), ZoomSliderChanged(f32), ResetView, + ToggleSidebar, } #[derive(Debug, Clone)] @@ -83,7 +95,8 @@ impl FerrulesDebug { show_elements: true, show_blocks: true, show_paths: true, - hovered_info: None, + hovered_info: InspectorItem::None, + sidebar_open: true, }, if let Some(path) = file_path { Task::done(Message::FileSelected(Some(path))) @@ -97,8 +110,6 @@ impl FerrulesDebug { match message { Message::PageChanged(idx) => { self.current_page_idx = idx as usize; - self.zoom = 1.0; - self.offset = Vector::new(0.0, 0.0); Task::none() } Message::ToggleLayer(l) => { @@ -126,8 +137,6 @@ impl FerrulesDebug { if let Ok(m) = unsafe { Mmap::map(&file) } { self.mmap = Some(m); self.current_page_idx = 0; - self.cached_page_image_handle = None; - self.last_page_idx_cached = None; self.zoom = 1.0; self.offset = Vector::new(0.0, 0.0); } @@ -137,7 +146,7 @@ impl FerrulesDebug { Message::FileSelected(None) => Task::none(), Message::CanvasEvent(ev) => match ev { CanvasMessage::Hovered(info) => { - self.hovered_info = info; + self.hovered_info = info.unwrap_or(InspectorItem::None); Task::none() } CanvasMessage::ZoomChanged(factor, pos) => { @@ -162,6 +171,10 @@ impl FerrulesDebug { self.offset = Vector::new(0.0, 0.0); Task::none() } + Message::ToggleSidebar => { + self.sidebar_open = !self.sidebar_open; + Task::none() + } } } @@ -173,6 +186,15 @@ impl FerrulesDebug { } fn view(&self) -> Element<'_, Message> { + let bold_font = Font { + weight: Weight::Bold, + ..Default::default() + }; + let medium_font = Font { + weight: Weight::Medium, + ..Default::default() + }; + if let Some(mmap) = &self.mmap { let archived = unsafe { archived_root::(&mmap[..]) }; let current_page_idx = self @@ -184,128 +206,199 @@ impl FerrulesDebug { let image_vec: Vec = (¤t_page.image_data[..]).to_vec(); let page_image = image::Handle::from_bytes(image_vec); - let sidebar = container( + // --- LEFT SIDEBAR --- + let left_sidebar_content = if self.sidebar_open { column![ - text(archived.name.as_str()).size(22), - button(text("Open File").size(14)) - .on_press(Message::OpenFile) - .padding([8, 16]), - column![ - text(format!("Page {} / {}", current_page_idx + 1, total_pages)).size(14), - slider( - 0..=(total_pages.saturating_sub(1)), - current_page_idx as u32, - Message::PageChanged - ), + row![ + text("FERRULES") + .size(20) + .font(bold_font) + .color(ACCENT_COLOR), + horizontal_space(), + button(text("‹").size(16).font(bold_font)) + .on_press(Message::ToggleSidebar) + .padding(5) + .style(button::text), ] - .spacing(5), + .align_y(Alignment::Center) + .width(Length::Fill), + Space::with_height(10), + text(archived.name.as_str()) + .size(13) + .color(Color::from_rgb(0.6, 0.6, 0.7)) + .font(medium_font), + Space::with_height(30), + button(text("Open Archive").size(14).font(medium_font)) + .on_press(Message::OpenFile) + .padding(10) + .width(Length::Fill), + Space::with_height(30), + text("LAYERS") + .size(11) + .color(Color::from_rgb(0.5, 0.5, 0.6)) + .font(bold_font), column![ - text("LAYERS") - .size(12) - .color(Color::from_rgb(0.5, 0.5, 0.5)), self.layer_checkbox( "Native Lines", self.show_native, Layer::Native, - Color::from_rgb(0.9, 0.2, 0.2) + Color::from_rgb(1.0, 0.33, 0.33) ), self.layer_checkbox( "Vector Paths", self.show_paths, Layer::Paths, - Color::from_rgb(0.5, 0.5, 0.5) + Color::from_rgba(0.9, 0.9, 0.9, 0.5) ), self.layer_checkbox( "Layout Analysis", self.show_layout, Layer::Layout, - Color::from_rgb(0.2, 0.8, 0.4) + Color::from_rgb(0.31, 1.0, 0.44) ), self.layer_checkbox( - "OCR Results", + "OCR Text Lines", self.show_ocr, Layer::OCR, - Color::from_rgb(0.2, 0.4, 0.9) + Color::from_rgb(0.54, 0.88, 1.0) ), self.layer_checkbox( - "Elements", + "Logical Elements", self.show_elements, Layer::Elements, - Color::from_rgb(0.9, 0.9, 0.2) + Color::from_rgb(1.0, 0.72, 0.42) ), self.layer_checkbox( - "Blocks", + "Structural Blocks", self.show_blocks, Layer::Blocks, - Color::from_rgb(0.7, 0.2, 0.9) + Color::from_rgb(0.74, 0.57, 0.97) ), ] - .spacing(10), - column![ - text("INSPECTOR") - .size(12) - .color(Color::from_rgb(0.5, 0.5, 0.5)), - container(scrollable(match &self.hovered_info { - Some(info) => Element::from( - column![ - text(&info.title) - .size(15) - .color(Color::from_rgb(0.2, 0.8, 0.4)), - text(&info.details).size(13) - ] - .spacing(5) - ), - None => Element::from( - text("Hover an element to explore") - .size(13) - .color(Color::from_rgb(0.4, 0.4, 0.4)) - ), - })) - .padding(10) - .width(Length::Fill) - .height(Length::Fixed(250.0)) - .style(|theme: &Theme| { - let palette = theme.extended_palette(); - container::Style { - background: Some(palette.background.weak.color.into()), - border: iced::Border { - radius: 4.0.into(), - width: 1.0, - color: Color::from_rgba(1.0, 1.0, 1.0, 0.1), - }, - ..Default::default() - } - }) - ] - .spacing(8), - column![ - text("VIEW CONTROL") - .size(12) - .color(Color::from_rgb(0.5, 0.5, 0.5)), + .spacing(14), + ] + .spacing(10) + .padding(20) + } else { + column![ + button(text("›").size(16).font(bold_font)) + .on_press(Message::ToggleSidebar) + .padding(5) + .style(button::text), + Space::with_height(30), + Tooltip::new( + button(text("📂").size(18)) + .on_press(Message::OpenFile) + .padding(8) + .style(button::secondary), + "Open File", + iced::widget::tooltip::Position::Right, + ), + ] + .spacing(20) + .padding(10) + .align_x(Alignment::Center) + }; + + let left_sidebar = container(left_sidebar_content) + .width(if self.sidebar_open { + Length::Fixed(240.0) + } else { + Length::Fixed(60.0) + }) + .height(Length::Fill) + .style(move |_| container::Style { + background: Some(SIDEBAR_COLOR.into()), + border: iced::Border { + width: 0.0, + color: Color::TRANSPARENT, + ..Default::default() + }, + ..Default::default() + }); + + // --- TOP BAR (Modern Compact Nav) --- + let top_bar = container( + row![ + horizontal_space(), + container( row![ - text(format!("{:3.0}%", self.zoom * 100.0)) + button(text("←").size(14).font(bold_font)) + .on_press(Message::PageChanged( + current_page_idx.saturating_sub(1) as u32 + )) + .padding(6) + .style(button::text), + text(format!("{} / {}", current_page_idx + 1, total_pages)) .size(14) - .width(Length::Fixed(55.0)), - slider(0.1..=5.0, self.zoom, Message::ZoomSliderChanged).step(0.1), - button(text("Reset").size(12)) - .on_press(Message::ResetView) - .padding(5), + .font(bold_font), + button(text("→").size(14).font(bold_font)) + .on_press(Message::PageChanged( + (current_page_idx + 1) + .min(total_pages.saturating_sub(1) as usize) + as u32 + )) + .padding(6) + .style(button::text), ] - .spacing(10) + .spacing(15) .align_y(Alignment::Center) + ) + .padding([0, 20]) + .style(move |_| container::Style { + background: Some(SIDEBAR_COLOR.into()), + border: iced::Border { + radius: 20.0.into(), + ..Default::default() + }, + ..Default::default() + }), + horizontal_space(), + row![ + text(format!("{:3.0}%", self.zoom * 100.0)) + .size(13) + .font(bold_font) + .color(Color::from_rgb(0.6, 0.6, 0.7)), + slider(0.1..=5.0, self.zoom, Message::ZoomSliderChanged) + .step(0.1) + .width(Length::Fixed(120.0)), + button(text("Reset").size(12).font(bold_font)) + .on_press(Message::ResetView) + .padding([4, 12]) + .style(button::secondary), ] - .spacing(8), + .spacing(15) + .align_y(Alignment::Center) + ] + .width(Length::Fill) + .align_y(Alignment::Center) + .padding(12), + ) + .style(move |_| container::Style { + background: Some(TOPBAR_COLOR.into()), + ..Default::default() + }); + + // --- RIGHT SIDEBAR (Inspector) --- + let right_sidebar = container( + column![ + text("INSPECTOR") + .size(11) + .color(Color::from_rgb(0.5, 0.5, 0.6)) + .font(bold_font), + Space::with_height(10), + view_inspector(&self.hovered_info) ] - .width(Length::Fixed(280.0)) - .spacing(15) .padding(20), ) + .width(Length::Fixed(320.0)) .height(Length::Fill) - .style(|_theme: &Theme| container::Style { - background: Some(Color::from_rgba(0.1, 0.1, 0.1, 0.9).into()), + .style(move |_| container::Style { + background: Some(SIDEBAR_COLOR.into()), ..Default::default() }); + // --- MAIN CANVAS --- let image_painter = PagePainter { page: current_page, image_handle: page_image.clone(), @@ -334,13 +427,13 @@ impl FerrulesDebug { show_paths: self.show_paths, }; - let viewer = container(iced::widget::stack(vec![ + let canvas_view = container(iced::widget::stack(vec![ Element::from( canvas(image_painter) .width(Length::Fill) .height(Length::Fill), ) - .map(|_| Message::ResetView), // Dummy mapping for compilation, will not emit events anyway + .map(|_| Message::ResetView), Element::from( canvas(overlay_painter) .width(Length::Fill) @@ -351,29 +444,44 @@ impl FerrulesDebug { .width(Length::Fill) .height(Length::Fill) .style(|_| container::Style { - background: Some(Color::BLACK.into()), + background: Some(Color::from_rgb(0.05, 0.05, 0.07).into()), ..Default::default() }); - row![sidebar, viewer].into() + // Assemble Layout + row![ + left_sidebar, + column![top_bar, canvas_view].width(Length::Fill), + right_sidebar + ] + .into() } else { + let extra_bold_font = Font { + weight: Weight::ExtraBold, + ..Default::default() + }; container( column![ text("FERRULES") - .size(60) - .color(Color::from_rgb(0.2, 0.8, 0.4)), - text("Structure Extraction Debugger") - .size(20) - .color(Color::from_rgb(0.6, 0.6, 0.6)), - button(text("Select .ferr File").size(18)) + .size(80) + .font(extra_bold_font) + .color(ACCENT_COLOR), + text("DOCUMENT ANALYSIS TOOLKIT") + .size(18) + .color(Color::from_rgb(0.5, 0.5, 0.6)) + .font(bold_font), + Space::with_height(40), + button(text("Select .ferr File").size(18).font(bold_font)) .on_press(Message::OpenFile) - .padding([15, 40]), - text("Or drag and drop a file here") + .padding([15, 60]) + .style(button::primary), + text("Or drop archive here") .size(14) - .color(Color::from_rgb(0.4, 0.4, 0.4)), + .color(Color::from_rgb(0.4, 0.4, 0.5)) + .font(medium_font), ] .align_x(Alignment::Center) - .spacing(30), + .spacing(20), ) .width(Length::Fill) .height(Length::Fill) @@ -390,19 +498,29 @@ impl FerrulesDebug { layer: Layer, color: Color, ) -> Element<'_, Message> { + let bold_font = Font { + weight: Weight::Bold, + ..Default::default() + }; row![ - container(column![]) - .width(Length::Fixed(4.0)) - .height(Length::Fixed(16.0)) + container(Space::with_width(3)) + .width(Length::Fixed(3.0)) + .height(Length::Fixed(12.0)) .style(move |_| container::Style { background: Some(color.into()), + border: iced::Border { + radius: 2.0.into(), + ..Default::default() + }, ..Default::default() }), checkbox(label, is_checked) .on_toggle(move |_| Message::ToggleLayer(layer.clone())) - .size(16) + .size(14) + .text_size(13) + .font(bold_font), ] - .spacing(10) + .spacing(12) .align_y(Alignment::Center) .into() } diff --git a/ferrules-debug/src/painter.rs b/ferrules-debug/src/painter.rs index 5283605..98eed3d 100644 --- a/ferrules-debug/src/painter.rs +++ b/ferrules-debug/src/painter.rs @@ -1,10 +1,13 @@ +use crate::inspector::InspectorItem; +use ferrules_core::blocks::ArchivedBlockType; use ferrules_core::debug_info::ArchivedDebugPage; -use ferrules_core::entities::ArchivedSegment; +use ferrules_core::entities::{ArchivedElementType, ArchivedSegment}; use iced::mouse::{self, Cursor}; -use iced::widget::canvas::{self, Frame, Geometry, Image, Path, Program, Stroke}; +use iced::widget::canvas::{self, Frame, Geometry, Image, Path, Program, Stroke, Text}; use iced::widget::image; -use iced::{Color, Point, Rectangle, Renderer, Theme, Vector}; +use iced::{Color, Font, Point, Rectangle, Renderer, Theme, Vector}; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PainterMode { Image, Overlay, @@ -29,13 +32,7 @@ pub struct PagePainter<'a> { pub struct State { pub last_cursor_position: Point, pub is_panning: bool, - pub hovered_info: Option, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct HoverDetailed { - pub title: String, - pub details: String, + pub hovered_info: Option, } impl Default for State { @@ -50,7 +47,7 @@ impl Default for State { #[derive(Debug, Clone)] pub enum CanvasMessage { - Hovered(Option), + Hovered(Option), ZoomChanged(f32, Point), OffsetChanged(Vector), } @@ -65,7 +62,6 @@ impl<'a> Program for PagePainter<'a> { bounds: Rectangle, cursor: Cursor, ) -> (canvas::event::Status, Option) { - // Only Overlay handles interactions if let PainterMode::Image = self.mode { return (canvas::event::Status::Ignored, None); } @@ -161,13 +157,13 @@ impl<'a> Program for PagePainter<'a> { frame.draw_image(image_rect, img); } PainterMode::Overlay => { - // Colors - let red = Color::from_rgb(1.0, 0.0, 0.0); - let blue = Color::from_rgb(0.2, 0.6, 1.0); - let green = Color::from_rgb(0.2, 1.0, 0.4); - let yellow = Color::from_rgb(1.0, 1.0, 0.0); - let purple = Color::from_rgb(0.8, 0.2, 1.0); - let path_col = Color::from_rgb(0.5, 0.5, 0.5); + // Vibrant Dracula Palette + let red = Color::from_rgb(1.0, 0.33, 0.33); // #ff5555 + let green = Color::from_rgb(0.31, 1.0, 0.44); // #50fa7b + let purple = Color::from_rgb(0.74, 0.57, 0.97); // #bd93f9 + let cyan = Color::from_rgb(0.54, 0.88, 1.0); // #8be9fd + let orange = Color::from_rgb(1.0, 0.72, 0.42); // #ffb86c + let path_col = Color::from_rgba(0.9, 0.9, 0.9, 0.2); if self.show_paths { for path in self.page.paths.as_slice() { @@ -184,7 +180,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::line(p1, p2), - Stroke::default().with_color(path_col).with_width(1.5), + Stroke::default().with_color(path_col).with_width(0.8), ); } ArchivedSegment::Rect { bbox } => { @@ -199,7 +195,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(path_col).with_width(1.5), + Stroke::default().with_color(path_col).with_width(0.8), ); } } @@ -220,7 +216,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(red).with_width(1.5), + Stroke::default().with_color(red).with_width(0.8), ); } } @@ -238,7 +234,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(blue).with_width(1.5), + Stroke::default().with_color(cyan).with_width(0.8), ); } } @@ -256,7 +252,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(green).with_width(2.0), + Stroke::default().with_color(green).with_width(1.2), ); } } @@ -274,7 +270,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(yellow).with_width(2.5), + Stroke::default().with_color(orange).with_width(1.5), ); } } @@ -292,61 +288,70 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(purple).with_width(3.0), + Stroke::default().with_color(purple).with_width(2.0), ); } } if let Some(hover) = &state.hovered_info { - let mut highlight_bbox = None; - if hover.title.starts_with("Element") { - if let Some(id_str) = hover.title.split('#').last() { - if let Ok(id) = id_str.parse::() { - if let Some(e) = self - .page - .elements - .as_slice() - .iter() - .find(|e| (e.id as usize) == id) - { - highlight_bbox = Some(&e.bbox); - } - } - } - } else if hover.title.starts_with("Block") { - if let Some(id_str) = hover.title.split('#').last() { - if let Ok(id) = id_str.parse::() { - if let Some(b) = self - .page - .blocks - .as_slice() - .iter() - .find(|b| (b.id as usize) == id) - { - highlight_bbox = Some(&b.bbox); - } - } - } - } - - if let Some(bbox) = highlight_bbox { + if let Some(bbox) = self.get_bbox_from_item(hover) { let rect = self.to_rect( - bbox.x0, - bbox.y0, - bbox.x1, - bbox.y1, + bbox[0], + bbox[1], + bbox[2], + bbox[3], final_scale, total_offset_x, total_offset_y, ); + + // Highlight fill frame.fill( &Path::rectangle(rect.position(), rect.size()), - Color::from_rgba(1.0, 1.0, 1.0, 0.4), + Color::from_rgba(1.0, 1.0, 1.0, 0.1), ); + // Highlight stroke frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(Color::WHITE).with_width(3.0), + Stroke::default().with_color(Color::WHITE).with_width(2.0), ); + + // Label etiquette + let label_text = self.get_label_from_item(hover); + if !label_text.is_empty() { + let label_bg_color = match hover { + InspectorItem::Block { .. } => purple, + InspectorItem::Element { .. } => orange, + InspectorItem::Layout { .. } => green, + _ => Color::from_rgb(0.2, 0.2, 0.2), + }; + + let label_size = 12.0; + let padding = 6.0; + let text_width = label_text.len() as f32 * 7.5; // Approximation + + // Draw etiquette box + frame.fill( + &Path::rectangle( + Point::new(rect.x, rect.y - 26.0), + [text_width + padding * 2.0, label_size + padding * 1.5].into(), + ), + label_bg_color, + ); + + // Draw text on etiquette + frame.fill_text(Text { + content: label_text, + position: Point::new(rect.x + padding, rect.y - 22.0), + color: Color::WHITE, + size: label_size.into(), + font: Font { + weight: iced::font::Weight::Bold, + ..Default::default() + }, + ..Default::default() + }); + } } } } @@ -367,7 +372,6 @@ impl<'a> PagePainter<'a> { off_x: f32, off_y: f32, ) -> Rectangle { - // Ensure x,y are top-left let x = x0.min(x1); let y = y0.min(y1); let w = (x1 - x0).abs(); @@ -381,7 +385,25 @@ impl<'a> PagePainter<'a> { } } - fn get_hover_detailed(&self, pos: Point, bounds: Rectangle) -> Option { + fn get_bbox_from_item(&self, item: &InspectorItem) -> Option<[f32; 4]> { + match item { + InspectorItem::Block { bbox, .. } => Some(*bbox), + InspectorItem::Element { bbox, .. } => Some(*bbox), + InspectorItem::Layout { bbox, .. } => Some(*bbox), + InspectorItem::None => None, + } + } + + fn get_label_from_item(&self, item: &InspectorItem) -> String { + match item { + InspectorItem::Block { kind, .. } => kind.clone(), + InspectorItem::Element { kind, .. } => kind.clone(), + InspectorItem::Layout { label, .. } => label.clone(), + InspectorItem::None => String::new(), + } + } + + fn get_hover_detailed(&self, pos: Point, bounds: Rectangle) -> Option { let fit_scale_x = bounds.width / self.page.width; let fit_scale_y = bounds.height / self.page.height; let fit_scale = fit_scale_x.min(fit_scale_y); @@ -394,6 +416,7 @@ impl<'a> PagePainter<'a> { let px = (pos.x - total_offset_x) / final_scale; let py = (pos.y - total_offset_y) / final_scale; + // Hit testing order: Blocks -> Elements -> Layout if self.show_blocks { for block in self.page.blocks.as_slice() { if px >= block.bbox.x0 @@ -401,16 +424,20 @@ impl<'a> PagePainter<'a> { && py >= block.bbox.y0 && py <= block.bbox.y1 { - return Some(HoverDetailed { - title: format!("Block #{}", block.id), - details: format!( - "BBox: [{:.1}, {:.1}, {:.1}, {:.1}]\nPages: {:?}", - block.bbox.x0, - block.bbox.y0, - block.bbox.x1, - block.bbox.y1, - block.pages_id - ), + let block_type = match &block.kind { + ArchivedBlockType::Header(_) => "Header", + ArchivedBlockType::Footer(_) => "Footer", + ArchivedBlockType::Title(_) => "Title", + ArchivedBlockType::ListBlock(_) => "List", + ArchivedBlockType::TextBlock(_) => "Text", + ArchivedBlockType::Image(_) => "Image", + ArchivedBlockType::Table(_) => "Table", + }; + return Some(InspectorItem::Block { + id: block.id as usize, + kind: block_type.to_string(), + bbox: [block.bbox.x0, block.bbox.y0, block.bbox.x1, block.bbox.y1], + pages: block.pages_id.iter().map(|&id| id as usize).collect(), }); } } @@ -423,17 +450,43 @@ impl<'a> PagePainter<'a> { && py >= element.bbox.y0 && py <= element.bbox.y1 { - return Some(HoverDetailed { - title: format!("Element #{}", element.id), - details: format!( - "BBox: [{:.1}, {:.1}, {:.1}, {:.1}]\nText: {}\nLayout ID: {}", + let elem_type = match &element.kind { + ArchivedElementType::Header => "Header", + ArchivedElementType::FootNote => "FootNote", + ArchivedElementType::Footer => "Footer", + ArchivedElementType::Text => "Text", + ArchivedElementType::Title => "Title", + ArchivedElementType::Subtitle => "Subtitle", + ArchivedElementType::ListItem => "ListItem", + ArchivedElementType::Caption => "Caption", + ArchivedElementType::Image => "Image", + ArchivedElementType::Table(_) => "Table", + }; + return Some(InspectorItem::Element { + id: element.id as usize, + kind: elem_type.to_string(), + bbox: [ element.bbox.x0, element.bbox.y0, element.bbox.x1, element.bbox.y1, - element.text_block.text.as_str(), - element.layout_block_id - ), + ], + layout_ref: element.layout_block_id, + text: element.text_block.text.to_string(), + }); + } + } + } + + if self.show_layout { + for lay in self.page.layout_bboxes.as_slice() { + if px >= lay.bbox.x0 && px <= lay.bbox.x1 && py >= lay.bbox.y0 && py <= lay.bbox.y1 + { + return Some(InspectorItem::Layout { + id: lay.id, + label: lay.label.to_string(), + proba: lay.proba, + bbox: [lay.bbox.x0, lay.bbox.y0, lay.bbox.x1, lay.bbox.y1], }); } } From 1f0640d0aefc1f4e701cfca8ab274b631c578804 Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 14:00:15 +0100 Subject: [PATCH 5/8] slider pages --- Cargo.toml | 1 - ferrules-core/src/blocks.rs | 4 +- ferrules-debug/src/inspector.rs | 401 +++++++++++++++++++------------- ferrules-debug/src/main.rs | 159 +++++++------ ferrules-debug/src/painter.rs | 220 ++++++++++-------- 5 files changed, 460 insertions(+), 325 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 079e71d..e28d5ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = ["ferrules-core", "ferrules-cli", "ferrules-api", "ferrules-debug"] resolver = "2" -edition = "2021" [workspace.dependencies] tracing = { version = "0.1.41", features = ["attributes"] } diff --git a/ferrules-core/src/blocks.rs b/ferrules-core/src/blocks.rs index 2630c1a..3991708 100644 --- a/ferrules-core/src/blocks.rs +++ b/ferrules-core/src/blocks.rs @@ -25,14 +25,14 @@ impl ImageBlock { Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, )] pub struct TextBlock { - pub(crate) text: String, + pub text: String, } #[derive( Clone, Debug, Default, Deserialize, Serialize, Archive, RkyvDeserialize, RkyvSerialize, )] pub struct List { - pub(crate) items: Vec, + pub items: Vec, } #[derive( diff --git a/ferrules-debug/src/inspector.rs b/ferrules-debug/src/inspector.rs index f0e54c2..5608f0c 100644 --- a/ferrules-debug/src/inspector.rs +++ b/ferrules-debug/src/inspector.rs @@ -1,214 +1,285 @@ use iced::font::Weight; -use iced::widget::{column, container, row, scrollable, text, Rule, Space}; -use iced::{Color, Element, Font, Length, Theme}; +use iced::widget::{button, column, container, row, scrollable, text, Space}; +use iced::{Alignment, Color, Element, Font, Length}; + +#[derive(Debug, Clone, PartialEq)] +pub struct InspectorBlock { + pub id: usize, + pub kind: String, + pub bbox: [f32; 4], + pub pages: Vec, + pub text: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct InspectorElement { + pub id: usize, + pub kind: String, + pub bbox: [f32; 4], + pub layout_ref: i32, + pub text: String, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct InspectorLayout { + pub id: i32, + pub label: String, + pub proba: f32, + pub bbox: [f32; 4], +} #[derive(Debug, Clone, PartialEq)] pub enum InspectorItem { - Block { - id: usize, - kind: String, - bbox: [f32; 4], // x0, y0, x1, y1 - pages: Vec, - }, - Element { - id: usize, - kind: String, - bbox: [f32; 4], - layout_ref: i32, - text: String, - }, - Layout { - id: i32, - label: String, - proba: f32, - bbox: [f32; 4], + Selection { + block: Option, + element: Option, + layout: Option, }, None, } -impl InspectorItem { - pub fn title(&self) -> String { - match self { - InspectorItem::Block { id, .. } => format!("Block #{}", id), - InspectorItem::Element { id, .. } => format!("Element #{}", id), - InspectorItem::Layout { id, .. } => format!("Layout #{}", id), - InspectorItem::None => "No Selection".to_string(), - } - } - - pub fn bbox(&self) -> Option<[f32; 4]> { - match self { - InspectorItem::Block { bbox, .. } => Some(*bbox), - InspectorItem::Element { bbox, .. } => Some(*bbox), - InspectorItem::Layout { bbox, .. } => Some(*bbox), - InspectorItem::None => None, - } - } - - pub fn kind_label(&self) -> String { - match self { - InspectorItem::Block { kind, .. } => kind.clone(), - InspectorItem::Element { kind, .. } => kind.clone(), - InspectorItem::Layout { label, .. } => label.clone(), - InspectorItem::None => "".to_string(), - } - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InspectorSection { + Block, + Element, + Layout, } -pub fn view_inspector<'a, Message>(item: &'a InspectorItem) -> Element<'a, Message> +pub fn view_inspector<'a, Message>( + item: &'a InspectorItem, + block_open: bool, + element_open: bool, + layout_open: bool, + on_toggle_block: Message, + on_toggle_element: Message, + on_toggle_layout: Message, +) -> Element<'a, Message> where - Message: 'a + Clone + std::fmt::Debug, + Message: 'a + Clone, { - let content: Element<'a, Message> = match item { - InspectorItem::Block { - id, - kind, - bbox, - pages, - } => column![ - header("BLOCK", *id, kind.clone()), - field("Type", kind.clone()), - bbox_field(bbox), - field("Pages", format!("{:?}", pages)), - ] - .into(), - InspectorItem::Element { - id, - kind, - bbox, - layout_ref, - text, + match item { + InspectorItem::Selection { + block, + element, + layout, } => { - column![ - header("ELEMENT", *id, kind.clone()), - field("Type", kind.clone()), - field("Layout Ref", layout_ref.to_string()), - bbox_field(bbox), - Space::with_height(10), - section_header("Content"), - container( - iced::widget::text(text.clone()) - .size(13) - .line_height(iced::widget::text::LineHeight::Relative(1.4)) - ) - .padding(8) // Apply padding to container widget - .style(|theme: &Theme| { - let palette = theme.extended_palette(); - container::Style { - background: Some(palette.background.weak.color.into()), - border: iced::Border { - color: palette.background.strong.color, - width: 1.0, - radius: 4.0.into(), - }, - ..Default::default() - } - }) - ] - .into() + let mut sections = column![].spacing(10); + + if let Some(e) = element { + sections = sections.push(render_foldable_section( + "ELEMENT", + &e.kind, + element_open, + || render_element_details(e), + on_toggle_element.clone(), + )); + } + + if let Some(b) = block { + sections = sections.push(render_foldable_section( + "BLOCK", + &b.kind, + block_open, + || render_block_details(b), + on_toggle_block.clone(), + )); + } + + if let Some(l) = layout { + sections = sections.push(render_foldable_section( + "LAYOUT", + &l.label, + layout_open, + || render_layout_details(l), + on_toggle_layout.clone(), + )); + } + + scrollable(container(sections).width(Length::Fill).padding(10)).into() } - InspectorItem::Layout { - id, - label, - proba, - bbox, - } => column![ - header("LAYOUT", *id as usize, label.clone()), - field("Label", label.clone()), - field("Confidence", format!("{:.2}%", proba * 100.0)), - bbox_field(bbox), - ] + InspectorItem::None => container( + text("No selection") + .size(14) + .color(Color::from_rgb(0.5, 0.5, 0.6)), + ) + .width(Length::Fill) + .center_x(Length::Fill) + .padding(20) .into(), - InspectorItem::None => column![text("No Selection") - .size(14) - .color(Color::from_rgb(0.6, 0.6, 0.6))] - .align_x(iced::alignment::Horizontal::Center) - .into(), - }; - - scrollable(container(content).width(Length::Fill).padding(15)).into() + } } -fn header<'a, Message>(type_name: &'a str, id: usize, subtitle: String) -> Element<'a, Message> +fn render_foldable_section<'a, Message>( + title: &'a str, + subtitle: &'a str, + is_open: bool, + content_fn: impl Fn() -> Element<'a, Message>, + on_toggle: Message, +) -> Element<'a, Message> where - Message: 'a + Clone + std::fmt::Debug, + Message: 'a + Clone, { - column![ + let header = button( row![ - text(type_name) - .size(12) + text(if is_open { "▼" } else { "▶" }) + .size(10) + .font(Font::MONOSPACE), + text(title) + .size(11) .font(Font { weight: Weight::Bold, ..Default::default() }) - .color(Color::from_rgb(0.4, 0.4, 1.0)), - text(format!("#{}", id)) - .size(12) - .color(Color::from_rgb(0.6, 0.6, 0.6)), + .color(Color::from_rgb(0.7, 0.7, 0.9)), + text(subtitle) + .size(11) + .color(Color::from_rgb(0.5, 0.5, 0.6)), ] - .spacing(5), - text(subtitle).size(20).font(Font { - weight: Weight::Semibold, - ..Default::default() - }), - Rule::horizontal(1), + .spacing(10) + .align_y(Alignment::Center), + ) + .on_press(on_toggle) + .style(button::text) + .padding(5) + .width(Length::Fill); + + let mut col = column![header].spacing(5); + if is_open { + col = col.push( + container(content_fn()) + .padding([5, 15]) + .style(|_| container::Style { + border: iced::Border { + width: 0.5, + color: Color::from_rgba(1.0, 1.0, 1.0, 0.1), + radius: 4.0.into(), + }, + ..Default::default() + }), + ); + } + + col.into() +} + +fn render_block_details<'a, Message: 'a>(b: &InspectorBlock) -> Element<'a, Message> { + let mut col = column![ + field::("Type", b.kind.clone()), + bbox_field::(&b.bbox), + field::("Pages", format!("{:?}", b.pages)), ] - .spacing(5) - .into() + .spacing(8); + + if !b.text.is_empty() { + col = col.push( + column![ + Space::with_height(5), + section_header::("Text Content"), + render_text_box::(&b.text) + ] + .spacing(5), + ); + } + + col.into() } -fn field<'a, Message>(key: &'a str, value: String) -> Element<'a, Message> -where - Message: 'a + Clone + std::fmt::Debug, -{ +fn render_element_details<'a, Message: 'a>(e: &InspectorElement) -> Element<'a, Message> { + let mut col = column![ + field::("Type", e.kind.clone()), + field::("Layout Ref", e.layout_ref.to_string()), + bbox_field::(&e.bbox), + ] + .spacing(8); + + if !e.text.is_empty() { + col = col.push( + column![ + Space::with_height(5), + section_header::("Text Content"), + render_text_box::(&e.text) + ] + .spacing(5), + ); + } + + col.into() +} + +fn render_layout_details<'a, Message: 'a>(l: &InspectorLayout) -> Element<'a, Message> { column![ - text(key).size(11).color(Color::from_rgb(0.5, 0.5, 0.5)), - text(value).size(14), + field::("Label", l.label.clone()), + field::("Confidence", format!("{:.2}%", l.proba * 100.0)), + bbox_field::(&l.bbox), ] + .spacing(8) .into() } -fn bbox_field<'a, Message>(bbox: &[f32; 4]) -> Element<'a, Message> -where - Message: 'a + Clone + std::fmt::Debug, -{ - let [x0, y0, x1, y1] = bbox; - let w = x1 - x0; - let h = y1 - y0; +fn render_text_box<'a, Message: 'a>(content: &str) -> Element<'a, Message> { + container( + text(content.to_string()) + .size(12) + .line_height(iced::widget::text::LineHeight::Relative(1.4)), + ) + .padding(8) + .style(|_| container::Style { + background: Some(Color::from_rgba(1.0, 1.0, 1.0, 0.03).into()), + border: iced::Border { + color: Color::from_rgba(1.0, 1.0, 1.0, 0.1), + width: 1.0, + radius: 4.0.into(), + }, + ..Default::default() + }) + .into() +} - column![ - text("Geometry") - .size(11) - .color(Color::from_rgb(0.5, 0.5, 0.5)), - row![kv_small("X", *x0), kv_small("Y", *y0),].spacing(15), - row![kv_small("W", w), kv_small("H", h),].spacing(15), +fn field<'a, Message: 'a>(key: &'a str, value: String) -> Element<'a, Message> { + row![ + text(format!("{}:", key)) + .size(10) + .color(Color::from_rgb(0.5, 0.5, 0.6)) + .font(Font { + weight: Weight::Bold, + ..Default::default() + }), + text(value).size(11), ] - .spacing(5) + .spacing(10) .into() } -fn kv_small<'a, Message>(k: &'a str, v: f32) -> Element<'a, Message> -where - Message: 'a + Clone + std::fmt::Debug, -{ - row![ - text(k).size(11).color(Color::from_rgb(0.5, 0.5, 0.5)), - text(format!("{:.1}", v)).size(12).font(Font::MONOSPACE), +fn bbox_field<'a, Message: 'a>(bbox: &[f32; 4]) -> Element<'a, Message> { + let [x0, y0, x1, y1] = bbox; + let w = (x1 - x0).abs(); + let h = (y1 - y0).abs(); + + column![ + text("Geometry") + .size(10) + .color(Color::from_rgb(0.5, 0.5, 0.6)) + .font(Font { + weight: Weight::Bold, + ..Default::default() + }), + row![ + text(format!("X:{:.1} Y:{:.1} W:{:.1} H:{:.1}", x0, y0, w, h)) + .size(11) + .font(Font::MONOSPACE) + ] ] - .spacing(5) + .spacing(2) .into() } -fn section_header<'a, Message>(title: &'a str) -> Element<'a, Message> -where - Message: 'a + Clone + std::fmt::Debug, -{ +fn section_header<'a, Message: 'a>(title: &'a str) -> Element<'a, Message> { text(title) - .size(12) + .size(10) .font(Font { weight: Weight::Bold, ..Default::default() }) - .color(Color::from_rgb(0.7, 0.7, 0.7)) + .color(Color::from_rgb(0.6, 0.6, 0.7)) .into() } diff --git a/ferrules-debug/src/main.rs b/ferrules-debug/src/main.rs index 500333c..e369ebe 100644 --- a/ferrules-debug/src/main.rs +++ b/ferrules-debug/src/main.rs @@ -1,10 +1,9 @@ use clap::Parser; use ferrules_core::debug_info::DebugDocument; use iced::font::Weight; -use iced::widget::text::LineHeight; use iced::widget::{ - button, canvas, checkbox, column, container, horizontal_space, image, row, scrollable, slider, - text, vertical_space, Space, Tooltip, + button, canvas, checkbox, column, container, horizontal_space, image, row, slider, text, Space, + Tooltip, }; use iced::{event, window, Alignment, Color, Element, Event, Font, Length, Task, Theme, Vector}; use memmap2::Mmap; @@ -13,7 +12,7 @@ use std::path::PathBuf; mod inspector; mod painter; -use inspector::{view_inspector, InspectorItem}; +use inspector::{view_inspector, InspectorItem, InspectorSection}; use painter::{CanvasMessage, PagePainter, PainterMode}; #[derive(Parser, Debug)] @@ -28,20 +27,21 @@ pub fn main() -> iced::Result { tracing_subscriber::fmt::init(); let args = Args::parse(); iced::application("Ferrules Debug", FerrulesDebug::update, FerrulesDebug::view) - .theme(|_| Theme::Dracula) + .theme(|_| Theme::CatppuccinMocha) .subscription(FerrulesDebug::subscription) .run_with(move || FerrulesDebug::new(args.file)) } -const SIDEBAR_COLOR: Color = Color::from_rgb(0.15, 0.16, 0.21); // #282a36 (Dracula Background) -const TOPBAR_COLOR: Color = Color::from_rgb(0.26, 0.28, 0.35); // #44475a (Dracula Current Line) -const ACCENT_COLOR: Color = Color::from_rgb(0.74, 0.57, 0.97); // #bd93f9 (Dracula Purple) +// Catppuccin Mocha Palette +const MOCHA_BASE: Color = Color::from_rgb(0.117, 0.117, 0.180); // #1e1e2e +const MOCHA_SURFACE0: Color = Color::from_rgb(0.192, 0.196, 0.266); // #313244 +const MOCHA_SURFACE1: Color = Color::from_rgb(0.270, 0.278, 0.352); // #45475a +const MOCHA_LAVENDER: Color = Color::from_rgb(0.705, 0.745, 0.996); // #b4befe (Accent) +const MOCHA_TEXT: Color = Color::from_rgb(0.803, 0.839, 0.956); // #cdd6f4 struct FerrulesDebug { mmap: Option, current_page_idx: usize, - cached_page_image_handle: Option, - last_page_idx_cached: Option, zoom: f32, offset: Vector, @@ -54,6 +54,11 @@ struct FerrulesDebug { hovered_info: InspectorItem, sidebar_open: bool, + + // Inspector Fold States + inspector_block_open: bool, + inspector_element_open: bool, + inspector_layout_open: bool, } #[derive(Debug, Clone)] @@ -67,6 +72,7 @@ enum Message { ZoomSliderChanged(f32), ResetView, ToggleSidebar, + ToggleInspectorSection(InspectorSection), } #[derive(Debug, Clone)] @@ -85,8 +91,6 @@ impl FerrulesDebug { Self { mmap: None, current_page_idx: 0, - cached_page_image_handle: None, - last_page_idx_cached: None, zoom: 1.0, offset: Vector::new(0.0, 0.0), show_native: true, @@ -97,6 +101,9 @@ impl FerrulesDebug { show_paths: true, hovered_info: InspectorItem::None, sidebar_open: true, + inspector_block_open: true, + inspector_element_open: true, + inspector_layout_open: false, }, if let Some(path) = file_path { Task::done(Message::FileSelected(Some(path))) @@ -175,6 +182,20 @@ impl FerrulesDebug { self.sidebar_open = !self.sidebar_open; Task::none() } + Message::ToggleInspectorSection(section) => { + match section { + InspectorSection::Block => { + self.inspector_block_open = !self.inspector_block_open + } + InspectorSection::Element => { + self.inspector_element_open = !self.inspector_element_open + } + InspectorSection::Layout => { + self.inspector_layout_open = !self.inspector_layout_open + } + } + Task::none() + } } } @@ -195,6 +216,8 @@ impl FerrulesDebug { ..Default::default() }; + let logo_path = "/Users/amine/coding/ferrules/imgs/ferrules-logo.png"; + if let Some(mmap) = &self.mmap { let archived = unsafe { archived_root::(&mmap[..]) }; let current_page_idx = self @@ -210,10 +233,14 @@ impl FerrulesDebug { let left_sidebar_content = if self.sidebar_open { column![ row![ - text("FERRULES") - .size(20) - .font(bold_font) - .color(ACCENT_COLOR), + button( + image(logo_path) + .width(Length::Fixed(32.0)) + .height(Length::Fixed(32.0)) + ) + .on_press(Message::ToggleSidebar) + .padding(0) + .style(button::text), horizontal_space(), button(text("‹").size(16).font(bold_font)) .on_press(Message::ToggleSidebar) @@ -225,10 +252,10 @@ impl FerrulesDebug { Space::with_height(10), text(archived.name.as_str()) .size(13) - .color(Color::from_rgb(0.6, 0.6, 0.7)) + .color(MOCHA_TEXT) .font(medium_font), Space::with_height(30), - button(text("Open Archive").size(14).font(medium_font)) + button(text("Open .ferr").size(14).font(medium_font)) .on_press(Message::OpenFile) .padding(10) .width(Length::Fill), @@ -242,8 +269,8 @@ impl FerrulesDebug { "Native Lines", self.show_native, Layer::Native, - Color::from_rgb(1.0, 0.33, 0.33) - ), + Color::from_rgb(0.95, 0.54, 0.65) + ), // Mocha Red self.layer_checkbox( "Vector Paths", self.show_paths, @@ -254,26 +281,26 @@ impl FerrulesDebug { "Layout Analysis", self.show_layout, Layer::Layout, - Color::from_rgb(0.31, 1.0, 0.44) - ), + Color::from_rgb(0.65, 0.89, 0.63) + ), // Mocha Green self.layer_checkbox( "OCR Text Lines", self.show_ocr, Layer::OCR, - Color::from_rgb(0.54, 0.88, 1.0) - ), + Color::from_rgb(0.53, 0.70, 0.98) + ), // Mocha Blue self.layer_checkbox( "Logical Elements", self.show_elements, Layer::Elements, - Color::from_rgb(1.0, 0.72, 0.42) - ), + Color::from_rgb(0.98, 0.70, 0.52) + ), // Mocha Peach self.layer_checkbox( "Structural Blocks", self.show_blocks, Layer::Blocks, - Color::from_rgb(0.74, 0.57, 0.97) - ), + Color::from_rgb(0.79, 0.65, 0.96) + ), // Mocha Mauve ] .spacing(14), ] @@ -281,10 +308,14 @@ impl FerrulesDebug { .padding(20) } else { column![ - button(text("›").size(16).font(bold_font)) - .on_press(Message::ToggleSidebar) - .padding(5) - .style(button::text), + button( + image(logo_path) + .width(Length::Fixed(32.0)) + .height(Length::Fixed(32.0)) + ) + .on_press(Message::ToggleSidebar) + .padding(0) + .style(button::text), Space::with_height(30), Tooltip::new( button(text("📂").size(18)) @@ -308,7 +339,7 @@ impl FerrulesDebug { }) .height(Length::Fill) .style(move |_| container::Style { - background: Some(SIDEBAR_COLOR.into()), + background: Some(MOCHA_SURFACE0.into()), border: iced::Border { width: 0.0, color: Color::TRANSPARENT, @@ -317,36 +348,29 @@ impl FerrulesDebug { ..Default::default() }); - // --- TOP BAR (Modern Compact Nav) --- + // --- TOP BAR --- let top_bar = container( row![ horizontal_space(), container( row![ - button(text("←").size(14).font(bold_font)) - .on_press(Message::PageChanged( - current_page_idx.saturating_sub(1) as u32 - )) - .padding(6) - .style(button::text), - text(format!("{} / {}", current_page_idx + 1, total_pages)) + text(format!("Page {} / {}", current_page_idx + 1, total_pages)) .size(14) .font(bold_font), - button(text("→").size(14).font(bold_font)) - .on_press(Message::PageChanged( - (current_page_idx + 1) - .min(total_pages.saturating_sub(1) as usize) - as u32 - )) - .padding(6) - .style(button::text), + slider( + 0..=total_pages.saturating_sub(1), + current_page_idx as u32, + Message::PageChanged, + ) + .width(Length::Fixed(300.0)) + .step(1u32), ] .spacing(15) .align_y(Alignment::Center) ) .padding([0, 20]) .style(move |_| container::Style { - background: Some(SIDEBAR_COLOR.into()), + background: Some(MOCHA_SURFACE0.into()), border: iced::Border { radius: 20.0.into(), ..Default::default() @@ -358,7 +382,7 @@ impl FerrulesDebug { text(format!("{:3.0}%", self.zoom * 100.0)) .size(13) .font(bold_font) - .color(Color::from_rgb(0.6, 0.6, 0.7)), + .color(MOCHA_TEXT), slider(0.1..=5.0, self.zoom, Message::ZoomSliderChanged) .step(0.1) .width(Length::Fixed(120.0)), @@ -375,7 +399,7 @@ impl FerrulesDebug { .padding(12), ) .style(move |_| container::Style { - background: Some(TOPBAR_COLOR.into()), + background: Some(MOCHA_SURFACE1.into()), ..Default::default() }); @@ -387,14 +411,22 @@ impl FerrulesDebug { .color(Color::from_rgb(0.5, 0.5, 0.6)) .font(bold_font), Space::with_height(10), - view_inspector(&self.hovered_info) + view_inspector( + &self.hovered_info, + self.inspector_block_open, + self.inspector_element_open, + self.inspector_layout_open, + Message::ToggleInspectorSection(InspectorSection::Block), + Message::ToggleInspectorSection(InspectorSection::Element), + Message::ToggleInspectorSection(InspectorSection::Layout), + ) ] - .padding(20), + .padding(10), ) .width(Length::Fixed(320.0)) .height(Length::Fill) .style(move |_| container::Style { - background: Some(SIDEBAR_COLOR.into()), + background: Some(MOCHA_SURFACE0.into()), ..Default::default() }); @@ -444,7 +476,7 @@ impl FerrulesDebug { .width(Length::Fill) .height(Length::Fill) .style(|_| container::Style { - background: Some(Color::from_rgb(0.05, 0.05, 0.07).into()), + background: Some(MOCHA_BASE.into()), ..Default::default() }); @@ -456,19 +488,14 @@ impl FerrulesDebug { ] .into() } else { - let extra_bold_font = Font { - weight: Weight::ExtraBold, - ..Default::default() - }; container( column![ - text("FERRULES") - .size(80) - .font(extra_bold_font) - .color(ACCENT_COLOR), + image(logo_path) + .width(Length::Fixed(120.0)) + .height(Length::Fixed(120.0)), text("DOCUMENT ANALYSIS TOOLKIT") .size(18) - .color(Color::from_rgb(0.5, 0.5, 0.6)) + .color(MOCHA_LAVENDER) .font(bold_font), Space::with_height(40), button(text("Select .ferr File").size(18).font(bold_font)) @@ -477,7 +504,7 @@ impl FerrulesDebug { .style(button::primary), text("Or drop archive here") .size(14) - .color(Color::from_rgb(0.4, 0.4, 0.5)) + .color(MOCHA_TEXT) .font(medium_font), ] .align_x(Alignment::Center) diff --git a/ferrules-debug/src/painter.rs b/ferrules-debug/src/painter.rs index 98eed3d..161adcd 100644 --- a/ferrules-debug/src/painter.rs +++ b/ferrules-debug/src/painter.rs @@ -1,4 +1,4 @@ -use crate::inspector::InspectorItem; +use crate::inspector::{InspectorBlock, InspectorElement, InspectorItem, InspectorLayout}; use ferrules_core::blocks::ArchivedBlockType; use ferrules_core::debug_info::ArchivedDebugPage; use ferrules_core::entities::{ArchivedElementType, ArchivedSegment}; @@ -157,13 +157,19 @@ impl<'a> Program for PagePainter<'a> { frame.draw_image(image_rect, img); } PainterMode::Overlay => { - // Vibrant Dracula Palette - let red = Color::from_rgb(1.0, 0.33, 0.33); // #ff5555 - let green = Color::from_rgb(0.31, 1.0, 0.44); // #50fa7b - let purple = Color::from_rgb(0.74, 0.57, 0.97); // #bd93f9 - let cyan = Color::from_rgb(0.54, 0.88, 1.0); // #8be9fd - let orange = Color::from_rgb(1.0, 0.72, 0.42); // #ffb86c - let path_col = Color::from_rgba(0.9, 0.9, 0.9, 0.2); + // Catppuccin Mocha Palette + let red = Color::from_rgb(0.95, 0.54, 0.65); // #f38ba8 + let green = Color::from_rgb(0.65, 0.89, 0.63); // #a6e3a1 + let mauve = Color::from_rgb(0.79, 0.65, 0.96); // #cba6f7 + let blue = Color::from_rgb(0.53, 0.70, 0.98); // #89b4fa + let peach = Color::from_rgb(0.98, 0.70, 0.52); // #fab387 + let surface0 = Color::from_rgb(0.19, 0.19, 0.26); // #313244 + let overlay0_faded = Color { + r: 0.43, + g: 0.45, + b: 0.56, + a: 0.3, + }; // #6c7086 if self.show_paths { for path in self.page.paths.as_slice() { @@ -180,7 +186,9 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::line(p1, p2), - Stroke::default().with_color(path_col).with_width(0.8), + Stroke::default() + .with_color(overlay0_faded) + .with_width(0.8), ); } ArchivedSegment::Rect { bbox } => { @@ -195,7 +203,9 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(path_col).with_width(0.8), + Stroke::default() + .with_color(overlay0_faded) + .with_width(0.8), ); } } @@ -234,7 +244,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(cyan).with_width(0.8), + Stroke::default().with_color(blue).with_width(0.8), ); } } @@ -270,7 +280,7 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(orange).with_width(1.5), + Stroke::default().with_color(peach).with_width(1.5), ); } } @@ -288,69 +298,92 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(purple).with_width(2.0), + Stroke::default().with_color(mauve).with_width(2.0), ); } } - if let Some(hover) = &state.hovered_info { - if let Some(bbox) = self.get_bbox_from_item(hover) { - let rect = self.to_rect( - bbox[0], - bbox[1], - bbox[2], - bbox[3], - final_scale, - total_offset_x, - total_offset_y, - ); - - // Highlight fill - frame.fill( - &Path::rectangle(rect.position(), rect.size()), - Color::from_rgba(1.0, 1.0, 1.0, 0.1), - ); - // Highlight stroke - frame.stroke( - &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(Color::WHITE).with_width(2.0), - ); - - // Label etiquette - let label_text = self.get_label_from_item(hover); - if !label_text.is_empty() { - let label_bg_color = match hover { - InspectorItem::Block { .. } => purple, - InspectorItem::Element { .. } => orange, - InspectorItem::Layout { .. } => green, - _ => Color::from_rgb(0.2, 0.2, 0.2), - }; - - let label_size = 12.0; - let padding = 6.0; - let text_width = label_text.len() as f32 * 7.5; // Approximation + if let Some(selection) = &state.hovered_info { + if let InspectorItem::Selection { + block, + element, + layout, + } = selection + { + // Choose a primary bbox for highlight and label (priority to Element -> Block -> Layout) + let primary_bbox = element + .as_ref() + .map(|e| e.bbox) + .or_else(|| block.as_ref().map(|b| b.bbox)) + .or_else(|| layout.as_ref().map(|l| l.bbox)); + + if let Some(bbox) = primary_bbox { + let rect = self.to_rect( + bbox[0], + bbox[1], + bbox[2], + bbox[3], + final_scale, + total_offset_x, + total_offset_y, + ); - // Draw etiquette box frame.fill( - &Path::rectangle( - Point::new(rect.x, rect.y - 26.0), - [text_width + padding * 2.0, label_size + padding * 1.5].into(), - ), - label_bg_color, + &Path::rectangle(rect.position(), rect.size()), + Color::from_rgba(1.0, 1.0, 1.0, 0.1), + ); + frame.stroke( + &Path::rectangle(rect.position(), rect.size()), + Stroke::default().with_color(Color::WHITE).with_width(2.0), ); - // Draw text on etiquette - frame.fill_text(Text { - content: label_text, - position: Point::new(rect.x + padding, rect.y - 22.0), - color: Color::WHITE, - size: label_size.into(), - font: Font { - weight: iced::font::Weight::Bold, + // Label etiquette + let label_text = if let Some(e) = element { + e.kind.clone() + } else if let Some(b) = block { + b.kind.clone() + } else if let Some(l) = layout { + l.label.clone() + } else { + String::new() + }; + + if !label_text.is_empty() { + let label_bg_color = if element.is_some() { + peach + } else if block.is_some() { + mauve + } else if layout.is_some() { + green + } else { + surface0 + }; + + let label_size = 12.0; + let padding = 6.0; + let text_width = label_text.len() as f32 * 7.5; + + frame.fill( + &Path::rectangle( + Point::new(rect.x, rect.y - 26.0), + [text_width + padding * 2.0, label_size + padding * 1.5] + .into(), + ), + label_bg_color, + ); + + frame.fill_text(Text { + content: label_text, + position: Point::new(rect.x + padding, rect.y - 22.0), + color: Color::WHITE, + size: label_size.into(), + font: Font { + weight: iced::font::Weight::Bold, + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }); + }); + } } } } @@ -385,24 +418,6 @@ impl<'a> PagePainter<'a> { } } - fn get_bbox_from_item(&self, item: &InspectorItem) -> Option<[f32; 4]> { - match item { - InspectorItem::Block { bbox, .. } => Some(*bbox), - InspectorItem::Element { bbox, .. } => Some(*bbox), - InspectorItem::Layout { bbox, .. } => Some(*bbox), - InspectorItem::None => None, - } - } - - fn get_label_from_item(&self, item: &InspectorItem) -> String { - match item { - InspectorItem::Block { kind, .. } => kind.clone(), - InspectorItem::Element { kind, .. } => kind.clone(), - InspectorItem::Layout { label, .. } => label.clone(), - InspectorItem::None => String::new(), - } - } - fn get_hover_detailed(&self, pos: Point, bounds: Rectangle) -> Option { let fit_scale_x = bounds.width / self.page.width; let fit_scale_y = bounds.height / self.page.height; @@ -416,7 +431,10 @@ impl<'a> PagePainter<'a> { let px = (pos.x - total_offset_x) / final_scale; let py = (pos.y - total_offset_y) / final_scale; - // Hit testing order: Blocks -> Elements -> Layout + let mut hovered_block = None; + let mut hovered_element = None; + let mut hovered_layout = None; + if self.show_blocks { for block in self.page.blocks.as_slice() { if px >= block.bbox.x0 @@ -424,7 +442,15 @@ impl<'a> PagePainter<'a> { && py >= block.bbox.y0 && py <= block.bbox.y1 { - let block_type = match &block.kind { + let block_text = match &block.kind { + ArchivedBlockType::TextBlock(t) => t.text.to_string(), + ArchivedBlockType::Header(h) => h.text.to_string(), + ArchivedBlockType::Footer(f) => f.text.to_string(), + ArchivedBlockType::Title(t) => t.text.to_string(), + ArchivedBlockType::ListBlock(l) => l.items.join("\n"), + _ => String::new(), + }; + let block_kind = match &block.kind { ArchivedBlockType::Header(_) => "Header", ArchivedBlockType::Footer(_) => "Footer", ArchivedBlockType::Title(_) => "Title", @@ -433,12 +459,14 @@ impl<'a> PagePainter<'a> { ArchivedBlockType::Image(_) => "Image", ArchivedBlockType::Table(_) => "Table", }; - return Some(InspectorItem::Block { + hovered_block = Some(InspectorBlock { id: block.id as usize, - kind: block_type.to_string(), + kind: block_kind.to_string(), bbox: [block.bbox.x0, block.bbox.y0, block.bbox.x1, block.bbox.y1], pages: block.pages_id.iter().map(|&id| id as usize).collect(), + text: block_text, }); + break; } } } @@ -462,7 +490,7 @@ impl<'a> PagePainter<'a> { ArchivedElementType::Image => "Image", ArchivedElementType::Table(_) => "Table", }; - return Some(InspectorItem::Element { + hovered_element = Some(InspectorElement { id: element.id as usize, kind: elem_type.to_string(), bbox: [ @@ -474,6 +502,7 @@ impl<'a> PagePainter<'a> { layout_ref: element.layout_block_id, text: element.text_block.text.to_string(), }); + break; } } } @@ -482,16 +511,25 @@ impl<'a> PagePainter<'a> { for lay in self.page.layout_bboxes.as_slice() { if px >= lay.bbox.x0 && px <= lay.bbox.x1 && py >= lay.bbox.y0 && py <= lay.bbox.y1 { - return Some(InspectorItem::Layout { + hovered_layout = Some(InspectorLayout { id: lay.id, label: lay.label.to_string(), proba: lay.proba, bbox: [lay.bbox.x0, lay.bbox.y0, lay.bbox.x1, lay.bbox.y1], }); + break; } } } - None + if hovered_block.is_some() || hovered_element.is_some() || hovered_layout.is_some() { + Some(InspectorItem::Selection { + block: hovered_block, + element: hovered_element, + layout: hovered_layout, + }) + } else { + None + } } } From 0ffc3dbbe721f93ebba3c277850e2b5e35d5d9b9 Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 15:30:50 +0100 Subject: [PATCH 6/8] selected --- ferrules-debug/src/main.rs | 27 ++++++++++++++++++++++++++- ferrules-debug/src/painter.rs | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/ferrules-debug/src/main.rs b/ferrules-debug/src/main.rs index e369ebe..1821926 100644 --- a/ferrules-debug/src/main.rs +++ b/ferrules-debug/src/main.rs @@ -53,6 +53,7 @@ struct FerrulesDebug { show_paths: bool, hovered_info: InspectorItem, + selected_info: Option, sidebar_open: bool, // Inspector Fold States @@ -100,6 +101,7 @@ impl FerrulesDebug { show_blocks: true, show_paths: true, hovered_info: InspectorItem::None, + selected_info: None, sidebar_open: true, inspector_block_open: true, inspector_element_open: true, @@ -156,6 +158,27 @@ impl FerrulesDebug { self.hovered_info = info.unwrap_or(InspectorItem::None); Task::none() } + CanvasMessage::Clicked(info) => { + if let Some(clicked_item) = info { + // If we clicked the same thing that is already selected, deselect it. + // Otherwise, select the new thing. + if let Some(current) = &self.selected_info { + // Simple equality check might depend on PartialEq implementation of InspectorItem + // which we derived. + if current == &clicked_item { + self.selected_info = None; + } else { + self.selected_info = Some(clicked_item); + } + } else { + self.selected_info = Some(clicked_item); + } + } else { + // Clicked on nothing -> deselect + self.selected_info = None; + } + Task::none() + } CanvasMessage::ZoomChanged(factor, pos) => { let prev_zoom = self.zoom; self.zoom = (self.zoom + factor).max(0.1).min(10.0); @@ -412,7 +435,7 @@ impl FerrulesDebug { .font(bold_font), Space::with_height(10), view_inspector( - &self.hovered_info, + self.selected_info.as_ref().unwrap_or(&self.hovered_info), self.inspector_block_open, self.inspector_element_open, self.inspector_layout_open, @@ -443,6 +466,7 @@ impl FerrulesDebug { show_elements: self.show_elements, show_blocks: self.show_blocks, show_paths: self.show_paths, + selected_item: self.selected_info.clone(), }; let overlay_painter = PagePainter { @@ -457,6 +481,7 @@ impl FerrulesDebug { show_elements: self.show_elements, show_blocks: self.show_blocks, show_paths: self.show_paths, + selected_item: self.selected_info.clone(), }; let canvas_view = container(iced::widget::stack(vec![ diff --git a/ferrules-debug/src/painter.rs b/ferrules-debug/src/painter.rs index 161adcd..abd186f 100644 --- a/ferrules-debug/src/painter.rs +++ b/ferrules-debug/src/painter.rs @@ -27,11 +27,15 @@ pub struct PagePainter<'a> { pub show_elements: bool, pub show_blocks: bool, pub show_paths: bool, + + // Selection + pub selected_item: Option, } pub struct State { pub last_cursor_position: Point, pub is_panning: bool, + pub start_drag_position: Option, pub hovered_info: Option, } @@ -40,6 +44,7 @@ impl Default for State { Self { last_cursor_position: Point::ORIGIN, is_panning: false, + start_drag_position: None, hovered_info: None, } } @@ -48,6 +53,7 @@ impl Default for State { #[derive(Debug, Clone)] pub enum CanvasMessage { Hovered(Option), + Clicked(Option), ZoomChanged(f32, Point), OffsetChanged(Vector), } @@ -86,12 +92,27 @@ impl<'a> Program for PagePainter<'a> { if let Some(pos) = cursor_position { state.is_panning = true; state.last_cursor_position = pos; + state.start_drag_position = Some(pos); return (canvas::event::Status::Captured, None); } } mouse::Event::ButtonReleased(mouse::Button::Left) => { - state.is_panning = false; - return (canvas::event::Status::Captured, None); + let mut message = None; + if state.is_panning { + state.is_panning = false; + if let Some(start_pos) = state.start_drag_position { + if let Some(current_pos) = cursor_position { + let dist = start_pos.distance(current_pos); + if dist < 5.0 { + // It's a click! + let item = self.get_hover_detailed(current_pos, bounds); + message = Some(CanvasMessage::Clicked(item)); + } + } + } + } + state.start_drag_position = None; + return (canvas::event::Status::Captured, message); } mouse::Event::CursorMoved { .. } => { if let Some(pos) = cursor_position { @@ -303,7 +324,9 @@ impl<'a> Program for PagePainter<'a> { } } - if let Some(selection) = &state.hovered_info { + let active_selection = self.selected_item.as_ref().or(state.hovered_info.as_ref()); + + if let Some(selection) = active_selection { if let InspectorItem::Selection { block, element, @@ -330,11 +353,13 @@ impl<'a> Program for PagePainter<'a> { frame.fill( &Path::rectangle(rect.position(), rect.size()), - Color::from_rgba(1.0, 1.0, 1.0, 0.1), + Color::from_rgba(1.0, 0.9, 0.2, 0.2), ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(Color::WHITE).with_width(2.0), + Stroke::default() + .with_color(Color::from_rgb(1.0, 0.9, 0.2)) + .with_width(4.0), ); // Label etiquette From 6df47b9e8ba5cd55ae0db1d860eb72884dd4d5ec Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 16:04:55 +0100 Subject: [PATCH 7/8] debugger --- ferrules-debug/src/inspector.rs | 128 +++++++++------------- ferrules-debug/src/main.rs | 182 ++++++++++++++------------------ ferrules-debug/src/painter.rs | 51 +++++---- ferrules-debug/src/theme.rs | 67 ++++++++++++ ferrules-debug/src/widgets.rs | 73 +++++++++++++ 5 files changed, 293 insertions(+), 208 deletions(-) create mode 100644 ferrules-debug/src/theme.rs create mode 100644 ferrules-debug/src/widgets.rs diff --git a/ferrules-debug/src/inspector.rs b/ferrules-debug/src/inspector.rs index 5608f0c..97aeb35 100644 --- a/ferrules-debug/src/inspector.rs +++ b/ferrules-debug/src/inspector.rs @@ -1,6 +1,7 @@ -use iced::font::Weight; -use iced::widget::{button, column, container, row, scrollable, text, Space}; -use iced::{Alignment, Color, Element, Font, Length}; +use crate::theme; +use crate::widgets; +use iced::widget::{button, column, container, row, scrollable, text}; +use iced::{Alignment, Color, Element, Length}; #[derive(Debug, Clone, PartialEq)] pub struct InspectorBlock { @@ -63,7 +64,7 @@ where element, layout, } => { - let mut sections = column![].spacing(10); + let mut sections = column![].spacing(theme::SPACING_MD); if let Some(e) = element { sections = sections.push(render_foldable_section( @@ -95,16 +96,21 @@ where )); } - scrollable(container(sections).width(Length::Fill).padding(10)).into() + scrollable( + container(sections) + .width(Length::Fill) + .padding(theme::PADDING_MD), + ) + .into() } InspectorItem::None => container( text("No selection") - .size(14) - .color(Color::from_rgb(0.5, 0.5, 0.6)), + .size(theme::TEXT_SIZE_LG) + .color(theme::SUBTEXT0), ) .width(Length::Fill) .center_x(Length::Fill) - .padding(20) + .padding(theme::PADDING_LG) .into(), } } @@ -122,37 +128,34 @@ where let header = button( row![ text(if is_open { "▼" } else { "▶" }) - .size(10) - .font(Font::MONOSPACE), + .size(theme::TEXT_SIZE_SM) + .font(theme::FONT_MONOSPACE), text(title) - .size(11) - .font(Font { - weight: Weight::Bold, - ..Default::default() - }) - .color(Color::from_rgb(0.7, 0.7, 0.9)), + .size(theme::TEXT_SIZE_SM) + .font(theme::FONT_BOLD) + .color(theme::LAVENDER), text(subtitle) - .size(11) - .color(Color::from_rgb(0.5, 0.5, 0.6)), + .size(theme::TEXT_SIZE_SM) + .color(theme::SUBTEXT0), ] - .spacing(10) + .spacing(theme::SPACING_MD) .align_y(Alignment::Center), ) .on_press(on_toggle) .style(button::text) - .padding(5) + .padding(theme::PADDING_SM) .width(Length::Fill); - let mut col = column![header].spacing(5); + let mut col = column![header].spacing(theme::SPACING_SM); if is_open { col = col.push( container(content_fn()) - .padding([5, 15]) + .padding([theme::PADDING_SM, theme::PADDING_LG]) .style(|_| container::Style { border: iced::Border { width: 0.5, - color: Color::from_rgba(1.0, 1.0, 1.0, 0.1), - radius: 4.0.into(), + color: theme::SURFACE1, + radius: theme::BORDER_RADIUS_SM.into(), }, ..Default::default() }), @@ -164,20 +167,20 @@ where fn render_block_details<'a, Message: 'a>(b: &InspectorBlock) -> Element<'a, Message> { let mut col = column![ - field::("Type", b.kind.clone()), + widgets::field::("Type", b.kind.clone()), bbox_field::(&b.bbox), - field::("Pages", format!("{:?}", b.pages)), + widgets::field::("Pages", format!("{:?}", b.pages)), ] - .spacing(8); + .spacing(theme::SPACING_MD); if !b.text.is_empty() { col = col.push( column![ - Space::with_height(5), - section_header::("Text Content"), + widgets::v_space(theme::SPACING_SM), + widgets::section_header("Text Content"), render_text_box::(&b.text) ] - .spacing(5), + .spacing(theme::SPACING_SM), ); } @@ -186,20 +189,20 @@ fn render_block_details<'a, Message: 'a>(b: &InspectorBlock) -> Element<'a, Mess fn render_element_details<'a, Message: 'a>(e: &InspectorElement) -> Element<'a, Message> { let mut col = column![ - field::("Type", e.kind.clone()), - field::("Layout Ref", e.layout_ref.to_string()), + widgets::field::("Type", e.kind.clone()), + widgets::field::("Layout Ref", e.layout_ref.to_string()), bbox_field::(&e.bbox), ] - .spacing(8); + .spacing(theme::SPACING_SM); if !e.text.is_empty() { col = col.push( column![ - Space::with_height(5), - section_header::("Text Content"), + widgets::v_space(theme::SPACING_SM), + widgets::section_header("Text Content"), render_text_box::(&e.text) ] - .spacing(5), + .spacing(theme::SPACING_SM), ); } @@ -208,48 +211,33 @@ fn render_element_details<'a, Message: 'a>(e: &InspectorElement) -> Element<'a, fn render_layout_details<'a, Message: 'a>(l: &InspectorLayout) -> Element<'a, Message> { column![ - field::("Label", l.label.clone()), - field::("Confidence", format!("{:.2}%", l.proba * 100.0)), + widgets::field::("Label", l.label.clone()), + widgets::field::("Confidence", format!("{:.2}%", l.proba * 100.0)), bbox_field::(&l.bbox), ] - .spacing(8) + .spacing(theme::SPACING_SM) .into() } fn render_text_box<'a, Message: 'a>(content: &str) -> Element<'a, Message> { container( text(content.to_string()) - .size(12) + .size(theme::TEXT_SIZE_MD) .line_height(iced::widget::text::LineHeight::Relative(1.4)), ) - .padding(8) + .padding(theme::PADDING_MD) .style(|_| container::Style { background: Some(Color::from_rgba(1.0, 1.0, 1.0, 0.03).into()), border: iced::Border { - color: Color::from_rgba(1.0, 1.0, 1.0, 0.1), + color: theme::SURFACE1, width: 1.0, - radius: 4.0.into(), + radius: theme::BORDER_RADIUS_SM.into(), }, ..Default::default() }) .into() } -fn field<'a, Message: 'a>(key: &'a str, value: String) -> Element<'a, Message> { - row![ - text(format!("{}:", key)) - .size(10) - .color(Color::from_rgb(0.5, 0.5, 0.6)) - .font(Font { - weight: Weight::Bold, - ..Default::default() - }), - text(value).size(11), - ] - .spacing(10) - .into() -} - fn bbox_field<'a, Message: 'a>(bbox: &[f32; 4]) -> Element<'a, Message> { let [x0, y0, x1, y1] = bbox; let w = (x1 - x0).abs(); @@ -257,29 +245,15 @@ fn bbox_field<'a, Message: 'a>(bbox: &[f32; 4]) -> Element<'a, Message> { column![ text("Geometry") - .size(10) - .color(Color::from_rgb(0.5, 0.5, 0.6)) - .font(Font { - weight: Weight::Bold, - ..Default::default() - }), + .size(theme::TEXT_SIZE_SM) + .color(theme::SUBTEXT0) + .font(theme::FONT_BOLD), row![ text(format!("X:{:.1} Y:{:.1} W:{:.1} H:{:.1}", x0, y0, w, h)) - .size(11) - .font(Font::MONOSPACE) + .size(theme::TEXT_SIZE_SM) + .font(theme::FONT_MONOSPACE) ] ] .spacing(2) .into() } - -fn section_header<'a, Message: 'a>(title: &'a str) -> Element<'a, Message> { - text(title) - .size(10) - .font(Font { - weight: Weight::Bold, - ..Default::default() - }) - .color(Color::from_rgb(0.6, 0.6, 0.7)) - .into() -} diff --git a/ferrules-debug/src/main.rs b/ferrules-debug/src/main.rs index 1821926..a5af4fc 100644 --- a/ferrules-debug/src/main.rs +++ b/ferrules-debug/src/main.rs @@ -1,17 +1,18 @@ use clap::Parser; use ferrules_core::debug_info::DebugDocument; -use iced::font::Weight; use iced::widget::{ button, canvas, checkbox, column, container, horizontal_space, image, row, slider, text, Space, Tooltip, }; -use iced::{event, window, Alignment, Color, Element, Event, Font, Length, Task, Theme, Vector}; +use iced::{event, window, Alignment, Color, Element, Event, Length, Task, Theme, Vector}; use memmap2::Mmap; use rkyv::archived_root; use std::path::PathBuf; mod inspector; mod painter; +pub mod theme; +pub mod widgets; use inspector::{view_inspector, InspectorItem, InspectorSection}; use painter::{CanvasMessage, PagePainter, PainterMode}; @@ -33,11 +34,7 @@ pub fn main() -> iced::Result { } // Catppuccin Mocha Palette -const MOCHA_BASE: Color = Color::from_rgb(0.117, 0.117, 0.180); // #1e1e2e -const MOCHA_SURFACE0: Color = Color::from_rgb(0.192, 0.196, 0.266); // #313244 -const MOCHA_SURFACE1: Color = Color::from_rgb(0.270, 0.278, 0.352); // #45475a -const MOCHA_LAVENDER: Color = Color::from_rgb(0.705, 0.745, 0.996); // #b4befe (Accent) -const MOCHA_TEXT: Color = Color::from_rgb(0.803, 0.839, 0.956); // #cdd6f4 +// Catppuccin Mocha Palette (Moved to theme.rs) struct FerrulesDebug { mmap: Option, @@ -230,15 +227,6 @@ impl FerrulesDebug { } fn view(&self) -> Element<'_, Message> { - let bold_font = Font { - weight: Weight::Bold, - ..Default::default() - }; - let medium_font = Font { - weight: Weight::Medium, - ..Default::default() - }; - let logo_path = "/Users/amine/coding/ferrules/imgs/ferrules-logo.png"; if let Some(mmap) = &self.mmap { @@ -265,70 +253,72 @@ impl FerrulesDebug { .padding(0) .style(button::text), horizontal_space(), - button(text("‹").size(16).font(bold_font)) + // Line 268 fix: use theme::FONT_BOLD not bold_font + button(text("‹").size(16).font(theme::FONT_BOLD)) .on_press(Message::ToggleSidebar) .padding(5) .style(button::text), ] .align_y(Alignment::Center) .width(Length::Fill), - Space::with_height(10), + widgets::v_space(10.0), text(archived.name.as_str()) - .size(13) - .color(MOCHA_TEXT) - .font(medium_font), - Space::with_height(30), - button(text("Open .ferr").size(14).font(medium_font)) - .on_press(Message::OpenFile) - .padding(10) - .width(Length::Fill), - Space::with_height(30), - text("LAYERS") - .size(11) - .color(Color::from_rgb(0.5, 0.5, 0.6)) - .font(bold_font), + .size(theme::TEXT_SIZE_MD) + .color(theme::TEXT) + .font(theme::FONT_MEDIUM), + widgets::v_space(30.0), + button( + text("Open .ferr") + .size(theme::TEXT_SIZE_LG) + .font(theme::FONT_MEDIUM) + ) + .on_press(Message::OpenFile) + .padding(theme::PADDING_MD) + .width(Length::Fill), + widgets::v_space(30.0), + widgets::section_header("LAYERS"), column![ self.layer_checkbox( "Native Lines", self.show_native, Layer::Native, - Color::from_rgb(0.95, 0.54, 0.65) - ), // Mocha Red + theme::RED + ), self.layer_checkbox( "Vector Paths", self.show_paths, Layer::Paths, - Color::from_rgba(0.9, 0.9, 0.9, 0.5) + theme::OVERLAY0_FADED ), self.layer_checkbox( "Layout Analysis", self.show_layout, Layer::Layout, - Color::from_rgb(0.65, 0.89, 0.63) - ), // Mocha Green + theme::GREEN + ), self.layer_checkbox( "OCR Text Lines", self.show_ocr, Layer::OCR, - Color::from_rgb(0.53, 0.70, 0.98) - ), // Mocha Blue + theme::BLUE + ), self.layer_checkbox( "Logical Elements", self.show_elements, Layer::Elements, - Color::from_rgb(0.98, 0.70, 0.52) - ), // Mocha Peach + theme::PEACH + ), self.layer_checkbox( "Structural Blocks", self.show_blocks, Layer::Blocks, - Color::from_rgb(0.79, 0.65, 0.96) - ), // Mocha Mauve + theme::MAUVE + ), ] - .spacing(14), + .spacing(theme::SPACING_LG), ] - .spacing(10) - .padding(20) + .spacing(theme::SPACING_MD) + .padding(theme::PADDING_LG) } else { column![ button( @@ -349,27 +339,18 @@ impl FerrulesDebug { iced::widget::tooltip::Position::Right, ), ] - .spacing(20) - .padding(10) + .spacing(theme::SPACING_XL) + .padding(theme::PADDING_MD) .align_x(Alignment::Center) }; - let left_sidebar = container(left_sidebar_content) + let left_sidebar = widgets::panel_container(left_sidebar_content) .width(if self.sidebar_open { - Length::Fixed(240.0) + Length::Fixed(theme::SIDEBAR_WIDTH_OPEN) } else { - Length::Fixed(60.0) + Length::Fixed(theme::SIDEBAR_WIDTH_CLOSED) }) - .height(Length::Fill) - .style(move |_| container::Style { - background: Some(MOCHA_SURFACE0.into()), - border: iced::Border { - width: 0.0, - color: Color::TRANSPARENT, - ..Default::default() - }, - ..Default::default() - }); + .height(Length::Fill); // --- TOP BAR --- let top_bar = container( @@ -378,8 +359,8 @@ impl FerrulesDebug { container( row![ text(format!("Page {} / {}", current_page_idx + 1, total_pages)) - .size(14) - .font(bold_font), + .size(theme::TEXT_SIZE_LG) + .font(theme::FONT_BOLD), slider( 0..=total_pages.saturating_sub(1), current_page_idx as u32, @@ -388,14 +369,14 @@ impl FerrulesDebug { .width(Length::Fixed(300.0)) .step(1u32), ] - .spacing(15) + .spacing(theme::SPACING_LG) .align_y(Alignment::Center) ) - .padding([0, 20]) + .padding([0.0, theme::PADDING_LG]) .style(move |_| container::Style { - background: Some(MOCHA_SURFACE0.into()), + background: Some(theme::SURFACE0.into()), border: iced::Border { - radius: 20.0.into(), + radius: theme::BORDER_RADIUS_LG.into(), ..Default::default() }, ..Default::default() @@ -403,18 +384,18 @@ impl FerrulesDebug { horizontal_space(), row![ text(format!("{:3.0}%", self.zoom * 100.0)) - .size(13) - .font(bold_font) - .color(MOCHA_TEXT), + .size(theme::TEXT_SIZE_MD) + .font(theme::FONT_BOLD) + .color(theme::TEXT), slider(0.1..=5.0, self.zoom, Message::ZoomSliderChanged) .step(0.1) .width(Length::Fixed(120.0)), - button(text("Reset").size(12).font(bold_font)) + button(text("Reset").size(12).font(theme::FONT_BOLD)) .on_press(Message::ResetView) .padding([4, 12]) .style(button::secondary), ] - .spacing(15) + .spacing(theme::SPACING_LG) .align_y(Alignment::Center) ] .width(Length::Fill) @@ -422,18 +403,15 @@ impl FerrulesDebug { .padding(12), ) .style(move |_| container::Style { - background: Some(MOCHA_SURFACE1.into()), + background: Some(theme::SURFACE1.into()), ..Default::default() }); // --- RIGHT SIDEBAR (Inspector) --- - let right_sidebar = container( + let right_sidebar = widgets::panel_container( column![ - text("INSPECTOR") - .size(11) - .color(Color::from_rgb(0.5, 0.5, 0.6)) - .font(bold_font), - Space::with_height(10), + widgets::section_header("INSPECTOR"), + widgets::v_space(10.0), view_inspector( self.selected_info.as_ref().unwrap_or(&self.hovered_info), self.inspector_block_open, @@ -444,14 +422,10 @@ impl FerrulesDebug { Message::ToggleInspectorSection(InspectorSection::Layout), ) ] - .padding(10), + .padding(theme::PADDING_MD), ) - .width(Length::Fixed(320.0)) - .height(Length::Fill) - .style(move |_| container::Style { - background: Some(MOCHA_SURFACE0.into()), - ..Default::default() - }); + .width(Length::Fixed(theme::INSPECTOR_WIDTH)) + .height(Length::Fill); // --- MAIN CANVAS --- let image_painter = PagePainter { @@ -501,7 +475,7 @@ impl FerrulesDebug { .width(Length::Fill) .height(Length::Fill) .style(|_| container::Style { - background: Some(MOCHA_BASE.into()), + background: Some(theme::BASE.into()), ..Default::default() }); @@ -519,21 +493,25 @@ impl FerrulesDebug { .width(Length::Fixed(120.0)) .height(Length::Fixed(120.0)), text("DOCUMENT ANALYSIS TOOLKIT") - .size(18) - .color(MOCHA_LAVENDER) - .font(bold_font), - Space::with_height(40), - button(text("Select .ferr File").size(18).font(bold_font)) - .on_press(Message::OpenFile) - .padding([15, 60]) - .style(button::primary), + .size(theme::TEXT_SIZE_XL) + .color(theme::LAVENDER) + .font(theme::FONT_BOLD), + widgets::v_space(40.0), + button( + text("Select .ferr File") + .size(theme::TEXT_SIZE_XL) + .font(theme::FONT_BOLD) + ) + .on_press(Message::OpenFile) + .padding([15, 60]) + .style(button::primary), text("Or drop archive here") - .size(14) - .color(MOCHA_TEXT) - .font(medium_font), + .size(theme::TEXT_SIZE_LG) + .color(theme::TEXT) + .font(theme::FONT_MEDIUM), ] .align_x(Alignment::Center) - .spacing(20), + .spacing(theme::SPACING_XL), ) .width(Length::Fill) .height(Length::Fill) @@ -550,10 +528,6 @@ impl FerrulesDebug { layer: Layer, color: Color, ) -> Element<'_, Message> { - let bold_font = Font { - weight: Weight::Bold, - ..Default::default() - }; row![ container(Space::with_width(3)) .width(Length::Fixed(3.0)) @@ -569,8 +543,8 @@ impl FerrulesDebug { checkbox(label, is_checked) .on_toggle(move |_| Message::ToggleLayer(layer.clone())) .size(14) - .text_size(13) - .font(bold_font), + .text_size(theme::TEXT_SIZE_MD) + .font(theme::FONT_BOLD), ] .spacing(12) .align_y(Alignment::Center) diff --git a/ferrules-debug/src/painter.rs b/ferrules-debug/src/painter.rs index abd186f..9602aea 100644 --- a/ferrules-debug/src/painter.rs +++ b/ferrules-debug/src/painter.rs @@ -178,20 +178,7 @@ impl<'a> Program for PagePainter<'a> { frame.draw_image(image_rect, img); } PainterMode::Overlay => { - // Catppuccin Mocha Palette - let red = Color::from_rgb(0.95, 0.54, 0.65); // #f38ba8 - let green = Color::from_rgb(0.65, 0.89, 0.63); // #a6e3a1 - let mauve = Color::from_rgb(0.79, 0.65, 0.96); // #cba6f7 - let blue = Color::from_rgb(0.53, 0.70, 0.98); // #89b4fa - let peach = Color::from_rgb(0.98, 0.70, 0.52); // #fab387 - let surface0 = Color::from_rgb(0.19, 0.19, 0.26); // #313244 - let overlay0_faded = Color { - r: 0.43, - g: 0.45, - b: 0.56, - a: 0.3, - }; // #6c7086 - + // Catppuccin Mocha Palette (Now using theme.rs) if self.show_paths { for path in self.page.paths.as_slice() { for segment in path.segments.as_slice() { @@ -208,7 +195,7 @@ impl<'a> Program for PagePainter<'a> { frame.stroke( &Path::line(p1, p2), Stroke::default() - .with_color(overlay0_faded) + .with_color(crate::theme::OVERLAY0_FADED.into()) .with_width(0.8), ); } @@ -225,7 +212,7 @@ impl<'a> Program for PagePainter<'a> { frame.stroke( &Path::rectangle(rect.position(), rect.size()), Stroke::default() - .with_color(overlay0_faded) + .with_color(crate::theme::OVERLAY0_FADED.into()) .with_width(0.8), ); } @@ -247,7 +234,9 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(red).with_width(0.8), + Stroke::default() + .with_color(crate::theme::RED) + .with_width(0.8), ); } } @@ -265,7 +254,9 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(blue).with_width(0.8), + Stroke::default() + .with_color(crate::theme::BLUE) + .with_width(0.8), ); } } @@ -283,7 +274,9 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(green).with_width(1.2), + Stroke::default() + .with_color(crate::theme::GREEN) + .with_width(1.2), ); } } @@ -301,7 +294,9 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(peach).with_width(1.5), + Stroke::default() + .with_color(crate::theme::PEACH) + .with_width(1.5), ); } } @@ -319,7 +314,9 @@ impl<'a> Program for PagePainter<'a> { ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), - Stroke::default().with_color(mauve).with_width(2.0), + Stroke::default() + .with_color(crate::theme::MAUVE) + .with_width(2.0), ); } } @@ -353,12 +350,12 @@ impl<'a> Program for PagePainter<'a> { frame.fill( &Path::rectangle(rect.position(), rect.size()), - Color::from_rgba(1.0, 0.9, 0.2, 0.2), + crate::theme::SELECTION_BG, ); frame.stroke( &Path::rectangle(rect.position(), rect.size()), Stroke::default() - .with_color(Color::from_rgb(1.0, 0.9, 0.2)) + .with_color(crate::theme::SELECTION_BORDER) .with_width(4.0), ); @@ -375,13 +372,13 @@ impl<'a> Program for PagePainter<'a> { if !label_text.is_empty() { let label_bg_color = if element.is_some() { - peach + crate::theme::PEACH } else if block.is_some() { - mauve + crate::theme::MAUVE } else if layout.is_some() { - green + crate::theme::GREEN } else { - surface0 + crate::theme::SURFACE0 }; let label_size = 12.0; diff --git a/ferrules-debug/src/theme.rs b/ferrules-debug/src/theme.rs new file mode 100644 index 0000000..dfc3f9c --- /dev/null +++ b/ferrules-debug/src/theme.rs @@ -0,0 +1,67 @@ +use iced::{font::Weight, Color, Font}; + +// --- PALETTE: Catppuccin Mocha --- +pub const BASE: Color = Color::from_rgb(0.117, 0.117, 0.180); // #1e1e2e +pub const SURFACE0: Color = Color::from_rgb(0.192, 0.196, 0.266); // #313244 +pub const SURFACE1: Color = Color::from_rgb(0.270, 0.278, 0.352); // #45475a +pub const OVERLAY0: Color = Color::from_rgb(0.424, 0.447, 0.533); // #6c7086 (Adjusted for visibility) +pub const OVERLAY0_FADED: Color = Color { a: 0.3, ..OVERLAY0 }; +pub const TEXT: Color = Color::from_rgb(0.803, 0.839, 0.956); // #cdd6f4 +pub const SUBTEXT0: Color = Color::from_rgb(0.659, 0.694, 0.808); // #a6adc8 + +// Accents +pub const RED: Color = Color::from_rgb(0.953, 0.545, 0.659); // #f38ba8 +pub const GREEN: Color = Color::from_rgb(0.651, 0.890, 0.631); // #a6e3a1 +pub const BLUE: Color = Color::from_rgb(0.537, 0.706, 0.980); // #89b4fa +pub const PEACH: Color = Color::from_rgb(0.980, 0.702, 0.522); // #fab387 +pub const MAUVE: Color = Color::from_rgb(0.796, 0.651, 0.969); // #cba6f7 +pub const LAVENDER: Color = Color::from_rgb(0.706, 0.745, 0.996); // #b4befe +pub const YELLOW: Color = Color::from_rgb(0.976, 0.890, 0.686); // #f9e2af + +pub const SELECTION_BG: Color = Color { + r: 0.976, + g: 0.890, + b: 0.686, + a: 0.2, +}; // Yellow with alpha +pub const SELECTION_BORDER: Color = YELLOW; + +// --- TYPOGRAPHY --- +pub const FONT_BOLD: Font = Font { + weight: Weight::Bold, + ..Font::DEFAULT +}; + +pub const FONT_MEDIUM: Font = Font { + weight: Weight::Medium, + ..Font::DEFAULT +}; + +pub const FONT_MONOSPACE: Font = Font::MONOSPACE; + +// --- SIZES --- +pub const TEXT_SIZE_SM: f32 = 11.0; +pub const TEXT_SIZE_MD: f32 = 13.0; +pub const TEXT_SIZE_LG: f32 = 14.0; +pub const TEXT_SIZE_XL: f32 = 18.0; + +pub const ICON_SIZE_SM: f32 = 16.0; +pub const ICON_SIZE_MD: f32 = 24.0; + +// --- LAYOUT --- +pub const SPACING_SM: f32 = 5.0; +pub const SPACING_MD: f32 = 10.0; +pub const SPACING_LG: f32 = 15.0; +pub const SPACING_XL: f32 = 20.0; + +pub const PADDING_SM: f32 = 5.0; +pub const PADDING_MD: f32 = 10.0; +pub const PADDING_LG: f32 = 20.0; + +pub const BORDER_RADIUS_SM: f32 = 4.0; +pub const BORDER_RADIUS_MD: f32 = 8.0; +pub const BORDER_RADIUS_LG: f32 = 20.0; + +pub const SIDEBAR_WIDTH_OPEN: f32 = 240.0; +pub const SIDEBAR_WIDTH_CLOSED: f32 = 60.0; +pub const INSPECTOR_WIDTH: f32 = 320.0; diff --git a/ferrules-debug/src/widgets.rs b/ferrules-debug/src/widgets.rs new file mode 100644 index 0000000..18bdf8e --- /dev/null +++ b/ferrules-debug/src/widgets.rs @@ -0,0 +1,73 @@ +use crate::theme; +use iced::{ + widget::{button, container, row, text, Space}, + Element, +}; + +// --- TEXT HELPER --- +pub fn header_text<'a>(content: impl Into) -> text::Text<'a> { + let s: String = content.into(); + text(s) + .size(theme::TEXT_SIZE_SM) + .color(theme::SUBTEXT0) + .font(theme::FONT_BOLD) +} + +pub fn body_text<'a>(content: impl Into) -> text::Text<'a> { + let s: String = content.into(); + text(s).size(theme::TEXT_SIZE_MD).color(theme::TEXT) +} + +pub fn section_header<'a>(content: impl Into) -> text::Text<'a> { + let s: String = content.into(); + text(s) + .size(theme::TEXT_SIZE_SM) + .font(theme::FONT_BOLD) + .color(theme::OVERLAY0) +} + +// --- BUTTONS --- +pub fn icon_button<'a, Message>( + icon: text::Text<'a>, + on_press: Message, +) -> button::Button<'a, Message> +where + Message: Clone + 'a, +{ + button(icon) + .on_press(on_press) + .padding(theme::PADDING_SM) + .style(button::text) +} + +// --- CONTAINERS --- +pub fn panel_container<'a, Message>( + content: impl Into>, +) -> container::Container<'a, Message> { + container(content).style(|_| container::Style { + background: Some(theme::SURFACE0.into()), + ..Default::default() + }) +} + +// --- FIELDS --- +pub fn field<'a, Message: 'a>(key: &'a str, value: String) -> Element<'a, Message> { + row![ + text(format!("{}:", key)) + .size(theme::TEXT_SIZE_SM) + .color(theme::SUBTEXT0) + .font(theme::FONT_BOLD), + text(value).size(theme::TEXT_SIZE_SM), + ] + .spacing(theme::SPACING_MD) + .into() +} + +// --- SPACERS --- +pub fn v_space<'a>(height: f32) -> Space { + Space::with_height(height) +} + +pub fn h_space<'a>(width: f32) -> Space { + Space::with_width(width) +} From 3198a3b2595db2395bb515e2296cfc3d5f423259 Mon Sep 17 00:00:00 2001 From: aminediro Date: Mon, 16 Feb 2026 16:10:40 +0100 Subject: [PATCH 8/8] release ubuntu --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3db06e1..238b55b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,7 +48,7 @@ on: jobs: # Run 'dist plan' (or host) to determine what tasks we need to do plan: - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" outputs: val: ${{ steps.plan.outputs.manifest }} tag: ${{ !github.event.pull_request && github.ref_name || '' }} @@ -175,7 +175,7 @@ jobs: needs: - plan - build-local-artifacts - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json @@ -228,7 +228,7 @@ jobs: if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" outputs: val: ${{ steps.host.outputs.manifest }} steps: @@ -295,7 +295,7 @@ jobs: # still allowing individual publish jobs to skip themselves (for prereleases). # "host" however must run to completion, no skipping allowed! if: ${{ always() && needs.host.result == 'success' }} - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: