diff --git a/Cargo.lock b/Cargo.lock index 3def04dfc..ee7db8b6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1092,26 +1092,26 @@ dependencies = [ [[package]] name = "craft-aep" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" [[package]] name = "craft-audio" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ - "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb)", + "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5)", "thiserror 2.0.21", ] [[package]] name = "craft-docpath" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" [[package]] name = "craft-dpx" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "thiserror 2.0.21", ] @@ -1119,7 +1119,7 @@ dependencies = [ [[package]] name = "craft-ecproj-format" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "serde", "serde_json", @@ -1129,12 +1129,12 @@ dependencies = [ [[package]] name = "craft-eps" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" [[package]] name = "craft-exr" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "serde_json", ] @@ -1142,17 +1142,17 @@ dependencies = [ [[package]] name = "craft-fonts-build" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" [[package]] name = "craft-gif" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" [[package]] name = "craft-i18n" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "fixed_decimal", "icu_decimal", @@ -1175,7 +1175,7 @@ dependencies = [ [[package]] name = "craft-lottie" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "serde_json", "thiserror 2.0.21", @@ -1184,7 +1184,7 @@ dependencies = [ [[package]] name = "craft-media" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "thiserror 2.0.21", ] @@ -1200,7 +1200,7 @@ dependencies = [ [[package]] name = "craft-mesh" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "miniz_oxide 0.9.1", "serde_json", @@ -1209,7 +1209,7 @@ dependencies = [ [[package]] name = "craft-pdf" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "hayro-syntax", "thiserror 2.0.21", @@ -1218,7 +1218,7 @@ dependencies = [ [[package]] name = "craft-psd" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "encoding_rs", "flate2", @@ -1229,7 +1229,7 @@ dependencies = [ [[package]] name = "craft-quit" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "dispatch2", "egui", @@ -1240,7 +1240,7 @@ dependencies = [ [[package]] name = "craft-svg" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "craft-xml", "flate2", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "craft-fonts-build", "craft-i18n", @@ -1273,7 +1273,7 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "chrono", "dunce", @@ -1300,9 +1300,9 @@ dependencies = [ [[package]] name = "craft-video" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ - "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb)", + "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5)", "rayon", "thiserror 2.0.21", ] @@ -1310,7 +1310,7 @@ dependencies = [ [[package]] name = "craft-webp" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "thiserror 2.0.21", ] @@ -1318,7 +1318,7 @@ dependencies = [ [[package]] name = "craft-xml" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "quick-xml 0.41.0", "thiserror 2.0.21", @@ -1681,7 +1681,7 @@ name = "effectcraft-export" version = "0.24.1" dependencies = [ "craft-audio", - "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb)", + "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5)", "craft-video", "effectcraft-color", "effectcraft-effects", @@ -1850,7 +1850,7 @@ dependencies = [ "craft-dpx", "craft-exr", "craft-gif", - "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb)", + "craft-media 0.1.0 (git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5)", "craft-webp", "effectcraft-color", "effectcraft-effects", @@ -3420,7 +3420,7 @@ checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a" [[package]] name = "hayro-jbig2" version = "0.3.0" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "hayro-ccitt", ] @@ -3434,7 +3434,7 @@ checksum = "c75ab947623ef4ccaa7acf0579edf7cbb5a73838e3839a7be73335e522f433a1" [[package]] name = "hayro-syntax" version = "0.7.2" -source = "git+https://github.com/storytold/craft-libs?rev=4cb271b785b02e7aafd83ab91d3737f2939111fb#4cb271b785b02e7aafd83ab91d3737f2939111fb" +source = "git+https://github.com/storytold/craft-libs?rev=091863e4a615f38743bfd5d6dc62a2e8f37cd5d5#091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" dependencies = [ "hayro-jbig2", "hayro-jpeg2000", diff --git a/Cargo.toml b/Cargo.toml index 018ec6848..3ac4eccbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,22 +11,22 @@ rust-version = "1.95" repository = "https://github.com/storytold/effectcraft" [workspace.dependencies] -craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", version = "=0.1.0", default-features = false, features = ["read"] } -craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-quit = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-ecproj-format = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-dpx = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-eps = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-exr = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-gif = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-psd = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-svg = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-webp = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-lottie = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } -craft-mesh = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", default-features = false, features = ["stl", "obj", "gltf", "fbx"] } -craft-aep = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb" } +craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", version = "=0.1.0", default-features = false, features = ["read"] } +craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-quit = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-ecproj-format = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-dpx = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-eps = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-exr = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-gif = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-psd = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-svg = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-webp = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-lottie = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } +craft-mesh = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", default-features = false, features = ["stl", "obj", "gltf", "fbx"] } +craft-aep = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5" } # craft-update: app-specific pretend versions apply to startup and manual checks. -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", features = ["egui"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", features = ["egui"] } # internal effectcraft-time = { path = "crates/time" } effectcraft-geom = { path = "crates/geom" } @@ -90,9 +90,9 @@ same-file = "1.0" tempfile = "3.27" # codecs + containers shared by the Craft apps (crates/media, crates/export); keep all three on one rev. -craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", default-features = false, features = ["isobmff", "matroska"] } -craft-audio = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", default-features = false, features = ["aac-decode", "aac-encode", "opus-decode"] } -craft-video = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", features = ["h264-encode", "prores", "hevc-encode", "vp9-encode", "av1", "threads"] } +craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", default-features = false, features = ["isobmff", "matroska"] } +craft-audio = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", default-features = false, features = ["aac-decode", "aac-encode", "opus-decode"] } +craft-video = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", features = ["h264-encode", "prores", "hevc-encode", "vp9-encode", "av1", "threads"] } # footage decoding: FilmCraft's pure-Rust crates, pinned to a revision (plan/adr/0001). # Only `crates/media`, `crates/export` and `crates/interchange` may depend on these. @@ -122,7 +122,7 @@ half = "2.7" # ui # Shared frontend components; keep this dependency above the engine boundary. # Shared UI and the complete egui family select reviewed immutable commits below. -craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", features = ["serde"] } +craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", features = ["serde"] } # `default_fonts` and `monochrome_emoji_fonts` are explicit: the app's `FontDefinitions::default()` # and its `✔`/`▣` chrome glyphs need both, and kittest's own dev-dependencies do not propagate. egui = { version = "0.37", default-features = false, features = ["default_fonts", "monochrome_emoji_fonts"] } @@ -231,8 +231,8 @@ lto = "thin" codegen-units = 1 [patch.crates-io] -hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", version = "=0.7.2" } -hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "4cb271b785b02e7aafd83ab91d3737f2939111fb", version = "=0.3.0" } +hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", version = "=0.7.2" } +hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5", version = "=0.3.0" } ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" } diff --git a/crates/lottie/src/shapes.rs b/crates/lottie/src/shapes.rs index ab13374e9..f28e73a04 100644 --- a/crates/lottie/src/shapes.rs +++ b/crates/lottie/src/shapes.rs @@ -532,7 +532,7 @@ fn repeater_tr_get(t: &RepeaterTransform) -> impl Fn(&str) -> Option + '_ } /// Import Lottie shape items into shape contents groups. -pub(crate) fn import_items<'a>(im: &mut Im, ids: &mut Ids, items: &mut dyn Iterator) -> Vec { +pub(crate) fn import_items(im: &mut Im, ids: &mut Ids, items: &mut dyn Iterator) -> Vec { let mut out = vec![]; for s in items { let x = &s.extra; diff --git a/crates/lottie/src/text.rs b/crates/lottie/src/text.rs index 2bb17572c..39199cfb3 100644 --- a/crates/lottie/src/text.rs +++ b/crates/lottie/src/text.rs @@ -55,7 +55,7 @@ fn doc_json(d: &TextDoc) -> TextDocument { /// A colour of a text document (`fc`, `sc`), 0..=1 or 0..=255. fn doc_color(c: Option<&Vec>, extra: &Map, key: &str) -> Option<[f32; 4]> { - let j = c.map(|c| json!(c)).or_else(|| extra.get(key).cloned())?; + let j = c.filter(|c| !c.is_empty()).map(|c| json!(c)).or_else(|| extra.get(key).cloned())?; anim::to_color(&j).map(|v| v.as_color()) } diff --git a/crates/media/src/dpx.rs b/crates/media/src/dpx.rs index 17df4bb85..f08f1344d 100644 --- a/crates/media/src/dpx.rs +++ b/crates/media/src/dpx.rs @@ -89,20 +89,27 @@ pub(crate) fn decode(path: &str, bytes: &[u8]) -> Result image::ImageBuffer::from_raw(w32, h32, out).map(image::DynamicImage::ImageLuma16), diff --git a/crates/model/src/gltf.rs b/crates/model/src/gltf.rs index 219a9d364..68bc86764 100644 --- a/crates/model/src/gltf.rs +++ b/crates/model/src/gltf.rs @@ -18,7 +18,9 @@ pub fn parse(bytes: &[u8], resolve: &dyn Fn(&str) -> Option>) -> Result< let uri = im.uri.as_deref()?; match uri.strip_prefix("data:") { Some(_) => None, - None => resolve(&uri_decode(uri)), + // craft-mesh decodes external image URIs while constructing the scene. Decode only + // once here; a second pass changes literal percent escapes in valid filenames. + None => resolve(uri), } }) } diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index 7a1b7f30e..b016c52b0 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -204,6 +204,9 @@ impl Primitive { if !self.uvs.is_empty() { p.uvs.push(self.uvs[i]); } + if !self.tangents.is_empty() { + p.tangents.push(self.tangents[i]); + } if !self.joints.is_empty() { p.joints.push(self.joints[i]); p.weights.push(self.weights[i]); diff --git a/crates/model/src/obj.rs b/crates/model/src/obj.rs index fd39dbd53..c305cf23f 100644 --- a/crates/model/src/obj.rs +++ b/crates/model/src/obj.rs @@ -29,7 +29,9 @@ fn resolve_reference(resolve: &dyn Fn(&str) -> Option>, name: &str) -> O /// Parse OBJ text (and its MTL libraries and textures through `resolve`). pub fn parse(bytes: &[u8], resolve: &dyn Fn(&str) -> Option>) -> Result { - let mut library = |name: &str| resolve_reference(resolve, name); + // `craft-mesh` splits `mtllib` names and tries each complete library path itself. Applying + // texture-option fallback here first can mistake the last library for the whole directive. + let mut library = |name: &str| resolve(name); let scene = craft_mesh::obj::read(bytes, &limits(), &mut library).map_err(error)?; to_model(scene, MissingNormals::BySmoothing, &|im| resolve_reference(resolve, im.uri.as_deref()?.trim())) } diff --git a/crates/model/src/scene.rs b/crates/model/src/scene.rs index c3ec05415..587b25586 100644 --- a/crates/model/src/scene.rs +++ b/crates/model/src/scene.rs @@ -120,7 +120,8 @@ pub(crate) fn to_model(scene: cm::Scene, missing: MissingNormals, image: &dyn Fn unlit: m.unlit, }) .collect(); - let meshes = scene.meshes.iter().map(|m| mesh(m, &materials, missing)).collect(); + let mut adapter_bytes = 0u64; + let meshes = scene.meshes.iter().map(|m| mesh(m, &materials, missing, &mut adapter_bytes)).collect::>>()?; let nodes = scene .nodes .iter() @@ -215,13 +216,32 @@ pub(crate) fn to_model(scene: cm::Scene, missing: MissingNormals, image: &dyn Fn /// One primitive per part, each with one vertex per distinct corner of its triangles. Normals /// and UVs are kept for a part when every corner of it has them; a triangle naming a vertex /// that is not there is left out. -fn mesh(m: &cm::Mesh, materials: &[Material], missing: MissingNormals) -> Mesh { +fn mesh(m: &cm::Mesh, materials: &[Material], missing: MissingNormals, adapter_bytes: &mut u64) -> Result { let skinned = !m.joints.is_empty() && m.joints.len() == m.positions.len() && m.weights.len() == m.positions.len(); let uv_set = m.uv_sets.first(); + let smooth = smooth_normals(m, missing); + // Include temporary hash keys and the peak duplicate produced by flat-normal expansion. + // Account against the whole scene before building any primitive arrays. + let projected = m + .parts + .iter() + .try_fold(0u64, |sum, part| { + let has_normals = part.corners.iter().any(|c| (c.normal as usize) < m.normals.len()); + let vertices = + if missing == MissingNormals::Flat && !has_normals { (part.triangle_count() as u64).checked_mul(3)? } else { part.corners.len() as u64 }; + sum.checked_add(vertices.checked_mul(64)?) + }) + .ok_or_else(|| ModelError::Parse("expanded mesh size overflow".into()))?; + *adapter_bytes = adapter_bytes.checked_add(projected).ok_or_else(|| ModelError::Parse("expanded mesh size overflow".into()))?; + if *adapter_bytes > (2u64 << 30) { + return Err(ModelError::Parse("expanded mesh data exceeds the 2 GiB import limit".into())); + } let mut primitives = vec![]; for part in &m.parts { - let normals = part.corners.iter().all(|c| (c.normal as usize) < m.normals.len()); - let uvs = uv_set.filter(|set| part.corners.iter().all(|c| (c.uv as usize) < set.len())); + let normals = part.corners.iter().any(|c| (c.normal as usize) < m.normals.len()); + let tangents = part.has_tangents && m.tangents.len() == m.positions.len(); + let uvs = uv_set.filter(|set| part.corners.iter().any(|c| (c.uv as usize) < set.len())); + let mut authored_normals = vec![]; let mut ids: HashMap = HashMap::new(); let mut p = Primitive { material: part.material.filter(|&i| i < materials.len()), ..Primitive::default() }; for t in part.triangles() { @@ -229,14 +249,23 @@ fn mesh(m: &cm::Mesh, materials: &[Material], missing: MissingNormals) -> Mesh { continue; } for c in t { - let key = - cm::Corner { position: c.position, normal: if normals { c.normal } else { cm::NONE }, uv: if uvs.is_some() { c.uv } else { cm::NONE } }; + let has_normal = (c.normal as usize) < m.normals.len(); + let has_uv = uvs.is_some_and(|set| (c.uv as usize) < set.len()); + let key = cm::Corner { position: c.position, normal: if has_normal { c.normal } else { cm::NONE }, uv: if has_uv { c.uv } else { cm::NONE } }; let next = p.positions.len() as u32; let id = *ids.entry(key).or_insert_with(|| { let at = c.position as usize; p.positions.push(m.positions.get(at).copied().unwrap_or_default().map(|x| x as f32)); if normals { - p.normals.push(m.normals.get(c.normal as usize).copied().unwrap_or_default().map(|x| x as f32)); + let authored = m.normals.get(c.normal as usize).copied().map(|n| n.map(|x| x as f32)); + let fallback = smooth.get(&(c.position, part.material, smooth_group(part, missing))).copied(); + authored_normals.push(authored.or(fallback)); + p.normals.push(authored.or(fallback).unwrap_or([0.0, 0.0, 1.0])); + } else if let Some(n) = smooth.get(&(c.position, part.material, smooth_group(part, missing))) { + p.normals.push(*n); + } + if tangents { + p.tangents.push(m.tangents.get(at).copied().unwrap_or_default().map(|x| x as f32)); } if let Some(set) = uvs { p.uvs.push(set.get(c.uv as usize).copied().unwrap_or_default()); @@ -253,17 +282,74 @@ fn mesh(m: &cm::Mesh, materials: &[Material], missing: MissingNormals) -> Mesh { if p.indices.is_empty() { continue; } - if p.normals.is_empty() { + if normals && authored_normals.iter().any(Option::is_none) { + // Keep authored corner normals, and fill only absent ones from the surrounding + // triangles. The generated normals are smooth in a mixed-authorship part. + if authored_normals.iter().any(Option::is_none) { + p.compute_normals(); + for (n, authored) in p.normals.iter_mut().zip(authored_normals) { + if let Some(authored) = authored { + *n = authored; + } + } + } + } else if p.normals.is_empty() { match missing { MissingNormals::Flat => p.flat_normals(), MissingNormals::BySmoothing if part.smoothing == 0 => p.flat_normals(), MissingNormals::Smooth | MissingNormals::BySmoothing => p.compute_normals(), } } - if p.material.is_some_and(|i| materials.get(i).is_some_and(|mat| mat.normal_tex.is_some())) { + if p.tangents.is_empty() && p.material.is_some_and(|i| materials.get(i).is_some_and(|mat| mat.normal_tex.is_some())) { p.compute_tangents(); } primitives.push(p); } - Mesh { name: m.name.clone(), primitives } + Ok(Mesh { name: m.name.clone(), primitives }) +} + +fn smooth_group(part: &cm::Part, missing: MissingNormals) -> u32 { + match missing { + MissingNormals::Smooth => 1, + MissingNormals::BySmoothing => part.smoothing, + MissingNormals::Flat => 0, + } +} + +/// Area weighted generated normals across parts with the same material and smoothing state. +fn smooth_normals(m: &cm::Mesh, missing: MissingNormals) -> HashMap<(u32, Option, u32), [f32; 3]> { + let mut sums: HashMap<(u32, Option, u32), [f64; 3]> = HashMap::new(); + if missing == MissingNormals::Flat { + return HashMap::new(); + } + for part in &m.parts { + let group = smooth_group(part, missing); + if group == 0 { + continue; + } + for tri in part.triangles() { + let Some(a) = m.positions.get(tri[0].position as usize) else { continue }; + let Some(b) = m.positions.get(tri[1].position as usize) else { continue }; + let Some(c) = m.positions.get(tri[2].position as usize) else { continue }; + let u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]]; + let v = [c[0] - a[0], c[1] - a[1], c[2] - a[2]]; + let face = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]]; + for corner in tri { + if (corner.normal as usize) < m.normals.len() { + continue; + } + let n = sums.entry((corner.position, part.material, group)).or_default(); + for i in 0..3 { + n[i] += face[i]; + } + } + } + } + sums.into_iter() + .map(|(key, n)| { + let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt(); + let n = if len > 0.0 { n.map(|x| (x / len) as f32) } else { [0.0, 0.0, 1.0] }; + (key, n) + }) + .collect() } diff --git a/crates/model/src/tests.rs b/crates/model/src/tests.rs index b63ec558c..125e4bf81 100644 --- a/crates/model/src/tests.rs +++ b/crates/model/src/tests.rs @@ -597,9 +597,9 @@ fn gltf_accessor_counts_are_checked_against_the_buffer_before_allocating() { let view = r#"{"buffer": 0, "byteLength": 36}"#; let cases = [ // A count far beyond the 36-byte buffer. - (r#"{"bufferView": 0, "componentType": 5126, "type": "VEC3", "count": 4000000000}"#, view, "past its buffer"), + (r#"{"bufferView": 0, "componentType": 5126, "type": "VEC3", "count": 4000000000}"#, view, "limit allows"), // count * components overflows. - (r#"{"bufferView": 0, "componentType": 5126, "type": "MAT4", "count": 9223372036854775807}"#, view, "too large"), + (r#"{"bufferView": 0, "componentType": 5126, "type": "MAT4", "count": 9223372036854775807}"#, view, "limit allows"), // Offsets that overflow when added. ( r#"{"bufferView": 0, "componentType": 5126, "type": "VEC3", "count": 3, "byteOffset": 16}"#, @@ -607,11 +607,11 @@ fn gltf_accessor_counts_are_checked_against_the_buffer_before_allocating() { "past its buffer", ), // A stride that overflows with the count. - (r#"{"bufferView": 0, "componentType": 5126, "type": "VEC3", "count": 3000000000}"#, r#"{"buffer": 0, "byteStride": 252}"#, "past its buffer"), + (r#"{"bufferView": 0, "componentType": 5126, "type": "VEC3", "count": 3000000000}"#, r#"{"buffer": 0, "byteStride": 252}"#, "limit allows"), // A stride smaller than an element would re-read the same bytes for a huge count. (r#"{"bufferView": 0, "componentType": 5126, "type": "MAT4", "count": 30}"#, r#"{"buffer": 0, "byteStride": 1}"#, "smaller than"), // No buffer view: nothing backs the count, so it is capped. - (r#"{"componentType": 5126, "type": "VEC3", "count": 1000000000}"#, view, "without a bufferView"), + (r#"{"componentType": 5126, "type": "VEC3", "count": 1000000000}"#, view, "limit allows"), ]; let t0 = std::time::Instant::now(); for (acc, v, want) in cases { @@ -669,16 +669,13 @@ fn gltf_issue_2139_huge_mat4_count_is_an_error() { } #[test] -fn gltf_buffer_less_accessors_share_one_zero_budget() { - // Each buffer-less accessor is capped, but nothing backs any of them, so referencing a capped - // one from many primitives must not multiply the allocation (#2139). +fn gltf_reused_buffer_less_accessor_is_read_once() { + // The shared reader parses a repeated accessor only once, so referencing it from several + // primitives does not multiply its one bounded allocation. let acc = r#"{"componentType": 5126, "type": "VEC3", "count": 5000000}"#; let prims = [r#"{"attributes": {"POSITION": 0}}"#; 4].join(", "); let doc = gltf_doc(acc, r#"{"buffer": 0}"#, 3).replace(r#"[{"attributes": {"POSITION": 0}}]"#, &format!("[{prims}]")); assert_eq!(doc.matches(r#""POSITION": 0"#).count(), 4); - let e = crate::gltf::parse(doc.as_bytes(), &|_| None).err().unwrap_or_else(|| panic!("imported")); - assert!(e.to_string().contains("without a bufferView"), "{e}"); - // One such accessor read once still imports. - let doc = gltf_doc(acc, r#"{"buffer": 0}"#, 3); - assert!(crate::gltf::parse(doc.as_bytes(), &|_| None).is_ok()); + let result = crate::gltf::parse(doc.as_bytes(), &|_| None); + assert!(result.is_ok(), "{result:?}"); } diff --git a/crates/pdf/README.md b/crates/pdf/README.md index 5d3685090..53689ba9a 100644 --- a/crates/pdf/README.md +++ b/crates/pdf/README.md @@ -144,7 +144,7 @@ ffmpeg elsewhere) to check the code tables against an independent implementation ## Shared parser setup and bounds The workspace pins `craft-pdf` to `craft-libs` Git revision -`4cb271b785b02e7aafd83ab91d3737f2939111fb`, with `default-features = false` and only the explicit `read` +`091863e4a615f38743bfd5d6dc62a2e8f37cd5d5`, with `default-features = false` and only the explicit `read` feature. This excludes the backend's bundled fonts and Adobe CMaps and preserves EffectCraft's font and asset policies. The shared parser does not change the supported drawing operators, image codecs, continuously rasterized SVG tree, editable shapes, or EPS interpreter. diff --git a/crates/pdf/src/tests/import.rs b/crates/pdf/src/tests/import.rs index 516ff054d..f8c6509c2 100644 --- a/crates/pdf/src/tests/import.rs +++ b/crates/pdf/src/tests/import.rs @@ -563,7 +563,8 @@ fn bounding_box_comments_are_scanned_once() { // A rejected `%%BoundingBox:` comment is not scanned again from each later marker on its // line: 20,000 markers on one line would otherwise take quadratic work. let mut src = b"%!PS-Adobe-3.0 EPSF-3.0\n".to_vec(); - src.extend_from_slice("%%BoundingBox: x ".repeat(20_000).as_bytes()); + // Keep the valid comment inside craft-eps's bounded header window. + src.extend_from_slice("%%BoundingBox: x ".repeat(3_000).as_bytes()); src.extend_from_slice(b"\n%%BoundingBox: 0 0 50 50\n0 0 10 10 rectfill\n"); let doc = parse(&src).unwrap(); assert_eq!((doc.width, doc.height), (50.0, 50.0)); diff --git a/crates/svg/src/tests.rs b/crates/svg/src/tests.rs index 7d0e24001..99c318c82 100644 --- a/crates/svg/src/tests.rs +++ b/crates/svg/src/tests.rs @@ -247,7 +247,7 @@ fn deep_nesting_errors_instead_of_overflowing_the_stack() { "".repeat(1000), "".repeat(1000) ); - assert!(!craft_svg::guard::deeper_than(&s, 2) && craft_svg::guard::deeper_than(&s, 1)); + assert!(!craft_svg::guard::deeper_than(&s, 3) && craft_svg::guard::deeper_than(&s, 2)); assert!(crate::parse(s.as_bytes()).is_ok()); } diff --git a/xtask/src/pdf_deps.rs b/xtask/src/pdf_deps.rs index d6fa0d87b..776ce5891 100644 --- a/xtask/src/pdf_deps.rs +++ b/xtask/src/pdf_deps.rs @@ -5,7 +5,7 @@ use std::process::Command; use serde_json::Value; -const REV: &str = "4cb271b785b02e7aafd83ab91d3737f2939111fb"; +const REV: &str = "091863e4a615f38743bfd5d6dc62a2e8f37cd5d5"; const PACKAGES: &[(&str, &str)] = &[("craft-pdf", "0.1.0"), ("hayro-syntax", "0.7.2"), ("hayro-jbig2", "0.3.0")]; pub fn run(root: &Path) -> Result<(), String> {