diff --git a/Cargo.lock b/Cargo.lock index cd2114ceec..69f9d3e808 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -161,6 +161,15 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.4.1" @@ -178,6 +187,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -254,6 +269,12 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backtrace" version = "0.3.74" @@ -284,6 +305,19 @@ dependencies = [ "simd-abstraction", ] +[[package]] +name = "biblatex" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35a7317fcbdbef94b60d0dd0a658711a936accfce4a631fea4bf8e527eff3c2" +dependencies = [ + "numerals", + "paste", + "strum", + "unicode-normalization", + "unscanny", +] + [[package]] name = "bincode" version = "1.3.3" @@ -293,6 +327,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit_field" version = "0.10.2" @@ -310,6 +359,9 @@ name = "bitflags" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +dependencies = [ + "serde", +] [[package]] name = "bitreader" @@ -386,9 +438,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "built" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73848a43c5d63a1251d17adf6c2bf78aa94830e60a335a95eeea45d6ba9e1e4d" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" @@ -396,6 +448,12 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + [[package]] name = "byte-tools" version = "0.3.1" @@ -484,6 +542,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-expr" version = "0.15.8" @@ -512,6 +576,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chinese-number" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fccaef6346f6d6a741908d3b79fe97c2debe2fbb5eb3a7d00ff5981b52bb6c" +dependencies = [ + "chinese-variant", + "enum-ordinalize", + "num-bigint", + "num-traits", +] + +[[package]] +name = "chinese-variant" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7588475145507237ded760e52bf2f1085495245502033756d28ea72ade0e498b" + [[package]] name = "chrono" version = "0.4.39" @@ -547,6 +629,43 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "citationberg" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4595e03beafb40235070080b5286d3662525efc622cca599585ff1d63f844fa" +dependencies = [ + "quick-xml 0.36.2", + "serde", +] + [[package]] name = "clap" version = "3.2.25" @@ -566,9 +685,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.29" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" dependencies = [ "clap_builder", "clap_derive 4.5.28", @@ -576,9 +695,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.29" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" dependencies = [ "anstream", "anstyle", @@ -588,11 +707,11 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.44" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375f9d8255adeeedd51053574fd8d4ba875ea5fa558e86617b07f09f1680c8b6" +checksum = "1e3040c8291884ddf39445dc033c70abc2bc44a42f0a3a00571a0f483a83f0cd" dependencies = [ - "clap 4.5.29", + "clap 4.5.30", ] [[package]] @@ -635,6 +754,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "codemap" version = "0.1.3" @@ -663,6 +788,29 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "comemo" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6916408a724339aa77b18214233355f3eb04c42eb895e5f8909215bd8a7a91" +dependencies = [ + "comemo-macros", + "once_cell", + "parking_lot", + "siphasher", +] + +[[package]] +name = "comemo-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8936e42f9b4f5bdfaf23700609ac1f11cb03ad4c1ec128a4ee4fd0903e228db" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "config" version = "0.1.0" @@ -754,6 +902,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -885,6 +1042,41 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -898,6 +1090,21 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + [[package]] name = "data-encoding" version = "2.8.0" @@ -913,6 +1120,12 @@ dependencies = [ "matches", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "deranged" version = "0.3.11" @@ -922,6 +1135,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "deunicode" version = "1.6.0" @@ -947,6 +1191,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -964,6 +1229,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "dtoa" version = "1.0.9" @@ -979,6 +1250,34 @@ dependencies = [ "dtoa", ] +[[package]] +name = "ducc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41bc1f8a30712eb6a7454f85747f218d9dfb41d173bb223a8c4f18daff829207" +dependencies = [ + "cesu8", + "ducc-sys", +] + +[[package]] +name = "ducc-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cdea834bf6a0fde522374db4404695c5f0465fc0ee814f2878d76eaabd4ffed" +dependencies = [ + "cc", +] + +[[package]] +name = "ecow" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42fc0a93992b20c58b99e59d61eaf1635a25bfbe49e4275c34ba0aee98119ba" +dependencies = [ + "serde", +] + [[package]] name = "either" version = "1.13.0" @@ -1001,6 +1300,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1071,6 +1382,26 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -1086,9 +1417,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -1134,6 +1465,22 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b3e85d14d419ba3e1db925519461c0d17a49bdd2d67ea6316fa965ca7acdf74" +[[package]] +name = "fancy-regex" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastrand" version = "2.3.0" @@ -1180,12 +1527,30 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fontdb" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37be9fc20d966be438cd57a45767f73349477fb0f85ce86e000557f787298afb" +dependencies = [ + "log", + "slotmap", + "tinyvec", + "ttf-parser 0.24.1", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -1529,10 +1894,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] -name = "heck" -version = "0.4.1" +name = "hayagriva" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "954907554bb7fcba29a4f917c2d43e289ec21b69d872ccf97db160eca6caeed8" +dependencies = [ + "biblatex", + "ciborium", + "citationberg", + "indexmap 2.7.1", + "numerals", + "paste", + "serde", + "serde_yaml", + "thiserror 1.0.69", + "unic-langid", + "unicode-segmentation", + "unscanny", + "url", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" @@ -1744,6 +2130,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "hypher" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b24ad5637230df201ab1034d593f1d09bf7f2a9274f2e8897638078579f4265" + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1774,6 +2166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", + "serde", "yoke", "zerofrom", "zerovec", @@ -1847,6 +2240,7 @@ dependencies = [ "icu_locid_transform", "icu_properties_data", "icu_provider", + "serde", "tinystr", "zerovec", ] @@ -1866,6 +2260,8 @@ dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", + "postcard", + "serde", "stable_deref_trait", "tinystr", "writeable", @@ -1874,6 +2270,33 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_provider_adapters" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6324dfd08348a8e0374a447ebd334044d766b1839bb8d5ccf2482a99a77c0bc" +dependencies = [ + "icu_locid", + "icu_locid_transform", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_provider_blob" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c24b98d1365f55d78186c205817631a4acf08d7a45bdf5dc9dcf9c5d54dccf51" +dependencies = [ + "icu_provider", + "postcard", + "serde", + "writeable", + "zerotrie", + "zerovec", +] + [[package]] name = "icu_provider_macros" version = "1.5.0" @@ -1885,6 +2308,35 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "icu_segmenter" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" +dependencies = [ + "core_maths", + "displaydoc", + "icu_collections", + "icu_locid", + "icu_provider", + "icu_segmenter_data", + "serde", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -1906,6 +2358,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "ignore" version = "0.4.23" @@ -1961,13 +2419,19 @@ version = "0.1.0" dependencies = [ "config", "errors", - "kamadak-exif", + "kamadak-exif 0.6.1", "libs", "serde", "tempfile", "utils", ] +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "imgref" version = "1.11.0" @@ -1995,6 +2459,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + [[package]] name = "inotify" version = "0.11.0" @@ -2143,6 +2613,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + [[package]] name = "kamadak-exif" version = "0.6.1" @@ -2152,6 +2631,19 @@ dependencies = [ "mutate_once", ] +[[package]] +name = "katex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bdbc7a1823f188f56ac9486993536b70a2686a58d47095dcc10507a7d242bf5" +dependencies = [ + "cfg-if 1.0.0", + "derive_builder", + "ducc", + "itertools 0.10.5", + "thiserror 1.0.69", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -2182,6 +2674,16 @@ dependencies = [ "libc", ] +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lasso" version = "0.7.3" @@ -2325,7 +2827,7 @@ dependencies = [ "const-str", "cssparser", "cssparser-color", - "dashmap", + "dashmap 5.5.3", "data-encoding", "getrandom 0.2.15", "indexmap 2.7.1", @@ -2533,11 +3035,24 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "lipsum" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636860251af8963cc40f6b4baadee105f02e21b28131d76eba8e40ce84ab8064" +dependencies = [ + "rand 0.8.5", + "rand_chacha 0.3.1", +] + [[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +dependencies = [ + "serde", +] [[package]] name = "lock_api" @@ -2590,14 +3105,28 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" name = "markdown" version = "0.1.0" dependencies = [ + "bincode", "config", "console 0.1.0", + "dashmap 6.1.0", + "dirs", "errors", + "flate2", "insta", + "katex", "libs", "pest", "pest_derive", + "serde", + "tar", "templates", + "time", + "ttf-parser 0.25.1", + "twox-hash", + "typst", + "typst-assets", + "typst-svg", + "urlencoding", "utils", ] @@ -2798,6 +3327,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "multi-stash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" + [[package]] name = "mutate_once" version = "0.1.1" @@ -3023,6 +3558,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numerals" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25be21376a772d15f97ae789845340a9651d3c4246ff5ebb6a2b35f9c37bd31" + [[package]] name = "object" version = "0.36.7" @@ -3079,9 +3620,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.70" +version = "0.10.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" dependencies = [ "bitflags 2.8.0", "cfg-if 1.0.0", @@ -3111,9 +3652,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.105" +version = "0.9.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" dependencies = [ "cc", "libc", @@ -3121,6 +3662,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -3133,6 +3680,30 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "parcel_selectors" version = "0.28.1" @@ -3156,7 +3727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" dependencies = [ "base64-simd", - "data-url", + "data-url 0.1.1", "rkyv", "serde", "serde_json", @@ -3329,6 +3900,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.9" @@ -3393,6 +3970,24 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "postcard" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3466,6 +4061,15 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "psm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" +dependencies = [ + "cc", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -3510,6 +4114,12 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" +[[package]] +name = "qcms" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa" + [[package]] name = "qoi" version = "0.4.1" @@ -3543,6 +4153,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quickxml_to_serde" version = "0.6.0" @@ -3773,6 +4393,17 @@ dependencies = [ "bitflags 2.8.0", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 2.0.11", +] + [[package]] name = "regex" version = "1.11.1" @@ -3929,6 +4560,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec", + "num-traits", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4009,6 +4650,24 @@ version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +[[package]] +name = "rustybuzz" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" +dependencies = [ + "bitflags 2.8.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser 0.24.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.19" @@ -4211,6 +4870,15 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -4228,6 +4896,7 @@ dependencies = [ "imageproc", "libs", "link_checker", + "markdown", "path-slash", "search", "serde", @@ -4245,6 +4914,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "slug" version = "0.1.6" @@ -4257,9 +4935,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" @@ -4271,12 +4949,51 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d08feb8f695b465baed819b03c128dc23f57a694510ab1f06c77f763975685e" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "string-interner" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.14.5", + "serde", +] + [[package]] name = "string_cache" version = "0.8.8" @@ -4314,6 +5031,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.98", +] + [[package]] name = "subtle" version = "2.6.1" @@ -4332,6 +5071,16 @@ dependencies = [ "roxmltree", ] +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -4382,6 +5131,7 @@ checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" dependencies = [ "bincode", "bitflags 1.3.2", + "fancy-regex", "flate2", "fnv", "once_cell", @@ -4434,9 +5184,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.16.0" +version = "3.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" dependencies = [ "cfg-if 1.0.0", "fastrand", @@ -4542,6 +5292,12 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +[[package]] +name = "thin-vec" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" + [[package]] name = "thiserror" version = "1.0.69" @@ -4626,6 +5382,17 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -4633,6 +5400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", + "serde", "zerovec", ] @@ -4795,11 +5563,200 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" +dependencies = [ + "core_maths", +] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "two-face" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384eda438ddf62e2c6f39a174452d952d9d9df5a8ad5ade22198609f8dcaf852" +dependencies = [ + "once_cell", + "serde", + "syntect", +] + +[[package]] +name = "twox-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "typst" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87286a6b7e417426c425f35c42fb3d86e54ee99485b7eeb3662f4aeb569151c6" +dependencies = [ + "arrayvec", + "az", + "bitflags 2.8.0", + "bumpalo", + "chinese-number", + "ciborium", + "comemo", + "csv", + "ecow", + "flate2", + "fontdb", + "hayagriva", + "hypher", + "icu_properties", + "icu_provider", + "icu_provider_adapters", + "icu_provider_blob", + "icu_segmenter", + "if_chain", + "image", + "indexmap 2.7.1", + "kamadak-exif 0.5.5", + "kurbo", + "lipsum", + "log", + "once_cell", + "palette", + "phf", + "png", + "portable-atomic", + "qcms", + "rayon", + "regex", + "roxmltree", + "rust_decimal", + "rustybuzz", + "serde", + "serde_json", + "serde_yaml", + "siphasher", + "smallvec", + "stacker", + "syntect", + "time", + "toml 0.8.20", + "ttf-parser 0.24.1", + "two-face", + "typed-arena", + "typst-assets", + "typst-macros", + "typst-syntax", + "typst-timing", + "typst-utils", + "unicode-bidi", + "unicode-math-class", + "unicode-script", + "unicode-segmentation", + "unscanny", + "usvg", + "wasmi", + "xmlwriter", +] + +[[package]] +name = "typst-assets" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe00da1b24da2c4a7da532fc33d0c3bd43a902ca4c408ee2c36eabe70f2f4ba" + +[[package]] +name = "typst-macros" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b8b94b63e868e969e372929d6d3efb0d5f8cedad95a4f3aa460959f4544e0d" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "typst-svg" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1eba33340e52cae6de14e8bd2610954a0a64f0a9f1bfa70fc221b058d65cfe" +dependencies = [ + "base64", + "comemo", + "ecow", + "flate2", + "ttf-parser 0.24.1", + "typst", + "typst-macros", + "typst-timing", + "xmlparser", + "xmlwriter", +] + +[[package]] +name = "typst-syntax" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b7be8b6ed6b2cb39ca495947d548a28d7db0ba244008e44c5a759120327693" +dependencies = [ + "ecow", + "once_cell", + "serde", + "toml 0.8.20", + "typst-utils", + "unicode-ident", + "unicode-math-class", + "unicode-script", + "unicode-segmentation", + "unscanny", +] + +[[package]] +name = "typst-timing" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "175e7755eca10fe7d5a37a54cff50fbdf1a1becd55f35330ab783f5317c9eb96" +dependencies = [ + "parking_lot", + "serde", + "serde_json", + "typst-syntax", +] + +[[package]] +name = "typst-utils" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0305443ed97f0b658471487228f86bf835705e7525fbdcc671cebd864f7a40" +dependencies = [ + "once_cell", + "portable-atomic", + "rayon", + "siphasher", + "thin-vec", +] [[package]] name = "ucd-trie" @@ -4843,6 +5800,7 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a5422c1f65949306c99240b81de9f3f15929f5a8bfe05bb44b034cc8bf593e5" dependencies = [ + "serde", "tinystr", ] @@ -4881,24 +5839,81 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f" + +[[package]] +name = "unicode-ccc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42" + [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +[[package]] +name = "unicode-math-class" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d246cf599d5fae3c8d56e04b20eb519adb89a8af8d0b0fbcded369aa3647d65" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "unscanny" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" + [[package]] name = "untrusted" version = "0.9.0" @@ -4914,6 +5929,40 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "usvg" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6803057b5cbb426e9fb8ce2216f3a9b4ca1dd2c705ba3cbebc13006e437735fd" +dependencies = [ + "base64", + "data-url 0.3.1", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", ] [[package]] @@ -4953,9 +6002,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" [[package]] name = "v_frame" @@ -5103,6 +6152,55 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmi" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbaac6e702fa7b52258e5ac90d6e20a40afb37a1fbe7c645d0903ee42c5f85f4" +dependencies = [ + "arrayvec", + "multi-stash", + "num-derive", + "num-traits", + "smallvec", + "spin", + "wasmi_collections", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "wasmi_collections" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff59e30e550a509cc689ec638e5042be4d78ec9f6dd8a71fd02ee28776a74fd" +dependencies = [ + "ahash 0.8.11", + "hashbrown 0.14.5", + "string-interner", +] + +[[package]] +name = "wasmi_core" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e10c674add0f92f47bf8ad57c55ee3ac1762a0d9baf07535e27e22b758a916" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" +dependencies = [ + "indexmap-nostd", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -5399,6 +6497,18 @@ dependencies = [ "rustix", ] +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "yada" version = "0.5.1" @@ -5486,12 +6596,25 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerotrie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "zerovec", +] + [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ + "serde", "yoke", "zerofrom", "zerovec-derive", @@ -5512,7 +6635,7 @@ dependencies = [ name = "zola" version = "0.20.0" dependencies = [ - "clap 4.5.29", + "clap 4.5.30", "clap_complete", "console 0.1.0", "ctrlc", diff --git a/components/config/src/config/markup.rs b/components/config/src/config/markup.rs index 93c5de247b..2aadbb0bbc 100644 --- a/components/config/src/config/markup.rs +++ b/components/config/src/config/markup.rs @@ -1,11 +1,11 @@ -use std::{path::Path, sync::Arc}; +use std::{fmt, path::Path, sync::Arc}; use libs::syntect::{ highlighting::{Theme, ThemeSet}, html::css_for_theme_with_class_style, parsing::{SyntaxSet, SyntaxSetBuilder}, }; -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize}; use errors::{bail, Result}; use utils::types::InsertAnchor; @@ -23,6 +23,84 @@ pub struct ThemeCss { pub filename: String, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum MathRenderingEngine { + #[default] + None, + Typst, + Katex, +} + +struct BoolWithPathVisitor; + +impl<'de> de::Visitor<'de> for BoolWithPathVisitor { + type Value = BoolWithPath; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a boolean or string") + } + + fn visit_bool(self, value: bool) -> Result + where + E: de::Error, + { + Ok(if value { BoolWithPath::True(None) } else { BoolWithPath::False }) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(BoolWithPath::True(Some(value.to_string()))) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + Ok(BoolWithPath::True(Some(value))) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] +pub enum BoolWithPath { + #[default] + False, + True(Option), +} + +impl<'de> Deserialize<'de> for BoolWithPath { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(BoolWithPathVisitor) + } +} + +impl Serialize for BoolWithPath { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + BoolWithPath::False => serializer.serialize_bool(false), + BoolWithPath::True(None) => serializer.serialize_bool(true), + BoolWithPath::True(Some(s)) => serializer.serialize_str(s), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct MathRenderer { + pub engine: MathRenderingEngine, + #[serde(default)] + pub svgo: BoolWithPath, + pub css: Option, + pub addon: Option, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Markdown { @@ -65,6 +143,10 @@ pub struct Markdown { /// Whether to insert a link for each header like the ones you can see in this site if you hover one /// The default template can be overridden by creating a `anchor-link.html` in the `templates` directory pub insert_anchor_links: InsertAnchor, + /// Whether to enable math rendering in markdown files + pub math: MathRenderer, + /// Whether to cache the rendered math + pub cache: BoolWithPath, } impl Markdown { @@ -240,6 +322,8 @@ impl Default for Markdown { extra_theme_set: Arc::new(None), lazy_async_image: false, insert_anchor_links: InsertAnchor::None, + math: MathRenderer::default(), + cache: BoolWithPath::True(None), } } } diff --git a/components/config/src/highlighting.rs b/components/config/src/highlighting.rs index ff50d2258b..be3022df31 100644 --- a/components/config/src/highlighting.rs +++ b/components/config/src/highlighting.rs @@ -34,6 +34,17 @@ pub struct SyntaxAndTheme<'config> { pub source: HighlightSource, } +impl Default for SyntaxAndTheme<'_> { + fn default() -> Self { + SyntaxAndTheme { + syntax: SYNTAX_SET.find_syntax_plain_text(), + syntax_set: &SYNTAX_SET as &SyntaxSet, + theme: None, + source: HighlightSource::Plain, + } + } +} + pub fn resolve_syntax_and_theme<'config>( language: Option<&'_ str>, config: &'config Config, diff --git a/components/config/src/lib.rs b/components/config/src/lib.rs index 05d621615c..0d6d212add 100644 --- a/components/config/src/lib.rs +++ b/components/config/src/lib.rs @@ -8,6 +8,7 @@ pub use crate::config::{ languages::LanguageOptions, link_checker::LinkChecker, link_checker::LinkCheckerLevel, + markup::{BoolWithPath, MathRenderingEngine}, search::{IndexFormat, Search}, slugify::Slugify, taxonomies::TaxonomyConfig, diff --git a/components/content/src/page.rs b/components/content/src/page.rs index 10c39e3039..5809360460 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -1,6 +1,7 @@ /// A page, can be a blog post or a basic page use std::collections::HashMap; use std::path::{Path, PathBuf}; +use std::sync::Arc; use libs::once_cell::sync::Lazy; use libs::regex::Regex; @@ -8,6 +9,8 @@ use libs::tera::{Context as TeraContext, Tera}; use config::Config; use errors::{Context, Result}; +use markdown::context::Caches; + use markdown::{render_content, RenderContext}; use utils::slugs::slugify_paths; use utils::table_of_contents::Heading; @@ -212,6 +215,7 @@ impl Page { config: &Config, anchor_insert: InsertAnchor, shortcode_definitions: &HashMap, + caches: Option>, ) -> Result<()> { let mut context = RenderContext::new( tera, @@ -220,9 +224,12 @@ impl Page { &self.permalink, permalinks, anchor_insert, + caches, ); context.set_shortcode_definitions(shortcode_definitions); context.set_current_page_path(&self.file.relative); + context.set_parent_absolute(&self.file.parent); + context.tera_context.insert("page", &SerializingPage::new(self, None, false)); let res = render_content(&self.raw_content, &context) @@ -329,6 +336,7 @@ Hello world"#; &config, InsertAnchor::None, &HashMap::new(), + None, ) .unwrap(); @@ -357,6 +365,7 @@ Hello world"#; &config, InsertAnchor::None, &HashMap::new(), + None, ) .unwrap(); @@ -527,6 +536,7 @@ Hello world &config, InsertAnchor::None, &HashMap::new(), + None, ) .unwrap(); assert_eq!(page.summary, Some("

Hello world

".to_string())); @@ -561,6 +571,7 @@ And here's another. [^3] &config, InsertAnchor::None, &HashMap::new(), + None, ) .unwrap(); assert_eq!( diff --git a/components/content/src/section.rs b/components/content/src/section.rs index 69a22a03d4..a2b3ef0429 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; +use std::sync::Arc; use libs::tera::{Context as TeraContext, Tera}; use config::Config; use errors::{Context, Result}; +use markdown::context::Caches; use markdown::{render_content, RenderContext}; use utils::fs::read_file; use utils::net::is_external_link; @@ -150,6 +152,7 @@ impl Section { tera: &Tera, config: &Config, shortcode_definitions: &HashMap, + caches: Option>, ) -> Result<()> { let mut context = RenderContext::new( tera, @@ -158,9 +161,11 @@ impl Section { &self.permalink, permalinks, self.meta.insert_anchor_links.unwrap_or(config.markdown.insert_anchor_links), + caches, ); context.set_shortcode_definitions(shortcode_definitions); context.set_current_page_path(&self.file.relative); + context.set_parent_absolute(&self.file.parent); context .tera_context .insert("section", &SerializingSection::new(self, SectionSerMode::ForMarkdown)); diff --git a/components/libs/Cargo.toml b/components/libs/Cargo.toml index 26ce627eac..6eb04fede5 100644 --- a/components/libs/Cargo.toml +++ b/components/libs/Cargo.toml @@ -9,7 +9,22 @@ ammonia = "4" atty = "0.2.11" base64 = "0.22" csv = "1" -elasticlunr-rs = { version = "3.0.2", features = ["da", "no", "de", "du", "es", "fi", "fr", "hu", "it", "pt", "ro", "ru", "sv", "tr"] } +elasticlunr-rs = { version = "3.0.2", features = [ + "da", + "no", + "de", + "du", + "es", + "fi", + "fr", + "hu", + "it", + "pt", + "ro", + "ru", + "sv", + "tr", +] } filetime = "0.2" gh-emoji = "1" glob = "0.3" @@ -27,8 +42,10 @@ quickxml_to_serde = "0.6" rayon = "1" regex = "1" relative-path = "1" -reqwest = { version = "0.12", default-features = false, features = ["blocking"] } -grass = {version = "0.13", default-features = false, features = ["random"]} +reqwest = { version = "0.12", default-features = false, features = [ + "blocking", +] } +grass = { version = "0.13", default-features = false, features = ["random"] } serde_json = "1" serde_yaml = "0.9" sha2 = "0.10" diff --git a/components/markdown/Cargo.toml b/components/markdown/Cargo.toml index d1226ee89e..3bff6d2945 100644 --- a/components/markdown/Cargo.toml +++ b/components/markdown/Cargo.toml @@ -14,6 +14,21 @@ config = { path = "../config" } console = { path = "../console" } libs = { path = "../libs" } +typst = "0.12.0" +typst-assets = { version = "0.12.0", features = ["fonts"] } +typst-svg = "0.12.0" +time = { version = "0.3.37", features = ["local-offset"] } +flate2 = "1.0.35" +tar = "0.4.43" +ttf-parser = "0.25.1" +urlencoding = "2.1.3" +bincode = "1.3.3" +serde = { version = "1.0.130", features = ["derive"] } +dashmap = { version = "6.1.0", features = ["serde"] } +twox-hash = "2.1.0" +dirs = "6.0.0" +katex = { version = "0.4.6", default-features = false, features = ["duktape"] } + [dev-dependencies] templates = { path = "../templates" } insta = "1.12.0" diff --git a/components/markdown/benches/all.rs b/components/markdown/benches/all.rs index ffec285088..228f02a140 100644 --- a/components/markdown/benches/all.rs +++ b/components/markdown/benches/all.rs @@ -95,6 +95,7 @@ fn bench_render_content_with_highlighting(b: &mut test::Bencher) { current_page_permalink, &permalinks_ctx, InsertAnchor::None, + None, ); let shortcode_def = utils::templates::get_shortcodes(&tera); context.set_shortcode_definitions(&shortcode_def); @@ -116,6 +117,7 @@ fn bench_render_content_without_highlighting(b: &mut test::Bencher) { current_page_permalink, &permalinks_ctx, InsertAnchor::None, + None, ); let shortcode_def = utils::templates::get_shortcodes(&tera); context.set_shortcode_definitions(&shortcode_def); @@ -137,6 +139,7 @@ fn bench_render_content_no_shortcode(b: &mut test::Bencher) { current_page_permalink, &permalinks_ctx, InsertAnchor::None, + None, ); b.iter(|| render_content(&content2, &context).unwrap()); @@ -159,6 +162,7 @@ fn bench_render_content_with_emoji(b: &mut test::Bencher) { current_page_permalink, &permalinks_ctx, InsertAnchor::None, + None, ); b.iter(|| render_content(&content2, &context).unwrap()); diff --git a/components/markdown/src/cache.rs b/components/markdown/src/cache.rs new file mode 100644 index 0000000000..14796838b1 --- /dev/null +++ b/components/markdown/src/cache.rs @@ -0,0 +1,106 @@ +use crate::Result; +use bincode; +use dashmap::DashMap; +use errors::Context; +use serde::{Deserialize, Serialize}; +use std::fs::{self, File, OpenOptions}; +use std::hash::Hash; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +/// Generic cache using DashMap, storing data in a binary file +#[derive(Debug)] +pub struct GenericCache +where + K: Eq + Hash + Serialize + for<'de> Deserialize<'de>, + V: Serialize + for<'de> Deserialize<'de>, +{ + cache_file: PathBuf, + cache: DashMap, +} + +impl GenericCache +where + K: Eq + Hash + Serialize + for<'de> Deserialize<'de>, + V: Serialize + for<'de> Deserialize<'de>, +{ + /// Get the directory where the cache is stored + pub fn dir(&self) -> &Path { + self.cache_file.parent().unwrap() + } + + /// Create a new cache for a specific type + pub fn new(base_cache_dir: &Path, filename: &str) -> crate::Result { + // Full path to the cache file + let cache_file = base_cache_dir.join(filename); + + // Attempt to load existing cache + let cache = match Self::read_cache(&cache_file) { + Ok(maybe_cache) => match maybe_cache { + Some(c) => { + println!("Loaded cache from {:?} ({:?})", cache_file, c.len()); + c + } + None => DashMap::new(), + }, + Err(e) => { + println!("Failed to load cache: {}", e); + DashMap::new() + } + }; + + Ok(Self { cache_file, cache }) + } + + /// Read cache from file + fn read_cache(cache_file: &Path) -> Result>> { + if !cache_file.exists() { + return Ok(None); + } + + let mut file = File::open(cache_file)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + bincode::deserialize(&buffer).context("Failed to deserialize cache").map(Some) + } + + /// Write cache to file + pub fn write(&self) -> Result<()> { + fs::create_dir_all(self.dir())?; + let serialized = bincode::serialize(&self.cache).context("Failed to serialize cache")?; + + let mut file = + OpenOptions::new().write(true).create(true).truncate(true).open(&self.cache_file)?; + + file.write_all(&serialized)?; + Ok(()) + } + + /// Get a reference to the underlying DashMap + pub fn inner(&self) -> &DashMap { + &self.cache + } + + pub fn get(&self, key: &K) -> Option + where + V: Clone, + { + self.cache.get(key).map(|r| r.value().clone()) + } + + pub fn insert(&self, key: K, value: V) { + self.cache.insert(key, value); + } + + /// Clear the cache and remove the file + pub fn clear(&self) -> Result<()> { + self.cache.clear(); + + if self.cache_file.exists() { + fs::remove_file(&self.cache_file)?; + } + + Ok(()) + } +} diff --git a/components/markdown/src/codeblock/mod.rs b/components/markdown/src/codeblock/mod.rs index a6d699db1a..6c3c67e210 100644 --- a/components/markdown/src/codeblock/mod.rs +++ b/components/markdown/src/codeblock/mod.rs @@ -89,7 +89,7 @@ pub struct CodeBlock<'config> { impl<'config> CodeBlock<'config> { pub fn new<'fence_info>( - fence: FenceSettings<'fence_info>, + fence: &FenceSettings<'fence_info>, config: &'config Config, // path to the current file if there is one, to point where the error is path: Option<&'config str>, @@ -123,8 +123,8 @@ impl<'config> CodeBlock<'config> { highlighter, line_numbers: fence.line_numbers, line_number_start: fence.line_number_start, - highlight_lines: fence.highlight_lines, - hide_lines: fence.hide_lines, + highlight_lines: fence.highlight_lines.clone(), + hide_lines: fence.hide_lines.clone(), }, html_start, )) diff --git a/components/markdown/src/context.rs b/components/markdown/src/context.rs index cefce7021a..338e7710e9 100644 --- a/components/markdown/src/context.rs +++ b/components/markdown/src/context.rs @@ -1,11 +1,16 @@ -use std::borrow::Cow; -use std::collections::HashMap; - use config::Config; +use dirs::cache_dir; +use libs::once_cell::sync::Lazy; use libs::tera::{Context, Tera}; +use std::borrow::Cow; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use utils::templates::ShortcodeDefinition; use utils::types::InsertAnchor; +use crate::math::MathCache; + /// All the information from the zola site that is needed to render HTML from markdown #[derive(Debug)] pub struct RenderContext<'a> { @@ -13,11 +18,36 @@ pub struct RenderContext<'a> { pub config: &'a Config, pub tera_context: Context, pub current_page_path: Option<&'a str>, + pub parent_absolute: Option<&'a PathBuf>, pub current_page_permalink: &'a str, pub permalinks: Cow<'a, HashMap>, pub insert_anchor: InsertAnchor, pub lang: &'a str, pub shortcode_definitions: Cow<'a, HashMap>, + pub caches: Option>, +} + +#[derive(Debug, Clone)] +pub struct Caches { + pub typst: Arc, + pub katex: Arc, +} + +impl Caches { + pub fn new(cache_path: &Path) -> Self { + Self { + typst: Arc::new(MathCache::new(cache_path, "typst").unwrap()), + katex: Arc::new(MathCache::new(cache_path, "katex").unwrap()), + } + } +} + +pub static CACHE_DIR: Lazy = Lazy::new(|| cache_dir().unwrap().join("zola")); + +impl Default for Caches { + fn default() -> Self { + Self::new(&CACHE_DIR) + } } impl<'a> RenderContext<'a> { @@ -28,6 +58,7 @@ impl<'a> RenderContext<'a> { current_page_permalink: &'a str, permalinks: &'a HashMap, insert_anchor: InsertAnchor, + caches: Option>, ) -> RenderContext<'a> { let mut tera_context = Context::new(); tera_context.insert("config", &config.serialize(lang)); @@ -43,6 +74,8 @@ impl<'a> RenderContext<'a> { config, lang, shortcode_definitions: Cow::Owned(HashMap::new()), + parent_absolute: None, + caches, } } @@ -57,6 +90,11 @@ impl<'a> RenderContext<'a> { self.current_page_path = Some(path); } + /// Same as above + pub fn set_parent_absolute(&mut self, path: &'a PathBuf) { + self.parent_absolute = Some(path); + } + // In use in the markdown filter // NOTE: This RenderContext is not i18n-aware, see MarkdownFilter::filter for details // If this function is ever used outside of MarkdownFilter, take this into consideration @@ -71,6 +109,9 @@ impl<'a> RenderContext<'a> { config, lang: &config.default_language, shortcode_definitions: Cow::Owned(HashMap::new()), + parent_absolute: None, + // We shouldn't need caches for this use case + caches: None, } } } diff --git a/components/markdown/src/lib.rs b/components/markdown/src/lib.rs index f374442312..5bf8ec378b 100644 --- a/components/markdown/src/lib.rs +++ b/components/markdown/src/lib.rs @@ -1,7 +1,9 @@ -mod codeblock; -mod context; -mod markdown; -mod shortcode; +pub mod cache; +pub mod codeblock; +pub mod context; +pub mod markdown; +pub mod math; +pub mod shortcode; use shortcode::{extract_shortcodes, insert_md_shortcodes}; diff --git a/components/markdown/src/markdown.rs b/components/markdown/src/markdown.rs index bd69efa93e..1a79dd9ede 100644 --- a/components/markdown/src/markdown.rs +++ b/components/markdown/src/markdown.rs @@ -1,13 +1,22 @@ use std::collections::HashMap; use std::fmt::Write; +use std::path::Path; +use std::sync::Arc; +use crate::cache::GenericCache; use crate::markdown::cmark::CowStr; + +use crate::math::{ + katex::KatexCompiler, svgo::Svgo, typst::TypstCompiler, MathCompiler, MathRenderMode, +}; +use config::{BoolWithPath, MathRenderingEngine}; use errors::bail; use libs::gh_emoji::Replacer as EmojiReplacer; use libs::once_cell::sync::Lazy; use libs::pulldown_cmark as cmark; use libs::pulldown_cmark_escape as cmark_escape; use libs::tera; +use utils::fs::read_file; use utils::net::is_external_link; use crate::context::RenderContext; @@ -409,6 +418,7 @@ pub fn markdown_to_html( .get("page") .or_else(|| context.tera_context.get("section")) .map(|x| x.as_object().unwrap().get("relative_path").unwrap().as_str().unwrap()); + // the rendered html let mut html = String::with_capacity(content.len()); let mut summary = None; @@ -436,6 +446,9 @@ pub fn markdown_to_html( opts.insert(Options::ENABLE_STRIKETHROUGH); opts.insert(Options::ENABLE_TASKLISTS); opts.insert(Options::ENABLE_HEADING_ATTRIBUTES); + if context.config.markdown.math.engine != config::MathRenderingEngine::None { + opts.insert(Options::ENABLE_MATH); + } if context.config.markdown.smart_punctuation { opts.insert(Options::ENABLE_SMART_PUNCTUATION); @@ -448,6 +461,57 @@ pub fn markdown_to_html( let mut html_shortcodes: Vec<_> = html_shortcodes.into_iter().rev().collect(); let mut next_shortcode = html_shortcodes.pop(); let contains_shortcode = |txt: &str| -> bool { txt.contains(SHORTCODE_PLACEHOLDER) }; + let addon = context + .config + .markdown + .math + .addon + .as_ref() + .map(|path| read_file(Path::new(&path)).ok()) + .flatten(); + + let styles = context + .config + .markdown + .math + .css + .as_ref() + .map(|path| read_file(Path::new(&path)).ok()) + .flatten(); + + let (mut compiler, cache): ( + Option>, + Option>>, + ) = match context.config.markdown.math.engine { + MathRenderingEngine::Typst => ( + Some(Box::new(TypstCompiler::new( + context.caches.as_ref().map(|e| e.typst.dir().to_path_buf()), + addon, + styles, + ))), + context.caches.as_ref().map(|e| e.typst.clone()), + ), + MathRenderingEngine::Katex => ( + Some(Box::new(KatexCompiler::new(addon))), + context.caches.as_ref().map(|e| e.katex.clone()), + ), + MathRenderingEngine::None => (None, None), + }; + + if let Some(ref mut compiler) = compiler { + if let Some(cache) = cache { + compiler.set_cache(cache); + } + } + + if matches!(context.config.markdown.math.svgo, BoolWithPath::True(_)) { + Svgo::default().check_bin().map_err(|e| { + Error::msg(format!( + "Error checking svgo installation, make sure it's installed and in your PATH: {}", + e + )) + })?; + } { let mut events = Vec::new(); @@ -510,6 +574,7 @@ pub fn markdown_to_html( } let mut accumulated_block = String::new(); + for (event, mut range) in Parser::new_ext(content, opts).into_offset_iter() { match event { Event::Text(text) => { @@ -564,15 +629,42 @@ pub fn markdown_to_html( cmark::CodeBlockKind::Fenced(fence_info) => FenceSettings::new(fence_info), _ => FenceSettings::new(""), }; - let (block, begin) = match CodeBlock::new(fence, context.config, path) { - Ok(cb) => cb, - Err(e) => { - error = Some(e); - break; + let should_render = match (fence.language.as_deref(), &compiler) { + (Some(lang), Some(compiler)) + if compiler.raw_extensions().contains(&lang) => + { + true } + _ => false, }; - code_block = Some(block); - events.push(Event::Html(begin.into())); + if should_render { + if let Some(ref compiler) = compiler { + let rendered = compiler.compile( + &accumulated_block, + MathRenderMode::Raw, + &context.config.markdown.math.svgo, + ); + + match rendered { + Ok(svg) => { + events.push(Event::Html(svg.into())); + } + Err(e) => { + error = Some(e); + } + } + } + } else { + let (block, begin) = match CodeBlock::new(&fence, context.config, path) { + Ok(cb) => cb, + Err(e) => { + error = Some(e); + break; + } + }; + code_block = Some(block); + events.push(Event::Html(begin.into())); + } } Event::End(TagEnd::CodeBlock { .. }) => { if let Some(ref mut code_block) = code_block { @@ -695,6 +787,32 @@ pub fn markdown_to_html( event }); } + + Event::InlineMath(ref content) | Event::DisplayMath(ref content) => { + if let Some(ref compiler) = compiler { + let render_mode = if matches!(event, Event::InlineMath(_)) { + MathRenderMode::Inline + } else { + MathRenderMode::Display + }; + + let rendered = compiler.compile( + content, + render_mode, + &context.config.markdown.math.svgo, + ); + + match rendered { + Ok(svg) => { + events.push(Event::Html(svg.into())); + } + Err(e) => { + error = Some(Error::msg(format!("Failed to render math: {}", e))); + } + } + } + } + Event::Html(text) | Event::InlineHtml(text) if !has_summary && MORE_DIVIDER_RE.is_match(text.as_ref()) => { @@ -846,6 +964,10 @@ pub fn markdown_to_html( summary = Some(summary_html); } + if let Some(ref compiler) = compiler { + compiler.write_cache()?; + } + // emit everything after summary cmark::html::push_html(&mut html, events); } diff --git a/components/markdown/src/math/katex/mod.rs b/components/markdown/src/math/katex/mod.rs new file mode 100644 index 0000000000..93623986a0 --- /dev/null +++ b/components/markdown/src/math/katex/mod.rs @@ -0,0 +1,74 @@ +use std::{ + hash::{Hash, Hasher}, + sync::Arc, +}; + +use config::BoolWithPath; +use errors::{Context, Error}; +use libs::pulldown_cmark::CowStr; +use twox_hash::XxHash64; + +use super::{MathCache, MathCompiler, MathRenderMode}; +use crate::Result; + +pub struct KatexCompiler { + cache: Option>, + addon: Option, +} + +impl KatexCompiler { + pub fn new(addon: Option) -> Self { + Self { cache: None, addon } + } +} + +impl MathCompiler for KatexCompiler { + fn set_cache(&mut self, cache: Arc) { + self.cache = Some(cache); + } + + fn write_cache(&self) -> Result<()> { + if let Some(ref cache) = self.cache { + cache.write().context("Failed to write KaTeX cache")?; + } + Ok(()) + } + + fn compile(&self, tex: &str, mode: MathRenderMode, minify: &BoolWithPath) -> Result { + let tex: CowStr = if let Some(addon) = self.addon.as_ref() { + CowStr::Boxed(format!("{}{}", tex, addon).into()) + } else { + CowStr::Borrowed(tex) + }; + let mut opts = katex::Opts::builder(); + + match mode { + MathRenderMode::Inline => opts.display_mode(false), + MathRenderMode::Display => opts.display_mode(true), + MathRenderMode::Raw => return Err(Error::msg("Raw mode is not supported by KaTeX")), + }; + + let opts = opts.build().map_err(|e| Error::msg(e.to_string()))?; + // Generate cache key + let key = { + let mut hasher = XxHash64::with_seed(42); + tex.hash(&mut hasher); + mode.hash(&mut hasher); + minify.hash(&mut hasher); + format!("{:x}", hasher.finish()) + }; + + if let Some(entry) = self.cache.as_ref().and_then(|e| e.get(&key)) { + return Ok(entry.clone()); + } + + let rendered = katex::render_with_opts(&tex, &opts) + .map_err(|e| Error::msg(format!("Failed to render KaTeX: {}", e)))?; + + if let Some(cache) = self.cache.as_ref() { + cache.insert(key, rendered.clone()); + } + + Ok(rendered) + } +} diff --git a/components/markdown/src/math/mod.rs b/components/markdown/src/math/mod.rs new file mode 100644 index 0000000000..6e4b6f3ccf --- /dev/null +++ b/components/markdown/src/math/mod.rs @@ -0,0 +1,31 @@ +use std::{hash::Hash, sync::Arc}; + +use crate::cache::GenericCache; +use crate::Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum MathRenderMode { + Inline, + Display, + Raw, +} + +pub mod katex; +pub mod svgo; +pub mod typst; + +pub trait MathCompiler { + fn compile( + &self, + input: &str, + mode: MathRenderMode, + svgo: &config::BoolWithPath, + ) -> Result; + fn raw_extensions(&self) -> &'static [&'static str] { + &[] + } + fn set_cache(&mut self, cache: Arc>); + fn write_cache(&self) -> Result<()>; +} + +pub type MathCache = GenericCache; diff --git a/components/markdown/src/math/svgo.rs b/components/markdown/src/math/svgo.rs new file mode 100644 index 0000000000..66cf5d7b16 --- /dev/null +++ b/components/markdown/src/math/svgo.rs @@ -0,0 +1,50 @@ +use std::{io::Write, process::Command}; + +pub struct Svgo { + bin_path: String, +} + +impl Default for Svgo { + fn default() -> Self { + Self { bin_path: "svgo".to_string() } + } +} + +impl Svgo { + pub fn new>(bin_path: S) -> Self { + Self { bin_path: bin_path.into() } + } + + pub fn check_bin(&self) -> Result<(), String> { + let output = + Command::new(&self.bin_path).arg("--version").output().map_err(|e| e.to_string())?; + + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).to_string()); + } + + Ok(()) + } + + pub fn minify(&self, svg: &str, config: Option<&str>) -> Result { + let mut cmd = Command::new(&self.bin_path); + let mut child = cmd.arg("-i").arg("-").arg("-o").arg("-").arg("--multipass"); + + if let Some(config) = config { + child = child.arg("--config").arg(config); + } + + let mut child = child + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .map_err(|e| e.to_string())?; + child.stdin.as_mut().unwrap().write_all(svg.as_bytes()).unwrap(); + let output = child.wait_with_output().map_err(|e| e.to_string())?; + if !output.status.success() { + return Err(String::from_utf8_lossy(&output.stderr).to_string()); + } + + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + } +} diff --git a/components/markdown/src/math/typst/format.rs b/components/markdown/src/math/typst/format.rs new file mode 100644 index 0000000000..13af607d1f --- /dev/null +++ b/components/markdown/src/math/typst/format.rs @@ -0,0 +1,55 @@ +use crate::math::MathRenderMode; + +use libs::once_cell::sync::Lazy; +use libs::regex; + +static HEIGHT_RE: Lazy = + Lazy::new(|| regex::Regex::new(r#"height="((?:\d|\.)+)(?:pt)?""#).unwrap()); +static WIDTH_RE: Lazy = + Lazy::new(|| regex::Regex::new(r#"width="((?:\d|\.)+)(?:pt)?""#).unwrap()); + +const EM_PER_PT: f64 = 11.0; + +pub fn format_svg( + svg: &str, + align: Option, + render_mode: MathRenderMode, + styles: Option<&str>, +) -> String { + let height = + HEIGHT_RE.captures(svg).and_then(|caps| caps[1].parse::().ok()).unwrap_or(0.0); + + let width = WIDTH_RE.captures(svg).and_then(|caps| caps[1].parse::().ok()).unwrap_or(0.0); + let mut svg = svg.to_string(); + + if render_mode == MathRenderMode::Raw { + // Add 10pt to the height to account for the padding + svg = svg.replacen( + &format!("height=\"{}pt\"", height), + &format!("height=\"{}pt\"", height + 10.0), + 1, + ); + } + + let shift = align.map(|align| height - align); + let shift_em = shift.map(|shift| shift / EM_PER_PT); + + if let Some(styles) = styles { + svg = svg.replacen(">", &format!(">", styles), 1); + } + + let url_encoded = urlencoding::encode(&svg); + format!( + "\"\"", + match render_mode { + MathRenderMode::Display | MathRenderMode::Raw => "typst-display", + MathRenderMode::Inline => "typst-inline", + }, + if let Some(shift_em) = shift_em { + format!("vertical-align: -{}em;", shift_em) + } else { + String::new() + }, + width / EM_PER_PT + ) +} diff --git a/components/markdown/src/math/typst/mod.rs b/components/markdown/src/math/typst/mod.rs new file mode 100644 index 0000000000..64fa4d81fb --- /dev/null +++ b/components/markdown/src/math/typst/mod.rs @@ -0,0 +1,341 @@ +use config::BoolWithPath; +use errors::{Context, Error}; +use std::sync::Arc; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, + io::Write, + path::PathBuf, + sync::Mutex, +}; +use twox_hash::XxHash64; + +use typst::{ + diag::{eco_format, FileError, FileResult, PackageError, PackageResult}, + foundations::{Bytes, Datetime, Label}, + syntax::{package::PackageSpec, FileId, Source}, + text::{Font, FontBook}, + utils::LazyHash, + Library, World, +}; + +mod format; +mod templates; + +pub use format::*; + +use super::svgo::Svgo; +use super::{MathCache, MathCompiler, MathRenderMode}; +use crate::context::CACHE_DIR; +use crate::Result; + +fn fonts() -> Vec { + typst_assets::fonts() + .flat_map(|bytes| { + let buffer = Bytes::from_static(bytes); + let face_count = ttf_parser::fonts_in_collection(&buffer).unwrap_or(1); + (0..face_count).map(move |face| { + Font::new(buffer.clone(), face).expect("failed to load font from typst-assets") + }) + }) + .collect() +} + +/// Fake file +/// +/// This is a fake file which wrap the real content takes from the md math block +pub struct TypstFile { + bytes: Bytes, + + source: Option, +} + +impl TypstFile { + fn source(&mut self, id: FileId) -> FileResult { + let source = match &self.source { + Some(source) => source, + None => { + let contents = + std::str::from_utf8(&self.bytes).map_err(|_| FileError::InvalidUtf8)?; + let source = Source::new(id, contents.into()); + self.source.insert(source) + } + }; + Ok(source.clone()) + } +} + +/// Compiler +/// +/// This is the compiler which has all the necessary fields except the source +pub struct TypstCompiler { + library: LazyHash, + book: LazyHash, + fonts: Vec, + packages_cache_path: PathBuf, + files: Mutex>, + render_cache: Option>, + addon: Option, + styles: Option, +} + +impl TypstCompiler { + pub fn new( + base_cache_path: Option, + addon: Option, + styles: Option, + ) -> Self { + let fonts = fonts(); + + Self { + library: LazyHash::new(Library::default()), + book: LazyHash::new(FontBook::from_fonts(&fonts)), + fonts, + packages_cache_path: base_cache_path + .unwrap_or(CACHE_DIR.to_path_buf()) + .join("packages"), + files: Mutex::new(HashMap::new()), + render_cache: None, + addon, + styles, + } + } + + pub fn wrap_source(&self, source: impl Into) -> WrapSource<'_> { + WrapSource { + compiler: self, + source: Source::detached(source), + time: time::OffsetDateTime::now_local().unwrap_or(time::OffsetDateTime::now_utc()), + } + } + + /// Get the package directory or download if not exists + fn package(&self, package: &PackageSpec) -> PackageResult { + let package_subdir = format!("{}/{}/{}", package.namespace, package.name, package.version); + let path = self.packages_cache_path.join(package_subdir); + + if path.exists() { + return Ok(path); + } + + // Download the package + let package_url = format!( + "https://packages.typst.org/{}/{}-{}.tar.gz", + package.namespace, package.name, package.version + ); + + let mut response = libs::reqwest::blocking::get(package_url).map_err(|e| { + PackageError::NetworkFailed(Some(eco_format!( + "Failed to download package {}: {}", + package.name, + e + ))) + })?; + + let mut compressed = Vec::new(); + response.copy_to(&mut compressed).map_err(|e| { + PackageError::NetworkFailed(Some(eco_format!( + "Failed to save package {}: {}", + package.name, + e + ))) + })?; + + let mut decompressed = Vec::new(); + let mut decoder = flate2::write::GzDecoder::new(decompressed); + decoder.write_all(&compressed).map_err(|e| { + PackageError::MalformedArchive(Some(eco_format!( + "Failed to decompress package {}: {}", + package.name, + e + ))) + })?; + decoder.try_finish().map_err(|e| { + PackageError::MalformedArchive(Some(eco_format!( + "Failed to decompress package {}: {}", + package.name, + e + ))) + })?; + decompressed = decoder.finish().map_err(|e| { + PackageError::MalformedArchive(Some(eco_format!( + "Failed to decompress package {}: {}", + package.name, + e + ))) + })?; + + let mut archive = tar::Archive::new(decompressed.as_slice()); + archive.unpack(&path).map_err(|e| { + std::fs::remove_dir_all(&path).ok(); + PackageError::MalformedArchive(Some(eco_format!( + "Failed to unpack package {}: {}", + package.name, + e + ))) + })?; + + Ok(path) + } + + // Weird pattern because mapping a MutexGuard is not stable yet. + fn file(&self, id: FileId, map: impl FnOnce(&mut TypstFile) -> T) -> FileResult { + let mut files = self.files.lock().unwrap(); + if let Some(entry) = files.get_mut(&id) { + return Ok(map(entry)); + } + // `files` must stay locked here so we don't download the same package multiple times. + // TODO proper multithreading, maybe with typst-kit. + + 'x: { + if let Some(package) = id.package() { + let package_dir = self.package(package)?; + let Some(path) = id.vpath().resolve(&package_dir) else { + break 'x; + }; + let contents = + std::fs::read(&path).map_err(|error| FileError::from_io(error, &path))?; + let entry = + files.entry(id).or_insert(TypstFile { bytes: contents.into(), source: None }); + return Ok(map(entry)); + } + } + + Err(FileError::NotFound(id.vpath().as_rootless_path().into())) + } +} + +impl MathCompiler for TypstCompiler { + fn set_cache(&mut self, cache: Arc) { + self.render_cache = Some(cache); + } + + fn raw_extensions(&self) -> &'static [&'static str] { + &["typ", "typst"] + } + + fn write_cache(&self) -> Result<()> { + if let Some(ref render_cache) = self.render_cache { + render_cache.write().context("Failed to write typst cache")?; + } + Ok(()) + } + + fn compile(&self, source: &str, mode: MathRenderMode, minify: &BoolWithPath) -> Result { + // Prepare source based on mode + let source = match mode { + MathRenderMode::Display => templates::display_math(&source, self.addon.as_deref()), + MathRenderMode::Inline => templates::inline_math(&source, self.addon.as_deref()), + MathRenderMode::Raw => templates::raw(&source, self.addon.as_deref()), + }; + + // Generate cache key + let key = { + let mut hasher = XxHash64::with_seed(42); + source.hash(&mut hasher); + mode.hash(&mut hasher); + minify.hash(&mut hasher); + format!("{:x}", hasher.finish()) + }; + + // Check cache first + if let Some(entry) = self.render_cache.as_ref().and_then(|e| e.get(&key)) { + return Ok(entry.clone()); + } + + // Compile the source + let world = self.wrap_source(source); + let document = typst::compile(&world); + let warnings = document.warnings; + + if !warnings.is_empty() { + return Err(Error::msg(format!("{:?}", warnings))); + } + + let document = document.output.map_err(|diags| Error::msg(format!("{:?}", diags)))?; + let page = document.pages.first().ok_or(Error::msg("No pages found"))?; + let image = typst_svg::svg(page); + + // Minify if requested + let minified = match minify { + BoolWithPath::True(config) => { + let svgo = Svgo::default(); + svgo.minify(&image, config.as_deref()) + .map_err(|e| Error::msg(format!("Failed to minify SVG: {}", e)))? + } + BoolWithPath::False => image, + }; + + // Get alignment (for inline mode) + let align = match mode { + MathRenderMode::Inline => { + let query = document.introspector.query_label(Label::construct("label".into())); + Some( + query + .map(|it| { + let field = it.clone().field_by_name("value").unwrap(); + if let typst::foundations::Value::Length(value) = field { + value.abs.to_pt() + } else { + 0.0 + } + }) + .unwrap_or(0.0), + ) + } + MathRenderMode::Raw | MathRenderMode::Display => None, + }; + + let formatted = format_svg(&minified, align, mode, self.styles.as_deref()); + + // Cache and return + if let Some(ref render_cache) = self.render_cache { + render_cache.insert(key, formatted.clone()); + } + + Ok(formatted) + } +} + +/// Wrap source +/// +/// This is a wrapper for the source which provides ref to the compiler +pub struct WrapSource<'a> { + compiler: &'a TypstCompiler, + source: Source, + time: time::OffsetDateTime, +} + +impl World for WrapSource<'_> { + fn library(&self) -> &LazyHash { + &self.compiler.library + } + + fn book(&self) -> &LazyHash { + &self.compiler.book + } + + fn main(&self) -> FileId { + self.source.id() + } + + fn source(&self, id: FileId) -> FileResult { + if id == self.source.id() { + Ok(self.source.clone()) + } else { + self.compiler.file(id, |file| file.source(id))? + } + } + + fn file(&self, id: FileId) -> FileResult { + self.compiler.file(id, |file| file.bytes.clone()) + } + + fn font(&self, index: usize) -> Option { + self.compiler.fonts.get(index).cloned() + } + + fn today(&self, _offset: Option) -> Option { + Some(Datetime::Date(self.time.date())) + } +} diff --git a/components/markdown/src/math/typst/templates.rs b/components/markdown/src/math/typst/templates.rs new file mode 100644 index 0000000000..491d563d5f --- /dev/null +++ b/components/markdown/src/math/typst/templates.rs @@ -0,0 +1,52 @@ +pub fn display_math(code: &str, addon: Option<&str>) -> String { + let addon = addon.unwrap_or(""); + format!( + r#" +#set page(height: auto, width: auto, margin: 0pt, fill: none) +#set text(14pt) +{addon} +$ {code} $ +"#, + ) +} + +pub fn inline_math(code: &str, addon: Option<&str>) -> String { + let addon = addon.unwrap_or(""); + format!( + r#" +#set page(height: auto, width: auto, margin: 0pt, fill: none) +#set text(13pt) +{addon} +#let s = state("t", (:)) + +#let pin(t) = context {{ + let computed = measure( + line(length: here().position().y) + ) + s.update(it => it.insert(t, computed.width) + it) + }} + +#show math.equation: it => {{ + box(it, inset: (top: 0.5em, bottom: 0.5em)) + }} + +$pin("l1"){code}$ + +#context [ + #metadata(s.final().at("l1"))