diff --git a/Cargo.lock b/Cargo.lock index 737b3e022..82976a2e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "accesskit" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad442f58ee04714aaa0ba0a2768c1ea1935b29507bb22ddece8cbbc76db02932" +checksum = "4e883d1d28c5fd5568c79ed2d2bd48d9f2fd3945c8974ed0f53690ceddb051be" dependencies = [ "uuid", ] [[package]] name = "accesskit_atspi_common" -version = "0.21.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c182f9c282ac9c5638d876d551d15e5f7d397ec263349a0c6a2b61595dd5e4" +checksum = "24f5baf62e34a4a6c30e3d697e4782f821a7337049c44c3b40576a27dfbb580d" dependencies = [ "accesskit", "accesskit_consumer", @@ -27,9 +27,9 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.39.1" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a45e2bb300bea6d5f33a82c3e051c3ddc6f41c209750f68a0acbe991a284344" +checksum = "f3da8f27636015191b2264ee1df681503170a632cafd832fdc1edfdd4e3a2fd3" dependencies = [ "accesskit", "hashbrown 0.17.1", @@ -37,37 +37,37 @@ dependencies = [ [[package]] name = "accesskit_ios" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28e539d64b27fd4c14de8f78ada8f7c944c092f52f7e49fb63316507a57f6937" +checksum = "a5280724673bbfa8b30a838c013500b719392989be3b97c5a9964062c252c232" dependencies = [ "accesskit", "accesskit_consumer", "hashbrown 0.17.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", "objc2-ui-kit 0.2.2", ] [[package]] name = "accesskit_macos" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5701624bf6a11efb6d4c8e922bf44126ed96250e5faa9a7a0468c979635839a4" +checksum = "123828ceeec755b05255e3976cb0852993202de98a0626c97dd8d34246991f84" dependencies = [ "accesskit", "accesskit_consumer", "hashbrown 0.17.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] [[package]] name = "accesskit_unix" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202f24df034a7476d07b7f74284de84f6d62aabd858dbe7ee9cad3b7ad6f8f9d" +checksum = "30d5192871228c54f1b0fb4a85cef000e11a3804f25d2165f14afa0daa67f3f6" dependencies = [ "accesskit", "accesskit_atspi_common", @@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.35.1" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce63f35d6bdcf59f26b76b3379063f738e6412cef46999cc772d46aa3de35adb" +checksum = "498668b90da9995302a0055aaecad4756932333da2ddef6baee1c00844c430fc" dependencies = [ "accesskit", "accesskit_consumer", @@ -97,9 +97,9 @@ dependencies = [ [[package]] name = "accesskit_winit" -version = "0.34.1" +version = "0.34.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee96dd150375c05912d8a473f98c2fb155fb3234fac88292d396548a2460fa77" +checksum = "cd4ab4437b520ff03088fbe48676a11e7d44a929535e64b31432c1073e48dbcc" dependencies = [ "accesskit", "accesskit_ios", @@ -207,30 +207,30 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" dependencies = [ "derive_arbitrary", ] [[package]] name = "arboard" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +checksum = "aeb9553e62312c8756a5979254fc6d7da8bbeb5986a1e17037fab932f7037917" dependencies = [ "clipboard-win", "image", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.61.2", "wl-clipboard-rs", "x11rb", ] @@ -352,13 +352,13 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] @@ -393,7 +393,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -505,6 +505,15 @@ dependencies = [ "bit-vec 0.9.1", ] +[[package]] +name = "bit-set" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" +dependencies = [ + "bit-vec 0.10.1", +] + [[package]] name = "bit-vec" version = "0.8.0" @@ -517,6 +526,16 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +[[package]] +name = "bit-vec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" +dependencies = [ + "borsh", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -544,7 +563,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2 0.5.2", + "objc2 0.5.3", ] [[package]] @@ -553,7 +572,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -569,6 +588,30 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "brotli" version = "8.0.4" @@ -613,7 +656,7 @@ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -644,14 +687,13 @@ dependencies = [ [[package]] name = "calloop" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e" dependencies = [ "bitflags 2.13.2", "polling", "rustix 1.1.5", - "slab", "tracing", ] @@ -673,7 +715,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ - "calloop 0.14.4", + "calloop 0.14.5", "rustix 1.1.5", "wayland-backend", "wayland-client", @@ -696,9 +738,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.5.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f360145194ee8e21db5ee7f3fcd4fe52210864c75c985dae33218202c8bbe040" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" dependencies = [ "find-msvc-tools", "jobserver", @@ -754,9 +796,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.4.1" +version = "5.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a" dependencies = [ "error-code", ] @@ -832,9 +874,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.8" +version = "4.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" dependencies = [ "bytes", "memchr", @@ -953,7 +995,7 @@ dependencies = [ [[package]] name = "craft-atomic" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "rustix 1.1.5", ] @@ -961,7 +1003,7 @@ dependencies = [ [[package]] name = "craft-cms" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "rayon", ] @@ -969,12 +1011,12 @@ dependencies = [ [[package]] name = "craft-docpath" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" [[package]] name = "craft-pdf" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "bytemuck", "hayro", @@ -987,7 +1029,7 @@ dependencies = [ [[package]] name = "craft-prepress" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "craft-cms", "craft-pdf", @@ -1000,7 +1042,7 @@ dependencies = [ [[package]] name = "craft-print" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "craft-winprint-gdi", "miniz_oxide 0.8.9", @@ -1013,23 +1055,48 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "egui", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "serde", ] +[[package]] +name = "craft-update" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" +dependencies = [ + "chrono", + "dunce", + "egui", + "flate2", + "getrandom 0.3.4", + "log", + "plist", + "semver", + "serde", + "serde_json", + "sha2", + "tar", + "tempfile", + "ureq", + "verifysign", + "winreg", + "winsafe", + "zip 9.1.1", +] + [[package]] name = "craft-window" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "egui", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "wayland-client", ] @@ -1044,9 +1111,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" dependencies = [ "cfg-if", ] @@ -1150,14 +1217,20 @@ dependencies = [ ] [[package]] -name = "derive_arbitrary" -version = "1.4.2" +name = "deranged" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" + +[[package]] +name = "derive_arbitrary" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b034bd7d5f032402a2479444dcc6f74e36a03f31854d41680fb240ef682a1ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] @@ -1165,6 +1238,7 @@ name = "designcraft" version = "0.13.0" dependencies = [ "arboard", + "craft-update", "craft-window", "designcraft-engine", "designcraft-ui-egui", @@ -1173,7 +1247,7 @@ dependencies = [ "fmv-macos-events", "log", "muda", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "rfd", "serde", @@ -1272,7 +1346,7 @@ dependencies = [ "indd", "kurbo", "log", - "quick-xml", + "quick-xml 0.41.0", "same-file", "serde", "serde_json", @@ -1280,7 +1354,7 @@ dependencies = [ "thiserror 2.0.21", "unicode-segmentation", "wasm-bindgen-test", - "zip", + "zip 3.0.0", ] [[package]] @@ -1290,7 +1364,7 @@ dependencies = [ "chrono", "designcraft-color", "designcraft-doc", - "zip", + "zip 3.0.0", ] [[package]] @@ -1315,7 +1389,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.21", - "zip", + "zip 3.0.0", ] [[package]] @@ -1341,9 +1415,9 @@ dependencies = [ "designcraft-geom", "designcraft-images", "log", - "quick-xml", + "quick-xml 0.41.0", "thiserror 2.0.21", - "zip", + "zip 3.0.0", ] [[package]] @@ -1356,7 +1430,7 @@ dependencies = [ "flate2", "image", "log", - "quick-xml", + "quick-xml 0.41.0", "resvg", "tiff", "zune-core", @@ -1413,10 +1487,10 @@ dependencies = [ "designcraft-geom", "designcraft-images", "image", - "quick-xml", + "quick-xml 0.41.0", "skrifa 0.47.0", "thiserror 2.0.21", - "zip", + "zip 3.0.0", ] [[package]] @@ -1443,8 +1517,8 @@ name = "designcraft-textimport" version = "0.13.0" dependencies = [ "designcraft-doc", - "quick-xml", - "zip", + "quick-xml 0.41.0", + "zip 3.0.0", ] [[package]] @@ -1464,6 +1538,7 @@ name = "designcraft-ui-egui" version = "0.13.0" dependencies = [ "craft-ui", + "craft-update", "craft-window", "designcraft-color", "designcraft-compose", @@ -1483,7 +1558,7 @@ dependencies = [ "toml", "unicode-bidi", "web-time", - "zip", + "zip 3.0.0", ] [[package]] @@ -1543,7 +1618,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.6.2", "libc", - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -1554,7 +1629,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -1619,7 +1694,7 @@ dependencies = [ "image", "js-sys", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "parking_lot", @@ -1687,7 +1762,7 @@ dependencies = [ "egui", "image", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "objc2-ui-kit 0.3.2", @@ -1736,9 +1811,9 @@ dependencies = [ [[package]] name = "either" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" [[package]] name = "emath" @@ -1799,7 +1874,7 @@ dependencies = [ "ecolor", "emath", "epaint_default_fonts", - "font-types 0.12.5", + "font-types 0.12.7", "harfrust 0.12.0", "log", "nohash-hasher", @@ -1822,9 +1897,9 @@ source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f9 [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "errno" @@ -1921,9 +1996,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" [[package]] name = "fixedbitset" @@ -1997,7 +2072,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90642cf66dde7f205dc8b42dfe57b47d0f3a29f571b832742883ef512e23fe55" dependencies = [ - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", ] @@ -2031,9 +2106,9 @@ dependencies = [ [[package]] name = "font-types" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6" +checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d" dependencies = [ "bytemuck", ] @@ -2079,7 +2154,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -2146,7 +2221,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -2326,7 +2401,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -2489,7 +2564,7 @@ dependencies = [ [[package]] name = "hayro" version = "0.7.1" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "bytemuck", "hayro-interpret", @@ -2509,7 +2584,7 @@ checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a" [[package]] name = "hayro-cmap" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "brotli", "hayro-postscript", @@ -2518,7 +2593,7 @@ dependencies = [ [[package]] name = "hayro-interpret" version = "0.7.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "bitflags 2.13.2", "hayro-cmap", @@ -2536,7 +2611,7 @@ dependencies = [ [[package]] name = "hayro-jbig2" version = "0.3.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "fearless_simd", "hayro-ccitt", @@ -2554,12 +2629,12 @@ dependencies = [ [[package]] name = "hayro-postscript" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" [[package]] name = "hayro-syntax" version = "0.7.2" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "flate2", "hayro-ccitt", @@ -2740,9 +2815,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" dependencies = [ "displaydoc", "potential_utf", @@ -2754,9 +2829,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" dependencies = [ "displaydoc", "litemap", @@ -2767,9 +2842,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2781,15 +2856,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" [[package]] name = "icu_properties" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" dependencies = [ "displaydoc", "icu_collections", @@ -2802,15 +2877,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" [[package]] name = "icu_provider" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" dependencies = [ "displaydoc", "icu_locale_core", @@ -3060,7 +3135,7 @@ dependencies = [ [[package]] name = "krilla" version = "0.8.2" -source = "git+https://github.com/storytold/craft-libs?rev=e3b9e239c52c14677be462e94d8920ea243de4f3#e3b9e239c52c14677be462e94d8920ea243de4f3" +source = "git+https://github.com/storytold/craft-libs?rev=495e5971bc024aa96e6ed318cb6e7cd3a0103184#495e5971bc024aa96e6ed318cb6e7cd3a0103184" dependencies = [ "base64 0.22.1", "bumpalo", @@ -3122,9 +3197,9 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "libc" -version = "0.2.189" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libloading" @@ -3151,7 +3226,7 @@ dependencies = [ "bitflags 2.13.2", "libc", "plain", - "redox_syscall 0.9.4", + "redox_syscall 0.9.5", ] [[package]] @@ -3193,9 +3268,9 @@ checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "litrs" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70" [[package]] name = "lock_api" @@ -3226,9 +3301,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" dependencies = [ "libc", ] @@ -3301,18 +3376,17 @@ dependencies = [ [[package]] name = "muda" -version = "0.21.0" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6744ab645ebe79f9c428455c7cb9dfa4275fa3b50a25408e08f927da8dc618c" +checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03" dependencies = [ "crossbeam-channel", "dpi", "keyboard-types", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", - "once_cell", "png", "thiserror 2.0.21", "windows-sys 0.61.2", @@ -3392,6 +3466,18 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -3416,6 +3502,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-traits" version = "0.2.19" @@ -3456,9 +3548,9 @@ checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "19d5490aaf8f1d7cf7688dfa9b0ce07900e168852c45cd2c03f534dfd27cfd0b" dependencies = [ "objc-sys", "objc2-encode", @@ -3466,9 +3558,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" dependencies = [ "objc2-encode", ] @@ -3482,7 +3574,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.5.1", "libc", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-core-data", "objc2-core-image", "objc2-foundation 0.2.2", @@ -3497,7 +3589,7 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.2", "block2 0.6.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -3511,7 +3603,7 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.13.2", "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-core-location", "objc2-foundation 0.2.2", ] @@ -3523,7 +3615,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", ] @@ -3535,7 +3627,7 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.13.2", "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", ] @@ -3547,7 +3639,7 @@ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", ] [[package]] @@ -3558,7 +3650,7 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.2", "dispatch2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-io-surface", ] @@ -3570,7 +3662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", "objc2-metal 0.2.2", ] @@ -3582,7 +3674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-contacts", "objc2-foundation 0.2.2", ] @@ -3603,7 +3695,7 @@ dependencies = [ "block2 0.5.1", "dispatch", "libc", - "objc2 0.5.2", + "objc2 0.5.3", ] [[package]] @@ -3614,7 +3706,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.2", "block2 0.6.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", ] @@ -3625,7 +3717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", ] @@ -3636,7 +3728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", ] @@ -3649,7 +3741,7 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.13.2", "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", ] @@ -3661,7 +3753,7 @@ checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" dependencies = [ "bitflags 2.13.2", "block2 0.6.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-foundation 0.3.2", ] @@ -3673,7 +3765,7 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.13.2", "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", "objc2-metal 0.2.2", ] @@ -3685,7 +3777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -3698,7 +3790,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", ] @@ -3710,7 +3802,7 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.13.2", "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-cloud-kit", "objc2-core-data", "objc2-core-image", @@ -3730,7 +3822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.13.2", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -3742,7 +3834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-foundation 0.2.2", ] @@ -3754,7 +3846,7 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.13.2", "block2 0.5.1", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-core-location", "objc2-foundation 0.2.2", ] @@ -3773,9 +3865,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "open" -version = "5.4.4" +version = "5.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95" dependencies = [ "is-wsl", "libc", @@ -3808,9 +3900,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "5.5.0" +version = "5.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" +checksum = "7f59006ce1e204c30956638535b74d317bca99477b9356dfaec54c37f6f600ab" dependencies = [ "num-traits", ] @@ -4099,6 +4191,19 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plist" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327a4cb2ad49dc48adfdc9da89e22c64d488b508c67a044d71ba729e4521a285" +dependencies = [ + "base64 0.23.1", + "indexmap", + "quick-xml 0.42.0", + "serde", + "time", +] + [[package]] name = "png" version = "0.18.1" @@ -4172,13 +4277,10 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "powerfmt" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" [[package]] name = "presser" @@ -4249,16 +4351,17 @@ checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" [[package]] name = "proptest" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0" dependencies = [ - "bit-set 0.8.0", - "bit-vec 0.8.0", + "bit-set 0.11.1", + "bit-vec 0.10.1", "bitflags 2.13.2", + "chacha20", + "core_detect", "num-traits", - "rand 0.9.5", - "rand_chacha", + "rand", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -4299,6 +4402,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.12" @@ -4329,7 +4441,7 @@ dependencies = [ "bytes", "getrandom 0.4.3", "lru-slab", - "rand 0.10.3", + "rand", "rand_pcg", "ring", "rustc-hash 2.1.3", @@ -4377,16 +4489,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.3" @@ -4398,31 +4500,12 @@ dependencies = [ "rand_core 0.10.1", ] -[[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]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "rand_core" version = "0.10.1" @@ -4440,11 +4523,11 @@ dependencies = [ [[package]] name = "rand_xorshift" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" dependencies = [ - "rand_core 0.9.5", + "rand_core 0.10.1", ] [[package]] @@ -4465,7 +4548,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" dependencies = [ - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-foundation 0.3.2", "objc2-quartz-core 0.3.2", @@ -4508,7 +4591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" dependencies = [ "bytemuck", - "font-types 0.12.5", + "font-types 0.12.7", "once_cell", ] @@ -4519,7 +4602,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea" dependencies = [ "bytemuck", - "font-types 0.12.5", + "font-types 0.12.7", "once_cell", ] @@ -4530,7 +4613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5a0cf4bb6cbce4c29756270d189a5e400ac05e91192161d83a0bcb6e68492e8" dependencies = [ "bytemuck", - "font-types 0.12.5", + "font-types 0.12.7", "once_cell", ] @@ -4554,9 +4637,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +checksum = "580c875845b41ecb1564ad1ae4703044cef774145e3d7a94dc2b3fa7c97868fd" dependencies = [ "bitflags 2.13.2", ] @@ -4667,7 +4750,7 @@ dependencies = [ "js-sys", "libc", "log", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -4720,9 +4803,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "roxmltree" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +checksum = "9da7f59328b09405b181fee1018925fad50f157afb43c0c0f5c522c256dd5907" dependencies = [ "memchr", ] @@ -4781,7 +4864,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "aws-lc-rs", + "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -4790,9 +4875,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -4908,9 +4993,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" dependencies = [ "windows-sys 0.61.2", ] @@ -4961,6 +5046,10 @@ name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "send_wrapper" @@ -4995,14 +5084,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -5030,14 +5119,14 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "serde_spanned" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" dependencies = [ "serde_core", ] @@ -5204,7 +5293,7 @@ dependencies = [ "const-oid", "der", "hex", - "rand 0.10.3", + "rand", "reqwest", "rustls-pki-types", "rustls-webpki", @@ -5345,9 +5434,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.16.2" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" dependencies = [ "serde", ] @@ -5384,7 +5473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ "bitflags 2.13.2", - "calloop 0.14.4", + "calloop 0.14.5", "calloop-wayland-source 0.4.1", "cursor-icon", "libc", @@ -5557,9 +5646,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -5583,7 +5672,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -5677,7 +5766,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -5703,6 +5792,36 @@ dependencies = [ "zune-jpeg", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-skia" version = "0.12.0" @@ -5731,9 +5850,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" dependencies = [ "displaydoc", "zerovec", @@ -5768,9 +5887,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.53.1" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -5806,9 +5925,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.6+spec-1.1.0" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" dependencies = [ "indexmap", "serde_core", @@ -5821,18 +5940,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.15+spec-1.1.0" +version = "0.25.17+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" dependencies = [ "indexmap", "toml_datetime", @@ -5842,18 +5961,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" [[package]] name = "tower" @@ -5967,6 +6086,12 @@ dependencies = [ "rustc-hash 2.1.3", ] +[[package]] +name = "typed-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" + [[package]] name = "typenum" version = "1.20.1" @@ -6016,15 +6141,15 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" [[package]] name = "unicode-ident" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-normalization" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +checksum = "75cd3fb03d5288b3ba9a1ee103ac7e9cbb59f0b03d64f8ddfcc04dd42758a85d" dependencies = [ "tinyvec", ] @@ -6071,6 +6196,35 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde93ff05758a36a851a398a772b0b79a82fbe3c186a46f2696baaeaf84c966b" +dependencies = [ + "base64 0.23.1", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "ureq-proto", + "utf8-zero", +] + +[[package]] +name = "ureq-proto" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841583fc36089d7d59419568ae157f43a5a03a2e5a81ba87c77d1596ef46e922" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -6097,7 +6251,7 @@ dependencies = [ "kurbo", "log", "pico-args", - "roxmltree 0.21.1", + "roxmltree 0.21.2", "rustybuzz", "simplecss", "siphasher", @@ -6111,6 +6265,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -6119,9 +6279,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.26.1" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" dependencies = [ "js-sys", "serde_core", @@ -6208,6 +6368,16 @@ dependencies = [ "vello_common 0.2.0", ] +[[package]] +name = "verifysign" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6acfcc42a8d44f340d001193d97ef56bf33210b51ad760ae8308c97d5241ca51" +dependencies = [ + "core-foundation 0.10.1", + "windows-sys 0.61.2", +] + [[package]] name = "version_check" version = "0.9.5" @@ -6300,7 +6470,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "wasm-bindgen-shared", ] @@ -6343,7 +6513,7 @@ checksum = "3b84b5ac638bfb168196a1a461fcc8f46a294a18b1b6be52133b4e0db122cc9f" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -6471,7 +6641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.41.0", "quote", ] @@ -6509,14 +6679,14 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.2.4" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" +checksum = "d2c8ddfbbf66e501434657400268cefe51b95140a0266236415cda7627732df7" dependencies = [ "jni", "log", "ndk-context", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "url", @@ -6525,9 +6695,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" dependencies = [ "rustls-pki-types", ] @@ -6666,7 +6836,7 @@ dependencies = [ "naga", "naga-types", "ndk-sys", - "objc2 0.6.4", + "objc2 0.6.5", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -6848,7 +7018,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -6857,16 +7027,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -6884,31 +7045,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -6926,96 +7070,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winit" version = "0.30.13" @@ -7038,7 +7134,7 @@ dependencies = [ "libc", "memmap2", "ndk", - "objc2 0.5.2", + "objc2 0.5.3", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", "objc2-ui-kit 0.2.2", @@ -7097,10 +7193,20 @@ dependencies = [ ] [[package]] -name = "winresource" -version = "0.1.31" +name = "winreg" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "winresource" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b" dependencies = [ "toml", "version_check", @@ -7286,9 +7392,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -7303,7 +7409,7 @@ checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] @@ -7375,7 +7481,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "zbus_names", "zvariant", "zvariant_utils", @@ -7383,9 +7489,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.4" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd" dependencies = [ "serde", "winnow", @@ -7415,18 +7521,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.59" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.59" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" dependencies = [ "proc-macro2", "quote", @@ -7450,7 +7556,7 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] @@ -7487,9 +7593,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" dependencies = [ "yoke", "zerofrom", @@ -7498,13 +7604,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -7521,6 +7627,23 @@ dependencies = [ "zopfli", ] +[[package]] +name = "zip" +version = "9.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518b847fd864440c379748ad684c449f293af68978a485a4ff16f7c901c1f7c4" +dependencies = [ + "crc32fast", + "flate2", + "getrandom 0.4.3", + "indexmap", + "memchr", + "nix", + "typed-path", + "windows-sys 0.61.2", + "zopfli", +] + [[package]] name = "zlib-rs" version = "0.6.8" @@ -7535,9 +7658,9 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zopfli" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11" dependencies = [ "bumpalo", "crc32fast", @@ -7562,9 +7685,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.15.0" +version = "5.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7" dependencies = [ "endi", "enumflags2", @@ -7577,14 +7700,14 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.15.0" +version = "5.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "zvariant_utils", ] @@ -7597,7 +7720,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 3.0.6", + "syn 3.0.9", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index c4cd90c74..c64d962b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,10 @@ repository = "https://github.com/storytold/designcraft" [workspace.dependencies] # Shared reader and writer; publish the immutable revision before this consumer. -craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.1.0", default-features = false } +craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.1.0", default-features = false } # Separations, ink coverage and the ink manager; same revision as craft-pdf and the krilla patch. -craft-prepress = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.1.0", features = ["flatten"] } +craft-prepress = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.1.0", features = ["flatten"] } +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", features = ["egui"] } # internal designcraft-geom = { path = "crates/geom" } designcraft-color = { path = "crates/color" } @@ -77,20 +78,20 @@ zip = { version = "3", default-features = false, features = ["deflate"] } indd = { git = "https://github.com/storytold/indd-utils", branch = "main" } # Which paths stored in documents may be followed (craft-libs). -craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3" } +craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184" } # Atomic file replacement for saves and exports (craft-libs). -craft-atomic = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3" } +craft-atomic = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184" } # File › Print through the system print spooler (craft-libs). -craft-print = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3" } +craft-print = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184" } # ICC colour management: working spaces, transforms, black point compensation (craft-libs). -craft-cms = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3" } +craft-cms = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184" } # ui # Shared frontend components; keep this dependency above the engine boundary. -craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", features = ["serde"] } +craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", features = ["serde"] } # The application bar as the window's title bar (craft-libs). -craft-window = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", features = ["egui"] } +craft-window = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", features = ["egui"] } egui = "0.37" egui_extras = { version = "0.37", default-features = false, features = ["svg"] } eframe = { version = "0.37", default-features = false } @@ -128,14 +129,14 @@ debug = 0 # Opt-in cancelable macOS termination on the pinned winit 0.30 line. [patch.crates-io] -krilla = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.8.2" } +krilla = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.8.2" } winit = { git = "https://github.com/storytold/winit.git", rev = "a517b3846ee8457d84ee4975110bfda6797c44dc" } -hayro = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.7.1" } -hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.7.0" } -hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.7.2" } -hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.3.0" } -hayro-cmap = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.1.0" } -hayro-postscript = { git = "https://github.com/storytold/craft-libs", rev = "e3b9e239c52c14677be462e94d8920ea243de4f3", version = "=0.1.0" } +hayro = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.7.1" } +hayro-interpret = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.7.0" } +hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.7.2" } +hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.3.0" } +hayro-cmap = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.1.0" } +hayro-postscript = { git = "https://github.com/storytold/craft-libs", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", version = "=0.1.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/apps/designcraft/Cargo.toml b/apps/designcraft/Cargo.toml index 1d47ef762..36415546e 100644 --- a/apps/designcraft/Cargo.toml +++ b/apps/designcraft/Cargo.toml @@ -32,6 +32,7 @@ arboard = { workspace = true } winit = { version = "0.30", default-features = false, features = ["x11"] } [target.'cfg(windows)'.dependencies] +craft-update = { workspace = true } # EnumDisplayDevices (src/gpu.rs): which GPU drives the display, so the window draws on the GPU # that shows it; drawing on a GPU without a monitor reset the display driver. Safe wrappers over # Win32 (MIT); the `user` feature is User32.dll only. diff --git a/apps/designcraft/src/main.rs b/apps/designcraft/src/main.rs index 12f423d79..ac695774b 100644 --- a/apps/designcraft/src/main.rs +++ b/apps/designcraft/src/main.rs @@ -398,6 +398,11 @@ fn control_port_from(source: &str, value: Option) -> Option { } fn main() -> std::process::ExitCode { + // Windows: the update helper is a copy of this program started with `--finish-update`. + #[cfg(windows)] + if let Some(result) = craft_update::self_update::finish(&craft_update::suite::DESIGNCRAFT, &std::env::args_os().skip(1).collect::>()) { + std::process::exit(i32::from(result.is_err())); + } // First, so every start-up record (and the engine's panic hook, installed with the first // Session) is captured; see `logging`. let logger = logging::install(); @@ -553,6 +558,7 @@ fn main() -> std::process::ExitCode { session.recovery_dir = profile::recovery_dir(); let recovered = session.execute("file.recovery.open", &serde_json::json!({})).ok(); let mut app = DesignApp::new(session, services()); + app.set_startup_update_check(true); if let Some(line) = restarted.or_else(|| created.status_line()) { app.status(line); } diff --git a/crates/doc/src/swatch_refs.rs b/crates/doc/src/swatch_refs.rs index 76960b07a..1e79662e9 100644 --- a/crates/doc/src/swatch_refs.rs +++ b/crates/doc/src/swatch_refs.rs @@ -17,6 +17,53 @@ struct Names<'a> { } impl Names<'_> { + /// Rename the swatch references in every paragraph, character, object, cell and table style. + fn styles(&self, styles: &mut crate::styles::Styles) { + let n = self; + for p in &mut styles.paragraph { + n.para(&mut p.para); + n.chars(&mut p.chars); + } + for c in &mut styles.character { + n.chars(&mut c.chars); + } + for o in &mut styles.object { + if let Some(f) = &mut o.fill { + n.fill(f); + } + if let Some(s) = &mut o.stroke { + n.stroke(s); + } + if let Some(tf) = &mut o.text_frame { + n.frame(tf); + } + } + for c in &mut styles.cell { + n.o(&mut c.fill); + if let Some(s) = &mut c.stroke { + n.cell_stroke(s); + } + for s in &mut c.strokes { + n.stroke_attrs(s); + } + } + for t in &mut styles.table { + if let Some(b) = &mut t.border { + n.cell_stroke(b); + } + for b in &mut t.borders { + n.stroke_attrs(b); + } + if let Some(f) = &mut t.alt_rows { + n.alt_fills(f); + } + n.alt_fill_attrs(&mut t.row_fills); + n.alt_fill_attrs(&mut t.column_fills); + n.alt_stroke_attrs(&mut t.row_strokes); + n.alt_stroke_attrs(&mut t.column_strokes); + } + } + fn s(&self, v: &mut String) { if v == self.from { *v = self.to.to_string(); @@ -217,49 +264,7 @@ impl Document { n.s(b); } n.s(&mut self.footnote_options.rule.color); - let styles = self.styles_mut(); - for p in &mut styles.paragraph { - n.para(&mut p.para); - n.chars(&mut p.chars); - } - for c in &mut styles.character { - n.chars(&mut c.chars); - } - for o in &mut styles.object { - if let Some(f) = &mut o.fill { - n.fill(f); - } - if let Some(s) = &mut o.stroke { - n.stroke(s); - } - if let Some(tf) = &mut o.text_frame { - n.frame(tf); - } - } - for c in &mut styles.cell { - n.o(&mut c.fill); - if let Some(s) = &mut c.stroke { - n.cell_stroke(s); - } - for s in &mut c.strokes { - n.stroke_attrs(s); - } - } - for t in &mut styles.table { - if let Some(b) = &mut t.border { - n.cell_stroke(b); - } - for b in &mut t.borders { - n.stroke_attrs(b); - } - if let Some(f) = &mut t.alt_rows { - n.alt_fills(f); - } - n.alt_fill_attrs(&mut t.row_fills); - n.alt_fill_attrs(&mut t.column_fills); - n.alt_stroke_attrs(&mut t.row_strokes); - n.alt_stroke_attrs(&mut t.column_strokes); - } + n.styles(self.styles_mut()); for sp in self.spreads.iter_mut().chain(self.parents.iter_mut()) { for it in &mut Arc::make_mut(sp).items { n.item(Arc::make_mut(it), 0); diff --git a/crates/engine/src/cmd/anchors.rs b/crates/engine/src/cmd/anchors.rs index 72b2f085b..fec81de4b 100644 --- a/crates/engine/src/cmd/anchors.rs +++ b/crates/engine/src/cmd/anchors.rs @@ -78,6 +78,70 @@ pub fn specs() -> Vec { ] } +/// The first open subpath (two anchors or more) of an item. +fn first_open_subpath(d: &Document, id: ItemId) -> Option { + d.item(id).and_then(|it| it.path.subpaths.iter().position(|sp| !sp.closed && sp.anchors.len() > 1)) +} + +/// Close every open subpath of one path. +fn close_open_subpaths(d: &mut Document, id: ItemId) -> Result { + let it = d.item_mut(id).ok_or(designcraft_doc::DocError::NoItem(id))?; + let mut closed = 0; + for sp in it.path.subpaths.iter_mut().filter(|sp| !sp.closed && sp.anchors.len() > 1) { + sp.closed = true; + closed += 1; + } + if closed == 0 { + return Err(bad("path.join", "the path is already closed")); + } + as_path(&mut it.shape); + Ok(json!({"id": id.0, "closed": closed})) +} + +/// Join the open subpaths of two paths at their nearest ends, into the first path. +fn join_open_pair(d: &mut Document, sel: &mut designcraft_doc::Selection, a: ItemId, b: ItemId) -> Result { + let (Some(sa), Some(sb)) = (first_open_subpath(d, a), first_open_subpath(d, b)) else { + return Err(bad("path.join", "select two open paths")); + }; + // b's path in a's space. + let inv_a = to_inner(d, a).ok_or(designcraft_doc::DocError::NoItem(a))?; + let xf_b = to_inner(d, b).ok_or(designcraft_doc::DocError::NoItem(b))?.inverse(); + let m = inv_a * xf_b; + let mut pb = d.item(b).and_then(|it| it.path.subpaths.get(sb).cloned()).ok_or(designcraft_doc::DocError::NoItem(b))?; + for an in &mut pb.anchors { + *an = Anchor { p: m * an.p, h_in: m * an.h_in, h_out: m * an.h_out, kind: an.kind }; + } + let ita = d.item_mut(a).ok_or(designcraft_doc::DocError::NoItem(a))?; + let pa = ita.path.subpaths.get_mut(sa).ok_or_else(|| bad("path.join", "no such subpath"))?; + // The pair of ends nearest each other; then a's end meets b's start. + let ends = |sp: &designcraft_geom::SubPath| (sp.anchors.first().map(|x| x.p), sp.anchors.last().map(|x| x.p)); + let ((Some(a0), Some(a1)), (Some(b0), Some(b1))) = (ends(pa), ends(&pb)) else { + return Err(bad("path.join", "select two open paths")); + }; + let pairs = [(a1, b0, false, false), (a1, b1, false, true), (a0, b0, true, false), (a0, b1, true, true)]; + let Some(&(_, _, rev_a, rev_b)) = pairs.iter().min_by(|x, y| (x.0 - x.1).hypot().total_cmp(&(y.0 - y.1).hypot())) else { + return Err(bad("path.join", "select two open paths")); + }; + if rev_a { + pa.reverse(); + } + if rev_b { + pb.reverse(); + } + let mut rest = pb.anchors.into_iter(); + if let Some(first) = rest.next() { + match pa.anchors.last_mut() { + Some(last) if (last.p - first.p).hypot() < 0.01 => *last = Anchor::with_handles(last.p, last.h_in, first.h_out), + _ => pa.anchors.push(first), + } + } + pa.anchors.extend(rest); + as_path(&mut ita.shape); + d.remove_item(b)?; + *sel = designcraft_doc::Selection::items(vec![a]); + Ok(json!({"id": a.0})) +} + /// Join Path: close one open path, or join two open paths at their nearest ends. fn join_paths(s: &mut Session, p: &Value) -> Result { let ids = super::targets(s, p)?; @@ -90,63 +154,9 @@ fn join_paths(s: &mut Session, p: &Value) -> Result { path_editable(d, *id, "path.join")?; } } - let open = |d: &Document, id: ItemId| d.item(id).and_then(|it| it.path.subpaths.iter().position(|sp| !sp.closed && sp.anchors.len() > 1)); match ids.as_slice() { - [id] => { - let it = d.item_mut(*id).ok_or(designcraft_doc::DocError::NoItem(*id))?; - let mut closed = 0; - for sp in it.path.subpaths.iter_mut().filter(|sp| !sp.closed && sp.anchors.len() > 1) { - sp.closed = true; - closed += 1; - } - if closed == 0 { - return Err(bad("path.join", "the path is already closed")); - } - as_path(&mut it.shape); - Ok(json!({"id": id.0, "closed": closed})) - } - [a, b] => { - let (Some(sa), Some(sb)) = (open(d, *a), open(d, *b)) else { - return Err(bad("path.join", "select two open paths")); - }; - // b's path in a's space. - let inv_a = to_inner(d, *a).ok_or(designcraft_doc::DocError::NoItem(*a))?; - let xf_b = to_inner(d, *b).ok_or(designcraft_doc::DocError::NoItem(*b))?.inverse(); - let m = inv_a * xf_b; - let mut pb = d.item(*b).and_then(|it| it.path.subpaths.get(sb).cloned()).ok_or(designcraft_doc::DocError::NoItem(*b))?; - for an in &mut pb.anchors { - *an = Anchor { p: m * an.p, h_in: m * an.h_in, h_out: m * an.h_out, kind: an.kind }; - } - let ita = d.item_mut(*a).ok_or(designcraft_doc::DocError::NoItem(*a))?; - let pa = ita.path.subpaths.get_mut(sa).ok_or_else(|| bad("path.join", "no such subpath"))?; - // The pair of ends nearest each other; then a's end meets b's start. - let ends = |sp: &designcraft_geom::SubPath| (sp.anchors.first().map(|x| x.p), sp.anchors.last().map(|x| x.p)); - let ((Some(a0), Some(a1)), (Some(b0), Some(b1))) = (ends(pa), ends(&pb)) else { - return Err(bad("path.join", "select two open paths")); - }; - let pairs = [(a1, b0, false, false), (a1, b1, false, true), (a0, b0, true, false), (a0, b1, true, true)]; - let Some(&(_, _, rev_a, rev_b)) = pairs.iter().min_by(|x, y| (x.0 - x.1).hypot().total_cmp(&(y.0 - y.1).hypot())) else { - return Err(bad("path.join", "select two open paths")); - }; - if rev_a { - pa.reverse(); - } - if rev_b { - pb.reverse(); - } - let mut rest = pb.anchors.into_iter(); - if let Some(first) = rest.next() { - match pa.anchors.last_mut() { - Some(last) if (last.p - first.p).hypot() < 0.01 => *last = Anchor::with_handles(last.p, last.h_in, first.h_out), - _ => pa.anchors.push(first), - } - } - pa.anchors.extend(rest); - as_path(&mut ita.shape); - d.remove_item(*b)?; - *sel = designcraft_doc::Selection::items(vec![*a]); - Ok(json!({"id": a.0})) - } + [id] => close_open_subpaths(d, *id), + [a, b] => join_open_pair(d, sel, *a, *b), _ => Err(bad("path.join", "select one or two open paths")), } }) diff --git a/crates/engine/src/cmd/datamerge/fill.rs b/crates/engine/src/cmd/datamerge/fill.rs index 9547830a7..629258d6d 100644 --- a/crates/engine/src/cmd/datamerge/fill.rs +++ b/crates/engine/src/cmd/datamerge/fill.rs @@ -128,21 +128,33 @@ struct Rep { text: String, } +/// Where `fill_row` looks for linked images: the merge options and the two search folders. +#[derive(Clone, Copy)] +pub struct Sources<'a> { + pub options: &'a MergeOptions, + pub data_dir: Option<&'a Path>, + pub doc_dir: Option<&'a Path>, +} + +/// Replacements and hyperlinks collected while walking the placeholders of one record. +#[derive(Default)] +struct Pending { + reps: Vec, + links: Vec<(PlaceholderAnchor, HyperlinkDest, String)>, + parent: bool, + report: FillReport, +} + /// Fill `row` (0-based) into the items `maps` points at. `record` is the 1-based source row. pub fn fill_row( doc: &mut designcraft_doc::Document, fields: &[DataField], row: &[String], record: u32, - options: &MergeOptions, maps: &Maps, - data_dir: Option<&Path>, - doc_dir: Option<&Path>, + sources: &Sources, ) -> FillReport { - let mut report = FillReport::default(); - let mut parent = false; - let mut reps: Vec = Vec::new(); - let mut links: Vec<(PlaceholderAnchor, HyperlinkDest, String)> = Vec::new(); + let mut pending = Pending::default(); let placeholders = doc.data_merge.placeholders.clone(); for ph in &placeholders { if !fields.iter().any(|f| f.name == ph.field) { @@ -150,43 +162,11 @@ pub fn fill_row( } let cell = cell_of(fields, row, &ph.field).to_string(); match &ph.anchor { - PlaceholderAnchor::Text { story, start, end } => { - let Some(&mapped) = maps.stories.get(story) else { - if anchor_is_parent(doc, &ph.anchor) { - parent = true; - } - continue; - }; - match ph.role { - PlaceholderRole::Text => reps.push(Rep { story: mapped, start: *start, end: *end, text: cell }), - PlaceholderRole::Hyperlink => { - if let Some(dest) = classify_link(&cell) { - links.push((PlaceholderAnchor::Text { story: mapped, start: *start, end: *end }, dest, ph.field.clone())); - } - } - PlaceholderRole::Image | PlaceholderRole::Qr => {} - } - } - PlaceholderAnchor::Item { id } => { - let Some(&mapped) = maps.items.get(id) else { - if anchor_is_parent(doc, &ph.anchor) { - parent = true; - } - continue; - }; - match ph.role { - PlaceholderRole::Image => place_image(doc, mapped, &ph.field, &cell, record, options, data_dir, doc_dir, &mut report), - PlaceholderRole::Qr => place_qr(doc, mapped, &ph.field, &cell, record, &mut report), - PlaceholderRole::Hyperlink => { - if let Some(dest) = classify_link(&cell) { - links.push((PlaceholderAnchor::Item { id: mapped }, dest, ph.field.clone())); - } - } - PlaceholderRole::Text => {} - } - } + PlaceholderAnchor::Text { story, start, end } => plan_text(doc, maps, ph, (*story, *start, *end), cell, &mut pending), + PlaceholderAnchor::Item { .. } => plan_item(doc, maps, ph, &cell, record, sources, &mut pending), } } + let Pending { mut reps, links, parent, mut report } = pending; typed_reps(doc, fields, row, maps, &placeholders, &mut reps); apply_reps(doc, &reps); for (anchor, dest, field) in links { @@ -207,6 +187,59 @@ pub fn fill_row( report } +fn plan_text( + doc: &designcraft_doc::Document, + maps: &Maps, + ph: &designcraft_doc::Placeholder, + (story, start, end): (StoryId, usize, usize), + cell: String, + pending: &mut Pending, +) { + let Some(&mapped) = maps.stories.get(&story) else { + if anchor_is_parent(doc, &ph.anchor) { + pending.parent = true; + } + return; + }; + match ph.role { + PlaceholderRole::Text => pending.reps.push(Rep { story: mapped, start, end, text: cell }), + PlaceholderRole::Hyperlink => { + if let Some(dest) = classify_link(&cell) { + pending.links.push((PlaceholderAnchor::Text { story: mapped, start, end }, dest, ph.field.clone())); + } + } + PlaceholderRole::Image | PlaceholderRole::Qr => {} + } +} + +fn plan_item( + doc: &mut designcraft_doc::Document, + maps: &Maps, + ph: &designcraft_doc::Placeholder, + cell: &str, + record: u32, + sources: &Sources, + pending: &mut Pending, +) { + let PlaceholderAnchor::Item { id } = &ph.anchor else { return }; + let Some(&mapped) = maps.items.get(id) else { + if anchor_is_parent(doc, &ph.anchor) { + pending.parent = true; + } + return; + }; + match ph.role { + PlaceholderRole::Image => place_image(doc, mapped, &ph.field, cell, record, sources, &mut pending.report), + PlaceholderRole::Qr => place_qr(doc, mapped, &ph.field, cell, record, &mut pending.report), + PlaceholderRole::Hyperlink => { + if let Some(dest) = classify_link(cell) { + pending.links.push((PlaceholderAnchor::Item { id: mapped }, dest, ph.field.clone())); + } + } + PlaceholderRole::Text => {} + } +} + fn typed_reps( doc: &designcraft_doc::Document, fields: &[DataField], @@ -326,17 +359,8 @@ fn create_link(doc: &mut designcraft_doc::Document, anchor: PlaceholderAnchor, d true } -fn place_image( - doc: &mut designcraft_doc::Document, - item: ItemId, - field: &str, - cell: &str, - record: u32, - options: &MergeOptions, - data_dir: Option<&Path>, - doc_dir: Option<&Path>, - report: &mut FillReport, -) { +fn place_image(doc: &mut designcraft_doc::Document, item: ItemId, field: &str, cell: &str, record: u32, sources: &Sources, report: &mut FillReport) { + let Sources { options, data_dir, doc_dir } = *sources; let cands = image_candidates(cell, data_dir, doc_dir); let found = cands.iter().find_map(|p| { // Refuse huge files before reading them; a too-large image counts as missing. diff --git a/crates/engine/src/cmd/datamerge/mod.rs b/crates/engine/src/cmd/datamerge/mod.rs index c07e2fff4..e96feb9fb 100644 --- a/crates/engine/src/cmd/datamerge/mod.rs +++ b/crates/engine/src/cmd/datamerge/mod.rs @@ -18,7 +18,7 @@ use serde_json::{Value, json}; use super::{CommandSpec, bad, cmd, has_doc, id_param, ok, str_param}; use crate::{DocState, Result, Session}; -use fill::{FillReport, Maps, MissingImage, copy_item, fill_row, identity_map}; +use fill::{FillReport, Maps, MissingImage, Sources, copy_item, fill_row, identity_map}; use parse::{ Table, TileInput, extension, fingerprint_of, relative_between, select_records, status_of, table_from_bytes, table_from_grid, table_from_objects, tile, @@ -210,7 +210,8 @@ fn preview_cmd(s: &mut Session, p: &Value) -> Result { let row = source.rows.get((record as usize).saturating_sub(1)).cloned().unwrap_or_default(); let data_dir = source_file(&source, st.path.as_deref().map(Path::new)).as_deref().and_then(Path::parent).map(Path::to_path_buf); let doc_dir = st.path.as_deref().and_then(parent_dir); - fill_row(&mut doc, &source.fields, &row, record as u32, &options, &maps, data_dir.as_deref(), doc_dir.as_deref()); + let sources = Sources { options: &options, data_dir: data_dir.as_deref(), doc_dir: doc_dir.as_deref() }; + fill_row(&mut doc, &source.fields, &row, record as u32, &maps, &sources); st.doc = Arc::new(doc); st.preview_record = Some(record as u32); st.bump_revision(); @@ -245,10 +246,12 @@ fn merge_cmd(s: &mut Session, p: &Value) -> Result { warnings.extend(table.warnings.iter().cloned()); let mut merged = src.clone(); let mut missing = Vec::new(); + let sources = Sources { options: &options, data_dir: data_dir.as_deref(), doc_dir: doc_dir.as_deref() }; + let mut sink = Sink { warnings: &mut warnings, missing: &mut missing, parent: false }; if options.per_page == "multiple" { - tile_records(&mut merged, &src, &table, &selected, &options, data_dir.as_deref(), doc_dir.as_deref(), &mut warnings, &mut missing)?; + tile_records(&mut merged, &src, &table, &selected, &sources, &mut sink)?; } else { - copy_records(&mut merged, &src, &table, &selected, &options, data_dir.as_deref(), doc_dir.as_deref(), &mut warnings, &mut missing)?; + copy_records(&mut merged, &src, &table, &selected, &sources, &mut sink)?; } // Status is written only after every error return. The new document then becomes active, // so this template edit does not push an undo step. The fingerprint stays the last read. @@ -272,25 +275,14 @@ fn merge_cmd(s: &mut Session, p: &Value) -> Result { })) } -fn copy_records( - dst: &mut Document, - src: &Document, - table: &Table, - selected: &[usize], - options: &MergeOptions, - data_dir: Option<&Path>, - doc_dir: Option<&Path>, - warnings: &mut Vec, - missing: &mut Vec, -) -> Result<()> { - let mut parent = false; +fn copy_records(dst: &mut Document, src: &Document, table: &Table, selected: &[usize], sources: &Sources, sink: &mut Sink) -> Result<()> { if let Some(&idx) = selected.first() { let maps = identity_map(dst); - fill_at(dst, table, idx, options, &maps, data_dir, doc_dir, warnings, missing, &mut parent); + fill_at(dst, table, idx, sources, &maps, sink); } for &idx in selected.iter().skip(1) { let maps = append_copy(dst, src)?; - fill_at(dst, table, idx, options, &maps, data_dir, doc_dir, warnings, missing, &mut parent); + fill_at(dst, table, idx, sources, &maps, sink); } Ok(()) } @@ -320,17 +312,8 @@ fn append_copy(dst: &mut Document, src: &Document) -> Result { Ok(maps) } -fn tile_records( - dst: &mut Document, - src: &Document, - table: &Table, - selected: &[usize], - options: &MergeOptions, - data_dir: Option<&Path>, - doc_dir: Option<&Path>, - warnings: &mut Vec, - missing: &mut Vec, -) -> Result<()> { +fn tile_records(dst: &mut Document, src: &Document, table: &Table, selected: &[usize], sources: &Sources, sink: &mut Sink) -> Result<()> { + let options = sources.options; let (block, ids) = block_of(src); let (width, height) = src.page(0).map(|p| (p.width, p.height)).unwrap_or((612.0, 792.0)); let plan = tile(&TileInput { @@ -344,7 +327,7 @@ fn tile_records( count: selected.len(), }); if plan.one_per_page { - warnings.push("Only one record fits on the page, so each record has its own page.".into()); + sink.warnings.push("Only one record fits on the page, so each record has its own page.".into()); } if plan.pages > 1 { let parent = src.page(0).and_then(|p| p.parent); @@ -356,12 +339,11 @@ fn tile_records( } } } - let mut parent = false; for hit in plan.hits { let Some(&idx) = selected.get(hit.index) else { continue }; if hit.index == 0 && hit.page == 0 && hit.col == 0 && hit.row == 0 { let maps = identity_map(dst); - fill_at(dst, table, idx, options, &maps, data_dir, doc_dir, warnings, missing, &mut parent); + fill_at(dst, table, idx, sources, &maps, sink); continue; } let Some((dsi, _)) = dst.page_loc(hit.page) else { continue }; @@ -370,39 +352,37 @@ fn tile_records( let Some(it) = src.item(*id) else { continue }; copy_item(dst, src, it, SpreadRef::Doc(dsi), Vec2::new(hit.dx, hit.dy), &mut maps)?; } - fill_at(dst, table, idx, options, &maps, data_dir, doc_dir, warnings, missing, &mut parent); + fill_at(dst, table, idx, sources, &maps, sink); } Ok(()) } -fn fill_at( - doc: &mut Document, - table: &Table, - idx: usize, - options: &MergeOptions, - maps: &Maps, - data_dir: Option<&Path>, - doc_dir: Option<&Path>, - warnings: &mut Vec, - missing: &mut Vec, - parent: &mut bool, -) { +fn fill_at(doc: &mut Document, table: &Table, idx: usize, sources: &Sources, maps: &Maps, sink: &mut Sink) { let Some(row) = table.rows.get(idx) else { return }; - let report = fill_row(doc, &table.fields, row, (idx as u32).saturating_add(1), options, maps, data_dir, doc_dir); - absorb(report, warnings, missing, parent); + let report = fill_row(doc, &table.fields, row, (idx as u32).saturating_add(1), maps, sources); + sink.absorb(report); } -fn absorb(report: FillReport, warnings: &mut Vec, missing: &mut Vec, parent: &mut bool) { - for w in report.warnings { - if w == PARENT_WARNING { - if *parent { - continue; +/// Warnings and missing images collected across every record, with the parent-page warning kept to one. +struct Sink<'a> { + warnings: &'a mut Vec, + missing: &'a mut Vec, + parent: bool, +} + +impl Sink<'_> { + fn absorb(&mut self, report: FillReport) { + for w in report.warnings { + if w == PARENT_WARNING { + if self.parent { + continue; + } + self.parent = true; } - *parent = true; + self.warnings.push(w); } - warnings.push(w); + self.missing.extend(report.missing); } - missing.extend(report.missing); } fn block_of(doc: &Document) -> (Rect, Vec) { diff --git a/crates/engine/src/cmd/datamerge/parse.rs b/crates/engine/src/cmd/datamerge/parse.rs index c76c37f1e..6cc838c3c 100644 --- a/crates/engine/src/cmd/datamerge/parse.rs +++ b/crates/engine/src/cmd/datamerge/parse.rs @@ -128,7 +128,7 @@ pub fn table_from_objects(rows: &[Value]) -> Result { warnings.push(format!("Row {} has an extra field \"{key}\". It was ignored.", i + 1)); } } - if cells.iter().all(|c| c.is_empty()) { + if cells.iter().all(String::is_empty) { continue; } out.push(cells); @@ -159,38 +159,7 @@ pub fn select_records(count: usize, records: &str, one: u32, range: &str, limit: let i = record_index(one, count)?; idx.push(i); } - "range" => { - if range.trim().is_empty() { - return Err("no records".into()); - } - for token in range.split(',') { - let token = token.trim(); - if token.is_empty() { - continue; - } - if let Some((a, b)) = token.split_once('-') { - let a = parse_record_no(a.trim())?; - let b = parse_record_no(b.trim())?; - if a > b { - return Err(format!("range {a}-{b} is reversed")); - } - let mut n = a; - while n <= b { - let i = record_index(n, count)?; - if seen.insert(i) { - idx.push(i); - } - n = n.saturating_add(1); - } - } else { - let n = parse_record_no(token)?; - let i = record_index(n, count)?; - if seen.insert(i) { - idx.push(i); - } - } - } - } + "range" => select_range(range, count, &mut idx, &mut seen)?, other => return Err(format!("unknown records value \"{other}\"")), } if let Some(limit) = limit { @@ -205,6 +174,41 @@ pub fn select_records(count: usize, records: &str, one: u32, range: &str, limit: Ok(idx) } +/// Expand a comma-separated list of record numbers and `a-b` ranges into `idx`, skipping repeats. +fn select_range(range: &str, count: usize, idx: &mut Vec, seen: &mut std::collections::HashSet) -> Result<(), String> { + if range.trim().is_empty() { + return Err("no records".into()); + } + for token in range.split(',') { + let token = token.trim(); + if token.is_empty() { + continue; + } + let (a, b) = match token.split_once('-') { + Some((a, b)) => (parse_record_no(a.trim())?, parse_record_no(b.trim())?), + None => { + let n = parse_record_no(token)?; + (n, n) + } + }; + if a > b { + return Err(format!("range {a}-{b} is reversed")); + } + let mut n = a; + while n <= b { + let i = record_index(n, count)?; + if seen.insert(i) { + idx.push(i); + } + if n == b { + break; + } + n += 1; + } + } + Ok(()) +} + fn parse_record_no(s: &str) -> Result { s.parse::().map_err(|_| format!("bad record \"{s}\"")) } diff --git a/crates/engine/src/cmd/file/eps_place_tests.rs b/crates/engine/src/cmd/file/eps_place_tests.rs index 9b2c89da9..42d3dd774 100644 --- a/crates/engine/src/cmd/file/eps_place_tests.rs +++ b/crates/engine/src/cmd/file/eps_place_tests.rs @@ -3,7 +3,7 @@ //! These helpers write real files, so they live in their own `_tests.rs` file: the //! file-system guard in `tests_file_guard.rs` checks `src` files but exempts test ones. -use super::*; +use super::{Arc, AssetId, Content, ItemId, Session, base64_encode, json}; pub(crate) fn tiff_eps() -> Vec { let mut tiff = b"II".to_vec(); diff --git a/crates/engine/src/cmd/find.rs b/crates/engine/src/cmd/find.rs index a40e8da51..a0afcfbb6 100644 --- a/crates/engine/src/cmd/find.rs +++ b/crates/engine/src/cmd/find.rs @@ -323,18 +323,38 @@ fn replacement(p: &Value) -> String { if bool_or(p, "grep", false) { translate_replacement(c) } else { metachars(c) } } +/// Repair byte validity of a text selection once against the final story, without remapping valid offsets. +fn repair_selection(t: &mut designcraft_doc::TextSel, st: &designcraft_doc::Story) { + let range = t.range(); + let start = floor_char_boundary(&st.text, range.start); + let mut end = range.end.min(st.len()); + if t.is_caret() { + end = start; + } else if !st.text.is_char_boundary(end) { + let floor = floor_char_boundary(&st.text, end); + end = st.text.get(floor..).and_then(|rest| rest.chars().next()).map_or(floor, |ch| floor + ch.len_utf8()); + } + (t.anchor, t.focus) = if t.anchor <= t.focus { (start, end) } else { (end, start) }; +} + +/// The character overrides a Change applies to every replacement, from its `attrs` object. +fn char_overrides(attrs: Option<&Value>) -> Result { + let mut over = designcraft_doc::CharAttrs::default(); + if let Some(o) = attrs.and_then(Value::as_object) { + for (k, v) in o { + over.set_json(k, v).map_err(|e| bad("find.change", e))?; + } + } + Ok(over) +} + fn change(s: &mut Session, p: &Value) -> Result { let re = build(p)?; let grep = bool_or(p, "grep", false); let rep = replacement(p); let first_only = bool_or(p, "first", false); let attrs = p.get("attrs").cloned(); - let mut over = designcraft_doc::CharAttrs::default(); - if let Some(o) = attrs.as_ref().and_then(Value::as_object) { - for (k, v) in o { - over.set_json(k, v).map_err(|e| bad("find.change", e))?; - } - } + let over = char_overrides(attrs.as_ref())?; super::text::validate_char_fill(&s.doc()?.doc, &over, "find.change")?; let targets = scope(s, p)?.targets; s.edit(|d, sel| { @@ -361,17 +381,7 @@ fn change(s: &mut Session, p: &Value) -> Result { count += 1; } if let Some(t) = sel.text.as_mut().filter(|t| t.story == sid && t.cell == cell) { - // Repair byte validity once against the final story, without remapping valid offsets. - let range = t.range(); - let start = floor_char_boundary(&st.text, range.start); - let mut end = range.end.min(st.len()); - if t.is_caret() { - end = start; - } else if !st.text.is_char_boundary(end) { - let floor = floor_char_boundary(&st.text, end); - end = st.text.get(floor..).and_then(|rest| rest.chars().next()).map_or(floor, |ch| floor + ch.len_utf8()); - } - (t.anchor, t.focus) = if t.anchor <= t.focus { (start, end) } else { (end, start) }; + repair_selection(t, st); } // `first` is the first match of the whole search, not of each story. if first_only && count > 0 { diff --git a/crates/engine/src/cmd/fonts.rs b/crates/engine/src/cmd/fonts.rs index b1577400c..ee80eb914 100644 --- a/crates/engine/src/cmd/fonts.rs +++ b/crates/engine/src/cmd/fonts.rs @@ -81,36 +81,41 @@ fn list(d: &Document) -> Vec { used_fonts(d).into_iter().map(|font| json!(font)).collect() } -pub(super) fn used_fonts(d: &Document) -> Vec { - let db = designcraft_fonts::FontDb::global().scoped(d.font_scope); - let mut used: BTreeMap<(String, String), (usize, std::collections::BTreeSet)> = BTreeMap::new(); - for_each_story(d, &mut |st| { - let ranges = st.para_ranges(); - for (pi, r) in ranges.iter().enumerate() { - let (_, base) = d.styles.resolve_para(&st.paras[pi]); - for (rr, fmt) in st.runs() { - let n = st.text[rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))].chars().count(); - if n == 0 && !(r.is_empty() && rr.contains(&r.start)) { - continue; - } - let p = d.styles.resolve_char(&base, fmt); - if let Some(f) = d.styles.composite_font(&p.font_family) { - let text = st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or(""); - for c in text.chars() { - if let Some(e) = f.entry(c) { - let entry = used.entry((e.family.clone(), e.style.clone())).or_default(); - entry.0 += 1; - entry.1.insert(c); - } +type FontTally = BTreeMap<(String, String), (usize, std::collections::BTreeSet)>; + +/// Count the characters of every run of one story against the font each resolves to. +fn tally_story(d: &Document, st: &Story, used: &mut FontTally) { + let ranges = st.para_ranges(); + for (pi, r) in ranges.iter().enumerate() { + let (_, base) = d.styles.resolve_para(&st.paras[pi]); + for (rr, fmt) in st.runs() { + let n = st.text[rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))].chars().count(); + if n == 0 && !(r.is_empty() && rr.contains(&r.start)) { + continue; + } + let p = d.styles.resolve_char(&base, fmt); + if let Some(f) = d.styles.composite_font(&p.font_family) { + let text = st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or(""); + for c in text.chars() { + if let Some(e) = f.entry(c) { + let entry = used.entry((e.family.clone(), e.style.clone())).or_default(); + entry.0 += 1; + entry.1.insert(c); } - } else { - let entry = used.entry((p.font_family, p.font_style)).or_default(); - entry.0 += n; - entry.1.extend(st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or("").chars()); } + } else { + let entry = used.entry((p.font_family, p.font_style)).or_default(); + entry.0 += n; + entry.1.extend(st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or("").chars()); } } - }); + } +} + +pub(super) fn used_fonts(d: &Document) -> Vec { + let db = designcraft_fonts::FontDb::global().scoped(d.font_scope); + let mut used = FontTally::new(); + for_each_story(d, &mut |st| tally_story(d, st, &mut used)); let mut out: Vec = used .into_iter() .map(|((family, style), (n, characters))| { diff --git a/crates/engine/src/cmd/style.rs b/crates/engine/src/cmd/style.rs index 5a8d24501..b0081afd1 100644 --- a/crates/engine/src/cmd/style.rs +++ b/crates/engine/src/cmd/style.rs @@ -762,6 +762,48 @@ fn redefine(s: &mut Session, p: &Value, paragraph: bool) -> Result { result } +/// Apply paragraph style `name` over `r` in one story; `clear` drops overrides instead of keeping +/// only those that differ from the style (`resolved` is the style's paragraph and character attributes). +fn apply_para_in_story( + st: &mut designcraft_doc::Story, + r: &std::ops::Range, + name: &str, + clear: bool, + resolved: (&designcraft_doc::ParaProps, &designcraft_doc::CharProps), + styles: &designcraft_doc::Styles, +) { + let (style_para, style_chars) = resolved; + // Text selections are half-open: a selection ending at the next paragraph's + // start must not apply a style to that paragraph too. + let end = if r.is_empty() { r.start } else { r.end.saturating_sub(1) }; + let (first, last) = (st.para_at(r.start), st.para_at(end)); + st.format_paras(r.start..end, |f| { + f.style = name.to_string(); + if clear { + f.para = ParaAttrs::default(); + f.chars = CharAttrs::default(); + } else { + f.para.retain_differences(style_para); + f.chars.retain_differences(style_chars); + } + }); + for (pi, range) in st.para_ranges().into_iter().enumerate().take(last.saturating_add(1)).skip(first) { + let Some(pf) = st.paras.get(pi) else { continue }; + let (_, base) = styles.resolve_para(pf); + let end = range.end.saturating_add(1).min(st.len()); + let range = if clear && !r.is_empty() { range.start.max(r.start)..end.min(r.end) } else { range.start..end }; + st.format_chars(range, |f| { + if clear { + f.over = CharAttrs::default(); + } else { + let mut inherited = base.clone(); + inherited.apply(&styles.char_style_attrs(&f.style)); + f.over.retain_differences(&inherited); + } + }); + } +} + fn apply_para(s: &mut Session, p: &Value) -> Result { let name = str_param(p, "name").ok_or_else(|| bad("style.paragraph.apply", "missing name"))?.to_string(); let clear = p.get("clearOverrides").and_then(Value::as_bool).unwrap_or(false); @@ -773,37 +815,8 @@ fn apply_para(s: &mut Session, p: &Value) -> Result { let styles = d.styles.clone(); let (style_para, style_chars) = styles.resolve_para_style(&name); for t in &targets { - let r = &t.range; if let Some(st) = d.text_story_mut(t.story, t.cell) { - // Text selections are half-open: a selection ending at the next paragraph's - // start must not apply a style to that paragraph too. - let end = if r.is_empty() { r.start } else { r.end.saturating_sub(1) }; - let (first, last) = (st.para_at(r.start), st.para_at(end)); - st.format_paras(r.start..end, |f| { - f.style = name.clone(); - if clear { - f.para = ParaAttrs::default(); - f.chars = CharAttrs::default(); - } else { - f.para.retain_differences(&style_para); - f.chars.retain_differences(&style_chars); - } - }); - for (pi, range) in st.para_ranges().into_iter().enumerate().take(last.saturating_add(1)).skip(first) { - let Some(pf) = st.paras.get(pi) else { continue }; - let (_, base) = styles.resolve_para(pf); - let end = range.end.saturating_add(1).min(st.len()); - let range = if clear && !r.is_empty() { range.start.max(r.start)..end.min(r.end) } else { range.start..end }; - st.format_chars(range, |f| { - if clear { - f.over = CharAttrs::default(); - } else { - let mut inherited = base.clone(); - inherited.apply(&styles.char_style_attrs(&f.style)); - f.over.retain_differences(&inherited); - } - }); - } + apply_para_in_story(st, &t.range, &name, clear, (&style_para, &style_chars), &styles); } } ok() diff --git a/crates/engine/src/tests_ime.rs b/crates/engine/src/tests_ime.rs index 792a6a407..222142c80 100644 --- a/crates/engine/src/tests_ime.rs +++ b/crates/engine/src/tests_ime.rs @@ -4,7 +4,7 @@ use designcraft_tools::{Mods, PointerEvent, PointerKind, ToolKey}; use serde_json::json; -use super::*; +use super::{Arc, Session, ViewInfo, ime_delete_ranges}; fn session() -> Session { let mut s = Session::new(); diff --git a/crates/engine/src/tests_table.rs b/crates/engine/src/tests_table.rs index 2fc4fd338..acb0d2352 100644 --- a/crates/engine/src/tests_table.rs +++ b/crates/engine/src/tests_table.rs @@ -1,6 +1,6 @@ use serde_json::json; -use super::*; +use super::{Session, doc}; fn session_with_frame() -> (Session, u64, u64) { let mut s = Session::new(); diff --git a/crates/epub/src/lib.rs b/crates/epub/src/lib.rs index cd367df67..c3150966f 100644 --- a/crates/epub/src/lib.rs +++ b/crates/epub/src/lib.rs @@ -983,6 +983,20 @@ impl<'a, 'f> StoryWriter<'a, 'f> { out } + /// One table cell with its row and column spans, as `th` or `td`. + fn table_cell(&mut self, text: &Story, cell_tag: &str, (rs, cs): (usize, usize), depth: usize, out: &mut String) { + let _ = write!(out, "<{cell_tag}"); + if rs > 1 { + let _ = write!(out, " rowspan=\"{rs}\""); + } + if cs > 1 { + let _ = write!(out, " colspan=\"{cs}\""); + } + out.push('>'); + self.paragraphs(text, depth, "", out); + let _ = writeln!(out, ""); + } + /// A table: header rows as `thead`, footer rows as `tfoot`, merged cells as row and column /// spans (kept within their row group). fn table(&mut self, t: &Table, depth: usize, out: &mut String) { @@ -1016,16 +1030,7 @@ impl<'a, 'f> StoryWriter<'a, 'f> { } } } - let _ = write!(out, "<{cell_tag}"); - if rs > 1 { - let _ = write!(out, " rowspan=\"{rs}\""); - } - if cs > 1 { - let _ = write!(out, " colspan=\"{cs}\""); - } - out.push('>'); - self.paragraphs(&cell.text, depth, "", out); - let _ = writeln!(out, ""); + self.table_cell(&cell.text, cell_tag, (rs, cs), depth, out); } out.push_str("\n"); } diff --git a/crates/fonts/src/tests_docfonts.rs b/crates/fonts/src/tests_docfonts.rs index 67b1d8748..7d22ece3d 100644 --- a/crates/fonts/src/tests_docfonts.rs +++ b/crates/fonts/src/tests_docfonts.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; -use super::*; +use super::{Arc, DOCUMENT_FONTS_FOLDER, FALLBACK_FAMILY, FontDb, FontSource, MAX_DOCUMENT_FONT_BYTES, MAX_DOCUMENT_FONT_FILES}; use crate::testing::{font_with, font_with_names, font_with_wws, with_fs_type}; /// An empty temporary folder unique to `name` and this process. diff --git a/crates/fonts/src/tests_fallback.rs b/crates/fonts/src/tests_fallback.rs index d9e27ac62..9ed897d3d 100644 --- a/crates/fonts/src/tests_fallback.rs +++ b/crates/fonts/src/tests_fallback.rs @@ -1,6 +1,6 @@ //! CJK fallback follows the character's language (and the script where it is unambiguous). -use super::*; +use super::FontDb; use crate::testing::font_with; const HAN: char = '直'; diff --git a/crates/fonts/src/tests_vertical.rs b/crates/fonts/src/tests_vertical.rs index 3fdf91b3b..a22dea0d7 100644 --- a/crates/fonts/src/tests_vertical.rs +++ b/crates/fonts/src/tests_vertical.rs @@ -3,9 +3,10 @@ //! The synthetic font (Source Sans 3 with `vhea`/`vmtx`, and `VORG` where asked) runs everywhere; //! the checks against Shippori Mincho's own tables run when built with craft-fonts. +use skrifa::MetadataProvider; use skrifa::raw::TableProvider; -use super::*; +use super::{Arc, FontBytes, FontDb, FontFace, FontSource, GlyphId, LocationRef, Size, make_face}; use crate::testing::{font_mapping, font_with, with_base, with_code_pages, with_table_len, with_table_u16, with_vmtx, with_vorg}; use crate::{ShapeContext, feature, shape_vertical, shape_with_context}; diff --git a/crates/idml/src/export.rs b/crates/idml/src/export.rs index d29561601..dbc6a2f42 100644 --- a/crates/idml/src/export.rs +++ b/crates/idml/src/export.rs @@ -440,6 +440,51 @@ impl<'a> Ex<'a> { ) } + /// A `Gradient` element; stop colours missing from the swatches become anonymous colours. + fn gradient_el( + &mut self, + s: &designcraft_color::Swatch, + gradient: &designcraft_color::Gradient, + colors: &mut Vec, + by_value: &mut Vec<(Color, String)>, + ) -> El { + let id = self.sw(&s.name); + let mut g = El::new("Gradient") + .attr("Self", &id) + .attr("Type", if gradient.kind == GradientKind::Radial { "Radial" } else { "Linear" }) + .attr("Name", &s.name) + .attr("ColorEditable", "true") + .attr("ColorRemovable", "true") + .attr("Visible", "true") + .attr("SwatchCreatorID", "7937"); + let d = self.d; + for (i, stop) in gradient.stops.iter().enumerate() { + // A stop on a swatch names it; a plain color names an equal color swatch or + // an unnamed color. + let named = + stop.swatch.as_deref().filter(|n| d.swatch(n).is_some_and(|w| !matches!(w.value, SwatchValue::Gradient { .. } | SwatchValue::None))); + let cid = match (named, by_value.iter().find(|(c, _)| *c == stop.color)) { + (Some(n), _) => self.sw(n), + (None, Some((_, id))) => id.clone(), + (None, None) => { + let cid = format!("Color/{}", self.fresh()); + colors.push(color_el(&cid, "$ID/", "Process", &stop.color, "Normal", (true, true, false))); + by_value.push((stop.color, cid.clone())); + cid + } + }; + let mut e = El::new("GradientStop") + .attr("Self", format!("{}GradientStop{i}", id.replace(['/', ' '], "_"))) + .attr("StopColor", cid) + .attr("Location", pct(stop.offset as f64)); + if i > 0 { + e.set("Midpoint", pct(gradient.stops[i - 1].midpoint as f64)); + } + g.push(e); + } + g + } + // ---------- Graphic.xml ---------- fn graphic_part(&mut self) -> Vec { @@ -449,39 +494,21 @@ impl<'a> Ex<'a> { let mut tints: Vec = Vec::new(); let mut gradients: Vec = Vec::new(); let mut inks: Vec = Vec::new(); - let color_el = |self_id: &str, name: &str, model: &str, c: &Color, over: &str, editable: bool, removable: bool, visible: bool| { - let (space, val) = color_value(c); - El::new("Color") - .attr("Self", self_id) - .attr("Model", model) - .attr("Space", space) - .attr("ColorValue", val) - .attr("ColorOverride", over) - .attr("AlternateSpace", "NoAlternateColor") - .attr("AlternateColorValue", "") - .attr("Name", name) - .attr("ColorEditable", bool_s(editable)) - .attr("ColorRemovable", bool_s(removable)) - .attr("Visible", bool_s(visible)) - .attr("SwatchCreatorID", "7937") - }; // Specials (always present). - colors.push(color_el("Color/Black", "Black", "Process", &Color::cmyk(0.0, 0.0, 0.0, 1.0), "Specialblack", false, false, true)); + colors.push(color_el("Color/Black", "Black", "Process", &Color::cmyk(0.0, 0.0, 0.0, 1.0), "Specialblack", (false, false, true))); let paper = match d.swatch(swatch::PAPER).map(|s| &s.value) { Some(SwatchValue::Paper { color }) => *color, _ => Color::cmyk(0.0, 0.0, 0.0, 0.0), }; let paper = if paper == Color::WHITE { Color::cmyk(0.0, 0.0, 0.0, 0.0) } else { paper }; - colors.push(color_el("Color/Paper", "Paper", "Process", &paper, "Specialpaper", true, false, true)); + colors.push(color_el("Color/Paper", "Paper", "Process", &paper, "Specialpaper", (true, false, true))); colors.push(color_el( "Color/Registration", "Registration", "Registration", &Color::cmyk(1.0, 1.0, 1.0, 1.0), "Specialregistration", - false, - false, - true, + (false, false, true), )); for (i, ink) in [("Process Cyan", 75, 0.61), ("Process Magenta", 15, 0.76), ("Process Yellow", 0, 0.16), ("Process Black", 45, 1.7)].iter().enumerate() @@ -509,7 +536,7 @@ impl<'a> Ex<'a> { match &s.value { SwatchValue::Color { color, color_type } => { let model = if *color_type == ColorType::Spot { "Spot" } else { "Process" }; - colors.push(color_el(&id, &s.name, model, color, "Normal", true, true, true)); + colors.push(color_el(&id, &s.name, model, color, "Normal", (true, true, true))); by_value.push((*color, id.clone())); if *color_type == ColorType::Spot { let mut ink = El::new("Ink"); @@ -551,42 +578,7 @@ impl<'a> Ex<'a> { } for s in &d.swatches { let SwatchValue::Gradient { gradient } = &s.value else { continue }; - let id = self.sw(&s.name); - let mut g = El::new("Gradient") - .attr("Self", &id) - .attr("Type", if gradient.kind == GradientKind::Radial { "Radial" } else { "Linear" }) - .attr("Name", &s.name) - .attr("ColorEditable", "true") - .attr("ColorRemovable", "true") - .attr("Visible", "true") - .attr("SwatchCreatorID", "7937"); - for (i, stop) in gradient.stops.iter().enumerate() { - // A stop on a swatch names it; a plain color names an equal color swatch or - // an unnamed color. - let named = stop - .swatch - .as_deref() - .filter(|n| d.swatch(n).is_some_and(|w| !matches!(w.value, SwatchValue::Gradient { .. } | SwatchValue::None))); - let cid = match (named, by_value.iter().find(|(c, _)| *c == stop.color)) { - (Some(n), _) => self.sw(n), - (None, Some((_, id))) => id.clone(), - (None, None) => { - let cid = format!("Color/{}", self.fresh()); - colors.push(color_el(&cid, "$ID/", "Process", &stop.color, "Normal", true, true, false)); - by_value.push((stop.color, cid.clone())); - cid - } - }; - let mut e = El::new("GradientStop") - .attr("Self", format!("{}GradientStop{i}", id.replace(['/', ' '], "_"))) - .attr("StopColor", cid) - .attr("Location", pct(stop.offset as f64)); - if i > 0 { - e.set("Midpoint", pct(gradient.stops[i - 1].midpoint as f64)); - } - g.push(e); - } - gradients.push(g); + gradients.push(self.gradient_el(s, gradient, &mut colors, &mut by_value)); } for c in colors { root.push(c); @@ -3301,6 +3293,24 @@ fn layer_el(l: &designcraft_doc::Layer) -> El { with_props(el, vec![color]) } +/// A `Color` element. `flags` is (editable, removable, visible). +fn color_el(self_id: &str, name: &str, model: &str, c: &Color, over: &str, (editable, removable, visible): (bool, bool, bool)) -> El { + let (space, val) = color_value(c); + El::new("Color") + .attr("Self", self_id) + .attr("Model", model) + .attr("Space", space) + .attr("ColorValue", val) + .attr("ColorOverride", over) + .attr("AlternateSpace", "NoAlternateColor") + .attr("AlternateColorValue", "") + .attr("Name", name) + .attr("ColorEditable", bool_s(editable)) + .attr("ColorRemovable", bool_s(removable)) + .attr("Visible", bool_s(visible)) + .attr("SwatchCreatorID", "7937") +} + /// A text variable definition with its type's settings element. fn text_variable_el(v: &designcraft_doc::vars::TextVariable) -> El { use designcraft_doc::vars::{Use, VarKind}; diff --git a/crates/idml/src/import.rs b/crates/idml/src/import.rs index 52d66c188..048809dc2 100644 --- a/crates/idml/src/import.rs +++ b/crates/idml/src/import.rs @@ -919,6 +919,45 @@ impl<'r> Importer<'r> { Some((c, ty)) } + /// Custom stroke styles. + fn read_stroke_styles(&mut self, top: &[El]) { + for e in top.iter().filter(|e| matches!(e.local(), "StripedStrokeStyle" | "DashedStrokeStyle" | "DottedStrokeStyle")) { + let (Some(id), Some(name)) = (e.get("Self"), e.get("Name")) else { continue }; + let kind = match e.local() { + "StripedStrokeStyle" => designcraft_doc::StrokeType::Stripes { + bands: nums(e.get("StripeArray").unwrap_or("")) + .as_chunks::<2>() + .0 + .iter() + .map(|c| (c[0] / 100.0, (c[1] - c[0]) / 100.0)) + .collect(), + }, + "DashedStrokeStyle" => designcraft_doc::StrokeType::Dashed { pattern: nums(e.get("DashArray").unwrap_or("")) }, + _ => designcraft_doc::StrokeType::Dotted, + }; + let _ = id; + self.stroke_styles.push(designcraft_doc::StrokeStyleDef { name: name.to_string(), kind }); + } + } + + /// Ink Manager settings: inks converted to process and ink aliases. + fn read_inks(&mut self, top: &[El]) { + for e in top.iter().filter(|e| e.local() == "Ink") { + let Some(name) = e + .get("Name") + .filter(|n| !n.starts_with("$ID/") && !["Process Cyan", "Process Magenta", "Process Yellow", "Process Black"].contains(n)) + else { + continue; + }; + if e.get("ConvertToProcess") == Some("true") { + self.inks.to_process.push(name.to_string()); + } + if let Some(a) = e.get("AliasInkName").filter(|a| !a.is_empty() && *a != name && !a.starts_with("$ID/")) { + self.inks.aliases.push((name.to_string(), a.to_string())); + } + } + } + fn graphics(&mut self, top: &[El]) { let mut order: Vec<(String, Swatch)> = Vec::new(); // Colours. @@ -962,39 +1001,8 @@ impl<'r> Importer<'r> { Swatch { name, value: SwatchValue::Tint { base, tint: (t / 100.0) as f32 }, locked: false, named: true, hidden: false }, )); } - // Custom stroke styles. - for e in top.iter().filter(|e| matches!(e.local(), "StripedStrokeStyle" | "DashedStrokeStyle" | "DottedStrokeStyle")) { - let (Some(id), Some(name)) = (e.get("Self"), e.get("Name")) else { continue }; - let kind = match e.local() { - "StripedStrokeStyle" => designcraft_doc::StrokeType::Stripes { - bands: nums(e.get("StripeArray").unwrap_or("")) - .as_chunks::<2>() - .0 - .iter() - .map(|c| (c[0] / 100.0, (c[1] - c[0]) / 100.0)) - .collect(), - }, - "DashedStrokeStyle" => designcraft_doc::StrokeType::Dashed { pattern: nums(e.get("DashArray").unwrap_or("")) }, - _ => designcraft_doc::StrokeType::Dotted, - }; - let _ = id; - self.stroke_styles.push(designcraft_doc::StrokeStyleDef { name: name.to_string(), kind }); - } - // Ink Manager. - for e in top.iter().filter(|e| e.local() == "Ink") { - let Some(name) = e - .get("Name") - .filter(|n| !n.starts_with("$ID/") && !["Process Cyan", "Process Magenta", "Process Yellow", "Process Black"].contains(n)) - else { - continue; - }; - if e.get("ConvertToProcess") == Some("true") { - self.inks.to_process.push(name.to_string()); - } - if let Some(a) = e.get("AliasInkName").filter(|a| !a.is_empty() && *a != name && !a.starts_with("$ID/")) { - self.inks.aliases.push((name.to_string(), a.to_string())); - } - } + self.read_stroke_styles(top); + self.read_inks(top); for e in top.iter().filter(|e| e.local() == "Gradient") { let Some(id) = e.get("Self") else { continue }; let mut stops: Vec = Vec::new(); @@ -2265,43 +2273,7 @@ impl<'r> Importer<'r> { cell.style = if cell_style == designcraft_doc::NO_CELL_STYLE { String::new() } else { cell_style }; let default_para = cell.text.paras.first().cloned().unwrap_or_default(); cell.text = self.story_with_para(StoryId(0), ce, default_para); - if let Some(fc) = ce.get("FillColor") { - cell.fill = self.swatch_ref(fc); - } - if let Some(tint) = tint(ce.num("FillTint")) { - cell.fill_tint = tint; - } - for (i, k) in ["TopInset", "LeftInset", "BottomInset", "RightInset"].iter().enumerate() { - if let Some(v) = ce.num(k) { - cell.insets[i] = v.max(0.0); - } - } - if let Some(vj) = ce.get("VerticalJustification") { - cell.vj = names::vj_in(vj); - } - if let Some(fb) = ce.get("FirstBaselineOffset") { - cell.first_baseline = names::first_baseline_in(fb); - } - if let Some(min) = ce.num("MinimumFirstBaselineOffset").filter(|v| v.is_finite()) { - cell.first_baseline_min = min.clamp(0.0, 8640.0); - } - cell.rotation = ce.num("RotationAngle").unwrap_or(cell.rotation); - if cell.rotation != 0.0 { - self.rotated_cells += 1; - } - for (i, side) in ["TopEdge", "LeftEdge", "BottomEdge", "RightEdge"].iter().enumerate() { - let attrs = self.cell_stroke_attrs(ce, side); - attrs.apply_to(&mut cell.strokes[i]); - cell.stroke_defined[i] |= !attrs.is_empty(); - let priority = ce.prop(&format!("{side}StrokePriority")).and_then(|v| v.trim().parse::().ok()).unwrap_or(0); - cell.stroke_priorities[i] = priority; - // Preserve style-defined edges and #102's positive-priority local overrides. - cell.border_overrides[i] |= priority > 0; - // No table or table style border is given anywhere, so the built-in 1 pt default must not - // draw over an outer edge the cell explicitly hides (weight 0 or the None swatch), as InDesign draws nothing. - let hidden = attrs.weight == Some(0.0) || attrs.color.as_deref() == Some(swatch::NONE); - cell.border_overrides[i] |= border_unset && hidden; - } + self.read_cell_attrs(&mut cell, ce, border_unset); if let Some(slot) = t.cell_mut(r, c) { *slot = cell; } @@ -2315,6 +2287,47 @@ impl<'r> Importer<'r> { t } + /// A cell's local fill, insets, alignment, rotation and edge strokes. + fn read_cell_attrs(&mut self, cell: &mut designcraft_doc::Cell, ce: &El, border_unset: bool) { + if let Some(fc) = ce.get("FillColor") { + cell.fill = self.swatch_ref(fc); + } + if let Some(tint) = tint(ce.num("FillTint")) { + cell.fill_tint = tint; + } + for (i, k) in ["TopInset", "LeftInset", "BottomInset", "RightInset"].iter().enumerate() { + if let Some(v) = ce.num(k) { + cell.insets[i] = v.max(0.0); + } + } + if let Some(vj) = ce.get("VerticalJustification") { + cell.vj = names::vj_in(vj); + } + if let Some(fb) = ce.get("FirstBaselineOffset") { + cell.first_baseline = names::first_baseline_in(fb); + } + if let Some(min) = ce.num("MinimumFirstBaselineOffset").filter(|v| v.is_finite()) { + cell.first_baseline_min = min.clamp(0.0, 8640.0); + } + cell.rotation = ce.num("RotationAngle").unwrap_or(cell.rotation); + if cell.rotation != 0.0 { + self.rotated_cells += 1; + } + for (i, side) in ["TopEdge", "LeftEdge", "BottomEdge", "RightEdge"].iter().enumerate() { + let attrs = self.cell_stroke_attrs(ce, side); + attrs.apply_to(&mut cell.strokes[i]); + cell.stroke_defined[i] |= !attrs.is_empty(); + let priority = ce.prop(&format!("{side}StrokePriority")).and_then(|v| v.trim().parse::().ok()).unwrap_or(0); + cell.stroke_priorities[i] = priority; + // Preserve style-defined edges and #102's positive-priority local overrides. + cell.border_overrides[i] |= priority > 0; + // No table or table style border is given anywhere, so the built-in 1 pt default must not + // draw over an outer edge the cell explicitly hides (weight 0 or the None swatch), as InDesign draws nothing. + let hidden = attrs.weight == Some(0.0) || attrs.color.as_deref() == Some(swatch::NONE); + cell.border_overrides[i] |= border_unset && hidden; + } + } + /// The format of the text in a `CharacterStyleRange`: its style, and its local attributes over /// the enclosing paragraph range's, inside a tracked change if `outer` (the enclosing format) is. fn range_format(&mut self, c: &El, pchars: &CharAttrs, outer: &CharFormat) -> CharFormat { @@ -2546,10 +2559,9 @@ impl<'r> Importer<'r> { .collect() } - fn spread(&mut self, e: &El, id: SpreadId, master: bool) -> Spread { - let facing = self.settings.facing_pages; - // Page rectangles in IDML spread coordinates. - let mut pages: Vec<(Rect, &El)> = Vec::new(); + /// The page rectangles of a spread element, in IDML spread coordinates. + fn page_rects<'a>(&self, e: &'a El) -> Vec<(Rect, &'a El)> { + let mut pages: Vec<(Rect, &'a El)> = Vec::new(); for p in e.find_all("Page") { let gb = nums(p.get("GeometricBounds").unwrap_or("")); let (t, l, bt, r) = @@ -2559,6 +2571,40 @@ impl<'r> Importer<'r> { let c = xf * Point::new(r, bt); pages.push((Rect::from_points(a, c), p)); } + pages + } + + /// A page's margins and columns, and whether they are local (differ from its parent page's). + fn page_margins(&self, p: &El, r: &Rect, master: bool, parent: Option, side: PageSide) -> (Margins, Columns, bool) { + // A document page's margins are local when they differ from its parent page's; a + // page without its own takes the parent page's. + let from_parent = if master { + None + } else { + parent + .and_then(|id| self.parents.iter().find(|sp| sp.id == id)) + .and_then(|sp| sp.pages.get(sp.parent_page_for_side(side)?)) + .map(|q| (q.margins, q.columns.clone())) + }; + let (margins, columns) = match (p.find("MarginPreference"), &from_parent) { + (Some(m), _) => { + let (margins, columns) = margins_of(m); + (margins, fit_columns(columns, &margins, r.width())) + } + (None, Some(own)) => own.clone(), + (None, None) => { + let (margins, columns) = self.default_margins.clone().unwrap_or((Margins::uniform(36.0), Columns::default())); + (margins, fit_columns(columns, &margins, r.width())) + } + }; + let local_margins = from_parent.is_some_and(|(m, c)| m != margins || c != columns); + (margins, columns, local_margins) + } + + fn spread(&mut self, e: &El, id: SpreadId, master: bool) -> Spread { + let facing = self.settings.facing_pages; + // Page rectangles in IDML spread coordinates. + let pages = self.page_rects(e); let min_x = pages.iter().map(|(r, _)| r.x0).fold(f64::INFINITY, f64::min); let min_y = pages.iter().map(|(r, _)| r.y0).fold(f64::INFINITY, f64::min); let (min_x, min_y) = if min_x.is_finite() { (min_x, min_y) } else { (0.0, 0.0) }; @@ -2585,28 +2631,7 @@ impl<'r> Importer<'r> { PageSide::Right }; let parent = p.get("AppliedMaster").and_then(|m| self.parent_ids.get(m)).copied(); - // A document page's margins are local when they differ from its parent page's; a - // page without its own takes the parent page's. - let from_parent = if master { - None - } else { - parent - .and_then(|id| self.parents.iter().find(|sp| sp.id == id)) - .and_then(|sp| sp.pages.get(sp.parent_page_for_side(side)?)) - .map(|q| (q.margins, q.columns.clone())) - }; - let (margins, columns) = match (p.find("MarginPreference"), &from_parent) { - (Some(m), _) => { - let (margins, columns) = margins_of(m); - (margins, fit_columns(columns, &margins, r.width())) - } - (None, Some(own)) => own.clone(), - (None, None) => { - let (margins, columns) = self.default_margins.clone().unwrap_or((Margins::uniform(36.0), Columns::default())); - (margins, fit_columns(columns, &margins, r.width())) - } - }; - let local_margins = from_parent.is_some_and(|(m, c)| m != margins || c != columns); + let (margins, columns, local_margins) = self.page_margins(p, r, master, parent, side); out_pages.push(Page { id: pid, width: r.width(), diff --git a/crates/idml/src/xml.rs b/crates/idml/src/xml.rs index 374e70c7f..62b853368 100644 --- a/crates/idml/src/xml.rs +++ b/crates/idml/src/xml.rs @@ -88,7 +88,7 @@ impl El { if let Some(v) = self.get(k) { return Some(v.to_string()); } - self.prop_el(k).map(|e| e.text_content()) + self.prop_el(k).map(El::text_content) } pub fn prop_el(&self, k: &str) -> Option<&El> { self.find("Properties").and_then(|p| p.find(k)) diff --git a/crates/pdf/src/rtl_tests.rs b/crates/pdf/src/rtl_tests.rs index 53db2ac25..82974029e 100644 --- a/crates/pdf/src/rtl_tests.rs +++ b/crates/pdf/src/rtl_tests.rs @@ -8,7 +8,7 @@ use designcraft_doc::{Align, Document, ParaAttrs, ParaFormat, SpreadRef, TextDir use designcraft_geom::Rect; use crate::tests::extract_text; -use crate::*; +use crate::{PdfOptions, export_pdf}; const HEBREW: &str = "DC Test PDF Hebrew"; const ARABIC: &str = "DC Test PDF Arabic"; diff --git a/crates/pdf/src/text.rs b/crates/pdf/src/text.rs index 38f26eaf9..ba44cf713 100644 --- a/crates/pdf/src/text.rs +++ b/crates/pdf/src/text.rs @@ -234,6 +234,34 @@ impl Exporter<'_> { } } + /// One line's glyphs as tagged runs; a glyph that is turned or slanted gets a transform of its own. + fn line_glyphs(&mut self, s: &mut Surface, cs: &ComposedStory, ft: &FrameText, l: &designcraft_compose::Line, story: &str) { + let gs = &l.glyphs; + let mut i = 0; + while i < gs.len() { + let g = &gs[i]; + if !g.visible || g.sx <= 0.0 || g.sy <= 0.0 { + i += 1; + continue; + } + // Vertical type: an upright glyph turns back a quarter about its em box centre. + // Shatai slants each glyph about its own em box centre first. + let turn = if ft.vertical { g.vertical_xf(l.baseline) } else { None }; + let shatai = cs.styles.get(g.style as usize).and_then(|st| st.shatai_xf(g, l.baseline)); + if turn.is_some() || shatai.is_some() { + let m = turn.unwrap_or(Affine::IDENTITY) * shatai.unwrap_or(Affine::IDENTITY); + s.push_transform(&crate::export::tf(m)); + self.tagged_run(s, cs, &gs[i..i + 1], l.baseline, story); + s.pop(); + i += 1; + continue; + } + let j = run_end(ft, gs, i); + self.tagged_run(s, cs, &gs[i..j], l.baseline, story); + i = j; + } + } + pub(crate) fn frame_text(&mut self, s: &mut Surface, cs: &ComposedStory, ft: &FrameText, story: &str) { // Paragraph shading and rules under the text. self.as_artifact(s, |me, s| { @@ -261,30 +289,7 @@ impl Exporter<'_> { }); } for l in &ft.lines { - let gs = &l.glyphs; - let mut i = 0; - while i < gs.len() { - let g = &gs[i]; - if !g.visible || g.sx <= 0.0 || g.sy <= 0.0 { - i += 1; - continue; - } - // Vertical type: an upright glyph turns back a quarter about its em box centre. - // Shatai slants each glyph about its own em box centre first. - let turn = if ft.vertical { g.vertical_xf(l.baseline) } else { None }; - let shatai = cs.styles.get(g.style as usize).and_then(|st| st.shatai_xf(g, l.baseline)); - if turn.is_some() || shatai.is_some() { - let m = turn.unwrap_or(Affine::IDENTITY) * shatai.unwrap_or(Affine::IDENTITY); - s.push_transform(&crate::export::tf(m)); - self.tagged_run(s, cs, &gs[i..i + 1], l.baseline, story); - s.pop(); - i += 1; - continue; - } - let j = run_end(ft, gs, i); - self.tagged_run(s, cs, &gs[i..j], l.baseline, story); - i = j; - } + self.line_glyphs(s, cs, ft, l, story); } self.as_artifact(s, |me, s| { for (rule, r) in text_rules(cs, ft, false) { diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 16d20d2be..5032e9b5c 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -555,6 +555,48 @@ impl Renderer { self.drawing_items.pop(); } + /// A group: children carry their own transforms relative to the group. + fn draw_group(&mut self, ctx: &mut RenderContext, f: &Frame, it: &Item, xf: Affine, page: ShownOn) { + // Isolate Blending / Knockout: the group is its own layer (blend modes stop at it). + let layered = it.opacity < 0.999 || it.blend != DcBlend::Normal || it.isolate || it.knockout; + if layered { + ctx.set_transform(Affine::IDENTITY); + ctx.push_layer(None, Some(blend_mode(it.blend)), Some(it.opacity), None, None); + } + // Gradient feather: the group's objects drawn together, then faded as one. + let feather = it.effects.gradient_feather.on && f.opts.quality != DisplayQuality::Fast; + if feather { + ctx.set_transform(Affine::IDENTITY); + ctx.push_layer(None, None, None, None, None); + } + if it.knockout { + // Knockout: each object first clears its area of those behind it, so it composites + // with the group's backdrop rather than with its siblings. + for c in it.shown_children() { + if !c.hidden { + ctx.set_transform(Affine::IDENTITY); + ctx.push_layer(None, Some(BlendMode::new(Mix::Normal, Compose::DestOut)), None, None, None); + ctx.set_transform(f.view * xf * c.xf); + ctx.set_paint(peniko::Color::BLACK); + ctx.fill_path(&c.path.to_bezpath()); + ctx.pop_layer(); + } + self.draw_item(ctx, f, c, xf, page); + } + } else { + for c in it.shown_children() { + self.draw_item(ctx, f, c, xf, page); + } + } + if feather { + fx::gradient_feather_mask(ctx, f, it, xf); + ctx.pop_layer(); + } + if layered { + ctx.pop_layer(); + } + } + fn draw_item_inner(&mut self, ctx: &mut RenderContext, f: &Frame, it: &Item, parent: Affine, page: ShownOn) { if it.hidden || f.opts.hidden.contains(&it.id) || f.opts.printing_only && it.nonprinting { return; @@ -564,45 +606,7 @@ impl Renderer { // A frame with items pasted into it draws like any other object (draw_body: fill, the items // clipped to the frame, stroke), so its effects apply to all of it. if !it.children().is_empty() && !it.has_nested_items() { - // Groups: children carry their own transforms relative to the group. - // Isolate Blending / Knockout: the group is its own layer (blend modes stop at it). - let layered = it.opacity < 0.999 || it.blend != DcBlend::Normal || it.isolate || it.knockout; - if layered { - ctx.set_transform(Affine::IDENTITY); - ctx.push_layer(None, Some(blend_mode(it.blend)), Some(it.opacity), None, None); - } - // Gradient feather: the group's objects drawn together, then faded as one. - let feather = it.effects.gradient_feather.on && f.opts.quality != DisplayQuality::Fast; - if feather { - ctx.set_transform(Affine::IDENTITY); - ctx.push_layer(None, None, None, None, None); - } - if it.knockout { - // Knockout: each object first clears its area of those behind it, so it composites - // with the group's backdrop rather than with its siblings. - for c in it.shown_children() { - if !c.hidden { - ctx.set_transform(Affine::IDENTITY); - ctx.push_layer(None, Some(BlendMode::new(Mix::Normal, Compose::DestOut)), None, None, None); - ctx.set_transform(f.view * xf * c.xf); - ctx.set_paint(peniko::Color::BLACK); - ctx.fill_path(&c.path.to_bezpath()); - ctx.pop_layer(); - } - self.draw_item(ctx, f, c, xf, page); - } - } else { - for c in it.shown_children() { - self.draw_item(ctx, f, c, xf, page); - } - } - if feather { - fx::gradient_feather_mask(ctx, f, it, xf); - ctx.pop_layer(); - } - if layered { - ctx.pop_layer(); - } + self.draw_group(ctx, f, it, xf, page); return; } // Composed text stays inside its frame apart from glyph overhang (italics, swashes, big diff --git a/crates/textimport/src/xlsx.rs b/crates/textimport/src/xlsx.rs index 83bea3c87..a26665ecc 100644 --- a/crates/textimport/src/xlsx.rs +++ b/crates/textimport/src/xlsx.rs @@ -91,6 +91,34 @@ fn general(f: f64) -> String { } } +/// The first and last column used on the header row, if that row has any cells. +fn header_span(cells: &[(usize, usize, String)], header_row: usize) -> Option<(usize, usize)> { + cells.iter().filter(|(r, _, _)| *r == header_row).fold(None, |span, (_, c, _)| match span { + None => Some((*c, *c)), + Some((lo, hi)) => Some((lo.min(*c), hi.max(*c))), + }) +} + +/// One row's values laid into `width` columns starting at column `min_c`; values outside that +/// span come back separately (only the non-empty ones), so the caller can warn. +fn place_row(cols: &[(usize, String)], min_c: usize, width: usize) -> (Vec, Vec) { + let mut row = vec![String::new(); width]; + let mut extra: Vec = Vec::new(); + for (c, v) in cols { + let at = c.checked_sub(min_c).filter(|at| *at < width); + match at { + Some(at) => { + if let Some(cell) = row.get_mut(at) { + *cell = v.clone(); + } + } + None if !v.is_empty() => extra.push(v.clone()), + None => {} + } + } + (row, extra) +} + /// One worksheet as a grid of cached cell text, empty cells kept, for data merge. /// The first used row is the header. Later rows that are entirely empty are left out. /// Cells past the header are kept so the caller can warn. Over 200 columns or 100,000 @@ -117,19 +145,9 @@ pub fn records(bytes: &[u8], sheet: Option<&str>) -> Result>, Im return Err(ImportError::Corrupt("the worksheet is empty".into())); } let header_row = cells.iter().map(|c| c.0).min().unwrap_or(0); - let mut min_c = usize::MAX; - let mut max_c = 0usize; - let mut header_used = false; - for (r, c, _) in &cells { - if *r == header_row { - header_used = true; - min_c = min_c.min(*c); - max_c = max_c.max(*c); - } - } - if !header_used || min_c == usize::MAX { + let Some((min_c, max_c)) = header_span(&cells, header_row) else { return Err(ImportError::Corrupt("the worksheet has no header".into())); - } + }; let width = max_c.saturating_sub(min_c).saturating_add(1); if width > crate::DATA_MERGE_MAX_COLS { return Err(ImportError::Corrupt(format!("the sheet has {width} columns; the limit is {}", crate::DATA_MERGE_MAX_COLS))); @@ -154,26 +172,8 @@ pub fn records(bytes: &[u8], sheet: Option<&str>) -> Result>, Im } let mut out = Vec::with_capacity(data_rows.saturating_add(1)); for (r, cols) in &by_row { - let mut row = vec![String::new(); width]; - let mut extra: Vec = Vec::new(); - for (c, v) in cols { - if *c < min_c { - // Outside the header span. Keep one non-empty value so the caller can warn. - if !v.is_empty() { - extra.push(v.clone()); - } - continue; - } - let at = c - min_c; - if at < width { - if let Some(cell) = row.get_mut(at) { - *cell = v.clone(); - } - } else if !v.is_empty() { - extra.push(v.clone()); - } - } - if *r != header_row && row.iter().all(|c| c.is_empty()) && extra.is_empty() { + let (mut row, extra) = place_row(cols, min_c, width); + if *r != header_row && row.iter().all(String::is_empty) && extra.is_empty() { continue; } row.extend(extra); diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 0a826b69e..3d4a7c6f0 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -44,3 +44,6 @@ designcraft-fonts = { workspace = true, features = ["testing"] } [build-dependencies] serde_json = { workspace = true } toml = "1" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +craft-update = { workspace = true } diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 331b83838..e2b066fb1 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2451,6 +2451,26 @@ fn scrollbar_rects(outer: Rect, screen: Rect, content: Rect, vertical: bool) -> } } +/// How far a scrollbar interaction pans the view along its axis: a thumb drag follows the pointer, +/// a click on the track beside the thumb pages by one view. +fn scrollbar_move(resp: &egui::Response, grabbed: bool, vertical: bool, thumb: Rect, page: f32, k: f32) -> f32 { + if grabbed { + let d = resp.drag_delta(); + return -(if vertical { d.y } else { d.x }) * k; + } + if resp.clicked() + && let Some(p) = resp.interact_pointer_pos() + { + let (p, a, b) = if vertical { (p.y, thumb.min.y, thumb.max.y) } else { (p.x, thumb.min.x, thumb.max.x) }; + if p < a { + return page; + } else if p > b { + return -page; + } + } + 0.0 +} + /// The view's scrollbars, on the right and bottom edges of `outer`: they span the pasteboard; /// dragging a thumb pans, clicking the track pages by one view. fn scrollbars(app: &mut DesignApp, ui: &mut egui::Ui, outer: Rect, screen: Rect, rect: Rect, t: &Tokens) { @@ -2476,21 +2496,8 @@ fn scrollbars(app: &mut DesignApp, ui: &mut egui::Ui, outer: Rect, screen: Rect, if !resp.dragged() { ui.data_mut(|d| d.remove::(grab_id)); } - let along = |p: egui::Vec2| if vertical { p.y } else { p.x }; - let page = along(screen.size()); - let mut moved = 0.0; - if grabbed { - moved = -along(resp.drag_delta()) * k; - } else if resp.clicked() - && let Some(p) = resp.interact_pointer_pos() - { - let (p, a, b) = if vertical { (p.y, thumb.min.y, thumb.max.y) } else { (p.x, thumb.min.x, thumb.max.x) }; - if p < a { - moved = page; - } else if p > b { - moved = -page; - } - } + let page = if vertical { screen.size().y } else { screen.size().x }; + let moved = scrollbar_move(&resp, grabbed, vertical, thumb, page, k); if moved != 0.0 && moved.is_finite() { pan(app, &xf, if vertical { vec2(0.0, moved) } else { vec2(moved, 0.0) }); } @@ -3686,6 +3693,37 @@ fn guide_at(app: &DesignApp, xf: &Xf, p: Pos2) -> Option<(designcraft_doc::Sprea None } +/// A primary press at `o` on a ruler starts a new guide; on a guide it selects and grabs it. +fn begin_guide_drag(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, rect: Rect, xf: &Xf, o: Pos2) -> Option { + let full = resp.rect; + if full.contains(o) && !rect.contains(o) { + // In a ruler (not the corner). + let orientation = if o.y < rect.min.y && o.x >= rect.min.x { + Some(designcraft_doc::Orientation::Horizontal) + } else if o.x < rect.min.x && o.y >= rect.min.y { + Some(designcraft_doc::Orientation::Vertical) + } else { + None + }; + if let Some(orientation) = orientation { + return Some(GuideDrag { orientation, from: None, at: Some(o), mods: egui::Modifiers::NONE }); + } + } else if rect.contains(o) + && matches!(app.session.tool_id(), "selection" | "directSelection") + && let Some((r, pi, gi, orientation)) = guide_at(app, xf, o) + { + let shift = ui.input(|i| i.modifiers.shift); + // A press on a guide selects it (Shift: on release, as a click adds or removes it). + let selected = app.session.active().is_some_and(|st| st.selection.guides.contains(&GuideSel { spread: r, page: pi, index: gi })); + if !shift && !selected { + let _ = app.run("guide.select", json!({"spread": r, "page": pi, "index": gi})); + } + ui.data_mut(|d| d.insert_temp(egui::Id::unique(GUIDE_PRESS), GuidePress { origin: o, shift })); + return Some(GuideDrag { orientation, from: Some((r, pi, gi)), at: Some(o), mods: egui::Modifiers::NONE }); + } + None +} + /// Ruler guides: drag one out of a ruler, drag an existing one (Selection tools) to move it, drop /// it back on a ruler to delete it. A press near an unlocked guide takes it even over an object: /// the guide is selected and the object is not. Returns true from the press to the release, so @@ -3697,32 +3735,7 @@ fn guide_drag(app: &mut DesignApp, ui: &mut egui::Ui, resp: &egui::Response, rec let mut drag: Option = ui.data(|d| d.get_temp(id)); if drag.is_none() && pressed && resp.hovered() && app.ui.guides && !app.ui.guides_locked { let Some(o) = origin else { return false }; - let full = resp.rect; - if full.contains(o) && !rect.contains(o) { - // In a ruler (not the corner). - let orientation = if o.y < rect.min.y && o.x >= rect.min.x { - Some(designcraft_doc::Orientation::Horizontal) - } else if o.x < rect.min.x && o.y >= rect.min.y { - Some(designcraft_doc::Orientation::Vertical) - } else { - None - }; - if let Some(orientation) = orientation { - drag = Some(GuideDrag { orientation, from: None, at: Some(o), mods: egui::Modifiers::NONE }); - } - } else if rect.contains(o) - && matches!(app.session.tool_id(), "selection" | "directSelection") - && let Some((r, pi, gi, orientation)) = guide_at(app, xf, o) - { - let shift = ui.input(|i| i.modifiers.shift); - // A press on a guide selects it (Shift: on release, as a click adds or removes it). - let selected = app.session.active().is_some_and(|st| st.selection.guides.contains(&GuideSel { spread: r, page: pi, index: gi })); - if !shift && !selected { - let _ = app.run("guide.select", json!({"spread": r, "page": pi, "index": gi})); - } - ui.data_mut(|d| d.insert_temp(egui::Id::unique(GUIDE_PRESS), GuidePress { origin: o, shift })); - drag = Some(GuideDrag { orientation, from: Some((r, pi, gi)), at: Some(o), mods: egui::Modifiers::NONE }); - } + drag = begin_guide_drag(app, ui, resp, rect, xf, o); } let Some(mut g) = drag else { return false }; if let Some(p) = latest { diff --git a/crates/ui-egui/src/guide_selection_tests.rs b/crates/ui-egui/src/guide_selection_tests.rs index d69026712..626a7b826 100644 --- a/crates/ui-egui/src/guide_selection_tests.rs +++ b/crates/ui-egui/src/guide_selection_tests.rs @@ -4,7 +4,10 @@ use egui::{Event, Modifiers, PointerButton}; use egui_kittest::Harness; -use super::*; +use super::{ + CanvasLayout, DesignApp, GuideDrag, GuidePress, Mods, Overlay, Point, Pos2, RULER, SpreadRef, ToolKey, Xf, guide_drag_delta, guide_spot, json, + moving_guides, pos2, vec2, +}; fn harness(app: DesignApp) -> Harness<'static, DesignApp> { let mut h = Harness::builder().with_size(vec2(1440.0, 900.0)).build_ui_state( diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index fb52fcfba..3b50bf996 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -33,6 +33,7 @@ pub mod story_editor; pub mod taskbar; pub mod theme; pub mod toolbar; +pub mod updates_ui; pub mod widgets; use std::collections::HashMap; @@ -836,6 +837,7 @@ pub struct DesignApp { pub cursors: cursors::Images, /// A panel's live preview (a slider being dragged), committed when it is let go. live_preview: LivePreview, + pub(crate) updates_rt: updates_ui::Runtime, } /// The preview a panel opened with `edit.preview`: it stays open while a panel holds it each frame. @@ -935,6 +937,7 @@ impl DesignApp { quitting: None, cursors: cursors::Images::default(), live_preview: LivePreview::default(), + updates_rt: updates_ui::Runtime::default(), } } @@ -1365,6 +1368,50 @@ impl DesignApp { self.ui.flyout = None; } + /// First frame only: load the saved colour profiles and make the saved Color Settings active. + fn apply_saved_color_settings(&mut self) { + if self.color_applied { + return; + } + self.color_applied = true; + for path in self.ui.color_profiles.clone() { + if let Err(e) = self.session.execute("color.loadProfile", &json!({"path": path})) { + log::warn!("Color Settings: {e}"); + self.status_error("Color Settings", e); + } + } + if let Some(cs) = self.ui.color_settings.clone() { + let known = cs.with_known_profiles(); + for (saved, used) in [(&cs.rgb, &known.rgb), (&cs.cmyk, &known.cmyk)] { + if saved != used { + let e = format!("saved profile {saved} is not available; using {used}"); + log::warn!("Color Settings: {e}"); + self.status_error("Color Settings", e); + } + } + let _ = designcraft_color::cms::set_active(&known); + } + } + + /// Install the UI fonts on the first frame, and again when the language or the system fonts change. + fn update_fonts(&mut self, ctx: &egui::Context) { + if !self.styled { + // The UI scale is a preference (applied above); egui's own Cmd+Plus/Minus/0 would rescale the + // whole window instead of the canvas, only to be reset next frame. + ctx.options_mut(|o| o.zoom_with_keyboard = false); + self.fonts_system = theme::install_fonts(ctx, &self.ui.language); + self.fonts_lang = self.ui.language.clone(); + self.styled = true; + self.restyle = true; + } else { + if self.fonts_lang != self.ui.language || (!self.fonts_system && theme::system_fonts_ready()) { + self.fonts_system = theme::install_fonts(ctx, &self.ui.language); + self.fonts_lang = self.ui.language.clone(); + } + self.fonts_ready = true; + } + } + /// Queue and deadline work, run once per app callback. /// /// `logic_tick` runs on every call (a first pass, a later discarded pass, or a hidden @@ -1384,6 +1431,7 @@ impl DesignApp { fn logic_tick(&mut self, ctx: &egui::Context) { self.update_screenshot_epoch(ctx); self.drain_control(ctx); + updates_ui::tick(self, ctx); if self.fonts_ready && !self.host_order_pending() { self.drain_inbox(); self.open_os_documents(); @@ -1414,45 +1462,12 @@ impl DesignApp { self.egui_ctx = Some(ctx.clone()); } self.window_maximized = ctx.input(|i| i.viewport().maximized.unwrap_or(false)); - if !self.color_applied { - self.color_applied = true; - for path in self.ui.color_profiles.clone() { - if let Err(e) = self.session.execute("color.loadProfile", &json!({"path": path})) { - log::warn!("Color Settings: {e}"); - self.status_error("Color Settings", e); - } - } - if let Some(cs) = self.ui.color_settings.clone() { - let known = cs.with_known_profiles(); - for (saved, used) in [(&cs.rgb, &known.rgb), (&cs.cmyk, &known.cmyk)] { - if saved != used { - let e = format!("saved profile {saved} is not available; using {used}"); - log::warn!("Color Settings: {e}"); - self.status_error("Color Settings", e); - } - } - let _ = designcraft_color::cms::set_active(&known); - } - } + self.apply_saved_color_settings(); let scale = self.ui.ui_scale.clamp(0.5, 3.0); if (ctx.zoom_factor() - scale).abs() > 1e-3 { ctx.set_zoom_factor(scale); } - if !self.styled { - // The UI scale is a preference (applied above); egui's own Cmd+Plus/Minus/0 would rescale the - // whole window instead of the canvas, only to be reset next frame. - ctx.options_mut(|o| o.zoom_with_keyboard = false); - self.fonts_system = theme::install_fonts(ctx, &self.ui.language); - self.fonts_lang = self.ui.language.clone(); - self.styled = true; - self.restyle = true; - } else { - if self.fonts_lang != self.ui.language || (!self.fonts_system && theme::system_fonts_ready()) { - self.fonts_system = theme::install_fonts(ctx, &self.ui.language); - self.fonts_lang = self.ui.language.clone(); - } - self.fonts_ready = true; - } + self.update_fonts(ctx); if !self.fonts_system { // The font scan runs in the background: look again shortly. ctx.request_repaint_after(std::time::Duration::from_millis(250)); @@ -2149,6 +2164,7 @@ impl DesignApp { story_editor::show(self, &ctx); dialogs::show(self, &ctx); about::show(self, &ctx); + updates_ui::show(self, &ctx); menus::palette(self, &ctx); // The IME is observed once, here: after the canvas, the floating panels, the story editor, // dialogs and Quick Apply have all applied this pass's commands and focus changes. diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index fda15c6bb..e3226044b 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -348,6 +348,7 @@ pub const UI_COMMANDS: &[(&str, &str, Option<&str>, &str)] = &[ None, "{open?: true, tab?: \"about\"|\"contributors\"|\"models\"} — the About window: splash with community links, contributor and model credits (open: false closes it)", ), + ("help.checkForUpdates", "Check for Updates…", None, "{} — checks for newer releases and offers to update"), ("window.toolsDoubleColumn", "Tools: Double Column", None, "{}"), ( "window.workspace", @@ -990,6 +991,7 @@ pub const MENUS: &[(&str, &[&str])] = &[ "-", "cmd:file.newSample", "-", + "ui:help.checkForUpdates", "ui:help.about", ], ), @@ -1911,6 +1913,7 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option crate::updates_ui::check(app), "help.app" => match p.get("app").and_then(Value::as_str) { Some(slug) if !slug.is_empty() && slug.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') => { let url = format!("{}/apps/{slug}", designcraft_engine::links::WEBSITE); @@ -2418,6 +2421,7 @@ pub enum AppMenuItem { /// The macOS application menu. Settings… is `app.preferences`, which `native_menu_tree` leaves out of Edit. pub const MAC_APP_MENU: &[AppMenuItem] = &[ AppMenuItem::Cmd { id: "dc-about", label: "About DesignCraft", command: "help.about" }, + AppMenuItem::Cmd { id: "dc-updates", label: "Check for Updates…", command: "help.checkForUpdates" }, AppMenuItem::Cmd { id: "dc-discord", label: "Join the ArtCraft Discord…", command: "help.discord" }, AppMenuItem::Sep, AppMenuItem::Cmd { id: "dc-settings", label: "Settings…", command: "app.preferences" }, diff --git a/crates/ui-egui/src/panel_docking_tests.rs b/crates/ui-egui/src/panel_docking_tests.rs index 2732313de..73f98f612 100644 --- a/crates/ui-egui/src/panel_docking_tests.rs +++ b/crates/ui-egui/src/panel_docking_tests.rs @@ -1,4 +1,8 @@ -use super::*; +use super::{ + CANVAS, Column, DOCK_LABELS, DesignApp, Floating, ICON_GROUPS, Layout, Node, PanelContent, Placed, Side, Value, area_id, cancel_gesture, + close_visible, default_layout, dock_columns, hit_id, icon_column, in_iconic_column, json, layout_of, modal_open, open_window_drops, panel_menu, + set_iconic, showing, tab_group, tab_id, valid, window_groups, +}; use egui::{Event, Modifiers, PointerButton, Pos2, vec2}; use egui_kittest::{Harness, kittest::Queryable}; diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 2084ab128..2b5ef2f69 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -211,6 +211,35 @@ pub(crate) fn preset_name(w: f64, h: f64) -> &'static str { PAGE_PRESETS.iter().find(|p| (p.1 - a).abs() < 0.6 && (p.2 - b).abs() < 0.6).map(|p| p.0).unwrap_or("Custom") } +/// The preset dropdown and orientation toggles for one page; the new (width, height) when one changed. +fn page_preset_row(ui: &mut Ui, lang: &str, w: f64, h: f64) -> Option<(f64, f64)> { + let landscape = w > h; + let b = block(ui, FIELD_H); + let mut size = None; + place(ui, sub(b, 0.0, 0.0, 93.0, FIELD_H), |ui| { + let resp = dropdown(ui, crate::i18n::tr(lang, preset_name(w, h)), 93.0); + egui::Popup::menu(&resp).show(|ui| { + ui.set_min_width(140.0); + for p in PAGE_PRESETS { + if ui.selectable_label(preset_name(w, h) == p.0, crate::i18n::tr(lang, p.0)).clicked() { + size = Some(if landscape { (p.2, p.1) } else { (p.1, p.2) }); + ui.close(); + } + } + }); + }); + place(ui, sub(b, 126.0, 0.0, 52.0, FIELD_H), |ui| { + ui.spacing_mut().item_spacing.x = 4.0; + if icon_toggle(ui, "orient-portrait", !landscape, crate::i18n::tr(lang, "Portrait")).clicked() && landscape { + size = Some((h, w)); + } + if icon_toggle(ui, "orient-landscape", landscape, crate::i18n::tr(lang, "Landscape")).clicked() && !landscape { + size = Some((h, w)); + } + }); + size +} + /// Properties with the Page tool (Properties › Edit Page), as InDesign's: a "Page" band with Back, /// Page Size for this page alone (which page, preset, orientation, W/H), Margins and Columns and /// Quick Actions. Back returns to the Selection tool and the document's properties. @@ -257,30 +286,7 @@ fn page_sections(app: &mut DesignApp, ui: &mut Ui, page: usize) { ui.add_space(3.0); // Preset and orientation: this page alone. let (w, h) = (pg.width, pg.height); - let landscape = w > h; - let b = block(ui, FIELD_H); - let mut size = None; - place(ui, sub(b, 0.0, 0.0, 93.0, FIELD_H), |ui| { - let resp = dropdown(ui, crate::i18n::tr(&lang, preset_name(w, h)), 93.0); - egui::Popup::menu(&resp).show(|ui| { - ui.set_min_width(140.0); - for p in PAGE_PRESETS { - if ui.selectable_label(preset_name(w, h) == p.0, crate::i18n::tr(&lang, p.0)).clicked() { - size = Some(if landscape { (p.2, p.1) } else { (p.1, p.2) }); - ui.close(); - } - } - }); - }); - place(ui, sub(b, 126.0, 0.0, 52.0, FIELD_H), |ui| { - ui.spacing_mut().item_spacing.x = 4.0; - if icon_toggle(ui, "orient-portrait", !landscape, crate::i18n::tr(&lang, "Portrait")).clicked() && landscape { - size = Some((h, w)); - } - if icon_toggle(ui, "orient-landscape", landscape, crate::i18n::tr(&lang, "Landscape")).clicked() && !landscape { - size = Some((h, w)); - } - }); + let mut size = page_preset_row(ui, &lang, w, h); ui.add_space(3.0); let b = block(ui, 2.0 * FIELD_H + 5.0); for (row, (label, v, key)) in [("W:", w, "width"), ("H:", h, "height")].into_iter().enumerate() { @@ -4171,21 +4177,16 @@ fn selected_container(app: &DesignApp) -> Option<(designcraft_doc::Item, Vec) -> Option { +/// The chosen (column, row) of the Flex Layout alignment grid, and whether a space justification +/// leaves only one axis (`Some(true)` for a row layout). +fn flex_grid_choice(f: Option<&designcraft_doc::FlexLayout>) -> ((usize, usize), Option) { use designcraft_doc::{FlexAlign, FlexJustify}; - let t = Tokens::get(ui.ctx()); - let resp = ui.interact(r, ui.scope_id().with("flex_grid"), Sense::click()); - ui.painter().rect_stroke(r, 2.0, Stroke::new(1.0, t.field_border), StrokeKind::Inside); - let cell = r.width() / 3.0; let pos = |a: FlexAlign| match a { FlexAlign::Start => 0, FlexAlign::Center => 1, FlexAlign::End => 2, }; - // The chosen (column, row), and whether a space justification leaves only one axis. - let (chosen, spread_row) = match f { + match f { Some(f) => { let main = match f.justify { FlexJustify::Center => 1, @@ -4197,7 +4198,17 @@ fn flex_grid(ui: &mut Ui, r: Rect, f: Option<&designcraft_doc::FlexLayout>) -> O (if f.direction.is_row() { (main, cross) } else { (cross, main) }, f.justify.distributes().then_some(f.direction.is_row())) } None => ((0, 0), None), - }; + } +} + +/// The alignment grid: 3 × 3 cells (one column or row of three when a space justification spreads +/// the objects along the main axis); the chosen cell shows the direction's arrow. +fn flex_grid(ui: &mut Ui, r: Rect, f: Option<&designcraft_doc::FlexLayout>) -> Option { + let t = Tokens::get(ui.ctx()); + let resp = ui.interact(r, ui.scope_id().with("flex_grid"), Sense::click()); + ui.painter().rect_stroke(r, 2.0, Stroke::new(1.0, t.field_border), StrokeKind::Inside); + let cell = r.width() / 3.0; + let (chosen, spread_row) = flex_grid_choice(f); let mut picked = None; for row in 0..3usize { for col in 0..3usize { diff --git a/crates/ui-egui/src/panels/swatches.rs b/crates/ui-egui/src/panels/swatches.rs index 6a2bce349..08b41ac87 100644 --- a/crates/ui-egui/src/panels/swatches.rs +++ b/crates/ui-egui/src/panels/swatches.rs @@ -242,6 +242,33 @@ fn folder_button(ui: &mut egui::Ui, tip: &str) -> egui::Response { }) } +/// The swatch's chip following the pointer while the row is dragged. +fn paint_drag_chip(ui: &egui::Ui, doc: &designcraft_doc::Document, name: &str, p: egui::Pos2) { + let t = Tokens::get(ui.ctx()); + let (c, g) = crate::widgets::swatch_colors(doc, name, 1.0); + let painter = ui.ctx().layer_painter(egui::LayerId::new(egui::Order::Tooltip, egui::Id::unique("swatch_drag"))); + let chip = egui::Rect::from_min_size(p + vec2(8.0, 8.0), vec2(16.0, 16.0)); + crate::widgets::paint_chip(&painter, chip, c, g); + painter.rect_stroke(chip, 0.0, egui::Stroke::new(1.0, t.field_border), egui::StrokeKind::Outside); +} + +/// The type indicator shown at the right of a swatch row: colour mode (spot dot), gradient or tint. +fn swatch_kind_label(lang: &str, value: &SwatchValue) -> String { + match value { + SwatchValue::Color { color, color_type } => { + let m = match color { + designcraft_color::Color::Cmyk { .. } => "CMYK", + designcraft_color::Color::Rgb { .. } => "RGB", + designcraft_color::Color::Gray { .. } => crate::i18n::tr(lang, "Gray"), + }; + format!("{}{}", if *color_type == ColorType::Spot { "● " } else { "" }, m) + } + SwatchValue::Gradient { .. } => "Gradient".into(), + SwatchValue::Tint { tint, .. } => format!("{:.0}%", tint * 100.0), + _ => String::new(), + } +} + /// One Swatches panel row: chip, name, type; click applies it to the fill or stroke in front of /// the proxy (of the frames or their text, see [`super::colors_affect_text`]), the context menu /// to the stroke, colour groups and delete. @@ -261,12 +288,7 @@ fn swatch_row( if resp.dragged() && let Some(p) = ui.ctx().pointer_interact_pos() { - // The swatch's chip follows the pointer. - let (c, g) = crate::widgets::swatch_colors(doc, &sw.name, 1.0); - let painter = ui.ctx().layer_painter(egui::LayerId::new(egui::Order::Tooltip, egui::Id::unique("swatch_drag"))); - let chip = egui::Rect::from_min_size(p + vec2(8.0, 8.0), vec2(16.0, 16.0)); - crate::widgets::paint_chip(&painter, chip, c, g); - painter.rect_stroke(chip, 0.0, egui::Stroke::new(1.0, t.field_border), egui::StrokeKind::Outside); + paint_drag_chip(ui, doc, &sw.name, p); } if fill_cur == Some(sw.name.as_str()) { ui.painter().rect_filled(row, 0.0, t.row_selected); @@ -278,19 +300,7 @@ fn swatch_row( crate::widgets::paint_chip(ui.painter(), chip, c, g); ui.painter().text(row.min + vec2(28.0 + indent, 11.0), egui::Align2::LEFT_CENTER, &sw.name, egui::FontId::proportional(12.5), t.text); // Type indicators: spot dot / process square, colour mode. - let kind = match &sw.value { - SwatchValue::Color { color, color_type } => { - let m = match color { - designcraft_color::Color::Cmyk { .. } => "CMYK", - designcraft_color::Color::Rgb { .. } => "RGB", - designcraft_color::Color::Gray { .. } => crate::i18n::tr(&app.ui.language, "Gray"), - }; - format!("{}{}", if *color_type == ColorType::Spot { "● " } else { "" }, m) - } - SwatchValue::Gradient { .. } => "Gradient".into(), - SwatchValue::Tint { tint, .. } => format!("{:.0}%", tint * 100.0), - _ => String::new(), - }; + let kind = swatch_kind_label(&app.ui.language, &sw.value); if sw.locked { icons::paint(ui.painter(), egui::Rect::from_min_size(egui::pos2(row.max.x - 64.0, row.min.y + 4.0), vec2(13.0, 13.0)), "lock", t.text_dim); } diff --git a/crates/ui-egui/src/parent_canvas_tests.rs b/crates/ui-egui/src/parent_canvas_tests.rs index b3197a7e0..b2e44b8ea 100644 --- a/crates/ui-egui/src/parent_canvas_tests.rs +++ b/crates/ui-egui/src/parent_canvas_tests.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{CanvasLayout, Color32, DesignApp, Point, Pos2, Rect, View, Xf, json, render_texture, vec2}; fn app_with_document_object() -> DesignApp { let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default()); diff --git a/crates/ui-egui/src/updates_ui.rs b/crates/ui-egui/src/updates_ui.rs new file mode 100644 index 000000000..43803c9a5 --- /dev/null +++ b/crates/ui-egui/src/updates_ui.rs @@ -0,0 +1,177 @@ +//! Software updates, background check at startup, and startup prompt modal. + +use serde_json::Value; + +use crate::DesignApp; + +#[cfg(not(target_arch = "wasm32"))] +use craft_update::updater::{Action, Status, Updater}; + +pub(crate) struct Runtime { + #[cfg(not(target_arch = "wasm32"))] + updater: Updater, + /// The desktop app turns the check at start on; tests and the web build leave it off. + #[cfg(not(target_arch = "wasm32"))] + startup_check: bool, + #[cfg(not(target_arch = "wasm32"))] + check_requested: bool, + /// The context of the last frame, to wake the UI when a check is queued from a menu callback. + #[cfg(not(target_arch = "wasm32"))] + repaint: Option, + pub(crate) manual_window_open: bool, + pub(crate) status_message: Option, +} + +impl Default for Runtime { + fn default() -> Self { + Self { + #[cfg(not(target_arch = "wasm32"))] + updater: Updater::new(&craft_update::suite::DESIGNCRAFT, env!("CARGO_PKG_VERSION")), + #[cfg(not(target_arch = "wasm32"))] + startup_check: false, + #[cfg(not(target_arch = "wasm32"))] + check_requested: false, + #[cfg(not(target_arch = "wasm32"))] + repaint: None, + manual_window_open: false, + status_message: None, + } + } +} + +impl DesignApp { + /// Whether the app asks for a newer release when it starts (off until the desktop app turns it on). + #[cfg(not(target_arch = "wasm32"))] + pub fn set_startup_update_check(&mut self, on: bool) { + self.updates_rt.startup_check = on; + } +} + +pub(crate) fn check(app: &mut DesignApp) -> Result { + #[cfg(not(target_arch = "wasm32"))] + { + app.updates_rt.check_requested = true; + if let Some(ctx) = &app.updates_rt.repaint { + ctx.request_repaint(); + } + Ok(Value::Null) + } + #[cfg(target_arch = "wasm32")] + { + app.updates_rt.manual_window_open = true; + app.updates_rt.status_message = Some("Update check is not supported in the web browser.".into()); + Ok(Value::Null) + } +} + +pub(crate) fn tick(app: &mut DesignApp, ctx: &egui::Context) { + #[cfg(not(target_arch = "wasm32"))] + { + let rt = &mut app.updates_rt; + rt.repaint = Some(ctx.clone()); + if std::mem::take(&mut rt.check_requested) { + rt.updater.check_now(ctx); + } else if rt.startup_check { + rt.updater.check_at_start(ctx); + } + rt.updater.tick(ctx); + } + #[cfg(target_arch = "wasm32")] + let _ = (app, ctx); +} + +pub(crate) fn show(app: &mut DesignApp, ctx: &egui::Context) { + #[cfg(not(target_arch = "wasm32"))] + { + // The restart helper is scheduled before the app closes, so no update starts over unsaved work. + let clean = !app.session.documents().iter().any(|d| d.is_dirty()); + match app.updates_rt.updater.show_if(ctx, clean) { + Action::OpenUrl(url) => ctx.open_url(egui::OpenUrl::new_tab(url)), + Action::Close => ctx.send_viewport_cmd(egui::ViewportCommand::Close), + Action::None => {} + } + // The window answers Help ▸ Check for Updates, so it follows the updater's status. + let rt = &mut app.updates_rt; + (rt.manual_window_open, rt.status_message) = match rt.updater.status() { + Status::Idle => (false, None), + Status::Checking => (true, Some(crate::i18n::tr(&app.ui.language, "Checking for updates…").to_string())), + Status::UpToDate => ( + true, + Some( + crate::i18n::tr(&app.ui.language, "DesignCraft {version} is currently the newest version available.") + .replace("{version}", env!("CARGO_PKG_VERSION")), + ), + ), + Status::Failed(e) => (true, Some(crate::i18n::tr(&app.ui.language, "Could not check for updates:\n{error}").replace("{error}", e))), + }; + } + + if app.updates_rt.manual_window_open { + let mut open = true; + let title = crate::i18n::tr(&app.ui.language, "DesignCraft Updates"); + let ok_label = crate::i18n::tr(&app.ui.language, "OK"); + egui::Window::new(title) + .open(&mut open) + .collapsible(false) + .resizable(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) + .show(ctx, |ui| { + if let Some(msg) = &app.updates_rt.status_message { + ui.label(msg); + } + ui.add_space(8.0); + if ui.button(ok_label).clicked() { + app.updates_rt.manual_window_open = false; + } + }); + if !open { + app.updates_rt.manual_window_open = false; + } + } + + // Closing the window dismisses the outcome it showed. + #[cfg(not(target_arch = "wasm32"))] + if !app.updates_rt.manual_window_open { + app.updates_rt.updater.dismiss_status(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use designcraft_engine::Session; + + #[test] + fn default_runtime_is_idle() { + let rt = Runtime::default(); + assert!(!rt.manual_window_open); + #[cfg(not(target_arch = "wasm32"))] + { + assert!(!rt.startup_check); + assert!(!rt.updater.prompt_open()); + assert_eq!(rt.updater.status(), &Status::Idle); + } + } + + #[test] + #[cfg(not(target_arch = "wasm32"))] + fn startup_prompt_shows_and_closes() { + let mut app = DesignApp::new(Session::new(), Default::default()); + let ctx = egui::Context::default(); + let release = craft_update::Release { + tag: "v99.0.0".into(), + version: craft_update::parse_version("99.0.0"), + name: "DesignCraft 99.0.0".into(), + html_url: "https://github.com/storytold/designcraft/releases/tag/v99.0.0".into(), + ..Default::default() + }; + app.updates_rt.updater.offer(release); + + let mut output = ctx.run_ui(egui::RawInput::default(), |ui| { + show(&mut app, ui.ctx()); + }); + output.textures_delta.clear(); + assert!(app.updates_rt.updater.prompt_open()); + } +} diff --git a/docs/pdf-backend.md b/docs/pdf-backend.md index c6e65754e..0bcd7992c 100644 --- a/docs/pdf-backend.md +++ b/docs/pdf-backend.md @@ -1,7 +1,7 @@ # Shared PDF backend DesignCraft pins `craft-pdf`, patched Krilla 0.8.2 and the six Hayro reader crates (with -`hayro-cmap` and `hayro-postscript`) to craft-libs revision `e3b9e239c52c14677be462e94d8920ea243de4f3`. `craft-ui`, +`hayro-cmap` and `hayro-postscript`) to craft-libs revision `495e5971bc024aa96e6ed318cb6e7cd3a0103184`. `craft-ui`, `craft-print` and the other craft-libs crates are pinned to the same revision. The reader migration is reviewed on top of the separate writer change. ## Writing diff --git a/xtask/src/pdf_deps.rs b/xtask/src/pdf_deps.rs index e7d21434b..eb7dd8c99 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 REPO: &str = "https://github.com/storytold/craft-libs"; -const REV: &str = "e3b9e239c52c14677be462e94d8920ea243de4f3"; +const REV: &str = "495e5971bc024aa96e6ed318cb6e7cd3a0103184"; const PACKAGES: &[(&str, &str)] = &[ ("craft-pdf", "0.1.0"), ("krilla", "0.8.2"),