heartwood every commit a ring

Render PDFs with embedded Typst instead of headless Chromium

21522012 by Isaac Bythewood · 4 days ago

Render PDFs with embedded Typst instead of headless Chromium

Drops the chrome-headless-shell subprocess in favor of compiling the
typst crate in-process. Comrak now emits Typst markup for PDF export
(blog HTML rendering is unchanged), and templates/blog_post.typ owns
the page layout that blog_post_pdf.html used to. Runtime image swaps
chromium for ttf-liberation + fontconfig so typst-kit can find a body
sans alongside the existing JetBrains Mono and DejaVu.
modified CLAUDE.md
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co## What This IsA personal blog (blog.bythewood.me) built as a Rust axum app that renders markdown files. No database — blog posts are `.md` files in `content/posts/` with YAML frontmatter. Uses headless Chromium for PDF export and comrak for markdown rendering.A personal blog (blog.bythewood.me) built as a Rust axum app that renders markdown files. No database — blog posts are `.md` files in `content/posts/` with YAML frontmatter. Uses Typst (embedded as a library) for PDF export and comrak for markdown rendering.This is a Rust port of the original Flask version (now deleted). Performance: ~30-50x lower memory, ~10-20x higher RPS, sub-millisecond per-request latency in release mode.
@@ -26,7 +26,7 @@ There are no tests or linters configured.**Templates:** Jinja2 templates in `templates/` rendered by minijinja (Jinja2-faithful Rust engine by Armin Ronacher). `base.html` is the layout. Markdown post content is rendered through comrak with a custom renderer (`src/markdown.rs`) that wraps blocks in `div.block-*` classes — mirrors the original Mistune renderer pattern.**PDF generation:** `src/pdf.rs` spawns chrome-headless-shell with `--print-to-pdf` against a temp `.html` file (the `.html` suffix matters — without it Chromium renders the HTML as plain text). Chromium binary is located via env var `CHROMIUM_BIN`, then PATH search, then a `/opt/playwright-browsers/` glob fallback.**PDF generation:** `src/pdf.rs` embeds the Typst compiler (`typst` + `typst-pdf`) as a library — no Chromium subprocess. At startup `PdfRenderer::new` runs `typst-kit`'s font searcher to discover system fonts (and bundle a few embedded ones via the `embed-fonts` feature). Per request: comrak walks the post's markdown AST to a Typst markup string (`markdown::render_typst`), `main.rs::build_typst_source` wraps it in a `#import "/templates/blog_post.typ": render` + `#render(title: ..., body: [...])` invocation, and `PdfRenderer::render` compiles that source to a `PagedDocument` then calls `typst_pdf::pdf` for the bytes. The `World` impl resolves absolute paths against the project root, so `image("/content/images/foo.webp")` reads `content/images/foo.webp` from disk.**Manifest reload:** `templates.rs::build_env` re-reads `dist/.vite/manifest.json` per `vite_asset()` call in debug builds (so Vite watcher rebuilds are picked up immediately). Release builds load it once at startup. Gated on `cfg(debug_assertions)`.
@@ -45,7 +45,7 @@ blog.bythewood.me/│   ├── posts.rs      # frontmatter + post loading│   ├── markdown.rs   # comrak custom renderer│   ├── templates.rs  # minijinja env, url_for, vite_asset, Jinja2-compat formatter│   └── pdf.rs        # chrome-headless-shell subprocess│   └── pdf.rs        # typst World + render entry point├── templates/                    # jinja2 source (minijinja-compatible)├── content/                      # markdown source (posts + images)├── frontend/                     # JS pipeline (package.json, vite.config.js, static_src/, node_modules/)
@@ -60,12 +60,12 @@ The binary reads `templates/`, `dist/`, and `content/` from the current working- **Rust deps:** managed with `cargo` (see `Cargo.toml`, `Cargo.lock`)- **JS deps:** managed with `bun`, run from `frontend/` (see `frontend/package.json`, `frontend/bun.lock`)- **Production:** Docker (Alpine-based multi-stage, `rust:alpine` builder + `alpine:3.23` runtime), deployed via `docker-compose`. Runtime image installs `chromium` for PDF generation.- **Production:** Docker (Alpine-based multi-stage, `rust:alpine` builder + `alpine:3.23` runtime), deployed via `docker-compose`. Runtime image installs `font-jetbrains-mono`, `ttf-dejavu`, `ttf-liberation`, and `fontconfig` so the embedded Typst renderer can find a body sans, mono, and fallback fonts.## Key Routes- `/posts/<slug>/` — single post (old `/blog/<slug>/` 301-redirects here)- `/posts/<slug>/pdf/` — PDF export via chrome-headless-shell- `/posts/<slug>/pdf/` — PDF export via embedded Typst (template at `templates/blog_post.typ`)- `/posts/<slug>/md/` — raw markdown download- `/blog/` — post index (also `/blog/tag/<tag>/` and `/blog/year/<year>/`)- `/search/?q=...` — server-rendered search page
modified Cargo.lock
@@ -82,6 +82,36 @@ version = "1.0.102"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"[[package]]name = "approx"version = "0.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"dependencies = [ "num-traits",][[package]]name = "ar_archive_writer"version = "0.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"dependencies = [ "object",][[package]]name = "arrayref"version = "0.3.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"[[package]]name = "arrayvec"version = "0.7.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"[[package]]name = "atomic-waker"version = "1.1.2"
@@ -146,12 +176,32 @@ dependencies = [ "tracing",][[package]]name = "az"version = "1.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7"[[package]]name = "base64"version = "0.22.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"[[package]]name = "biblatex"version = "0.11.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "53d0c374feba1b9a59042a7c1cf00ce7c34b977b9134fe7c42b08e5183729f66"dependencies = [ "paste", "roman-numerals-rs", "strum", "unic-langid", "unicode-normalization", "unscanny",][[package]]name = "bincode"version = "1.3.3"
@@ -176,11 +226,20 @@ version = "0.8.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"[[package]]name = "bitflags"version = "1.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"[[package]]name = "bitflags"version = "2.11.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"dependencies = [ "serde_core",][[package]]name = "blog"
@@ -196,10 +255,12 @@ dependencies = [ "rand", "serde", "serde_json", "tempfile", "tokio", "tower", "tower-http", "typst", "typst-kit", "typst-pdf", "urlencoding",]
@@ -234,6 +295,38 @@ version = "3.20.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"[[package]]name = "by_address"version = "1.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"[[package]]name = "bytemuck"version = "1.25.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"dependencies = [ "bytemuck_derive",][[package]]name = "bytemuck_derive"version = "1.10.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "byteorder-lite"version = "0.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"[[package]]name = "bytes"version = "1.11.1"
@@ -265,6 +358,24 @@ version = "1.0.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"[[package]]name = "chinese-number"version = "0.7.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3e964125508474a83c95eb935697abbeb446ff4e9d62c71ce880e3986d1c606b"dependencies = [ "chinese-variant", "enum-ordinalize", "num-bigint", "num-traits",][[package]]name = "chinese-variant"version = "1.1.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "58b52a9840ffff5d4d0058ae529fa066a75e794e3125546acfc61c23ad755e49"[[package]]name = "chrono"version = "0.4.44"
@@ -276,6 +387,43 @@ dependencies = [ "windows-link",][[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.6.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1f6597e8bdbca37f1f56e5a80d15857b0932aead21a78d20de49e99e74933046"dependencies = [ "quick-xml 0.38.4", "serde",][[package]]name = "clap"version = "4.6.1"
@@ -317,12 +465,57 @@ version = "1.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"[[package]]name = "cobs"version = "0.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"dependencies = [ "thiserror",][[package]]name = "codex"version = "0.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9589e1effc5cacbea347899645c654158b03b2053d24bb426fd3128ced6e423c"[[package]]name = "color_quant"version = "1.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"[[package]]name = "colorchoice"version = "1.0.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"[[package]]name = "comemo"version = "0.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3c963350b2b08aa4b725d7802593245380ab53dacfedcaa971385fc33306c0d4"dependencies = [ "comemo-macros", "parking_lot", "rustc-hash", "siphasher", "slab",][[package]]name = "comemo-macros"version = "0.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a3c400139ba1389ef9e20ad2d87cda68b437a66483aa0da616bdf2cea7413853"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "comrak"version = "0.30.0"
@@ -350,6 +543,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 = "crc32fast"version = "1.5.0"
@@ -359,6 +561,58 @@ dependencies = [ "cfg-if",][[package]]name = "crossbeam-deque"version = "0.8.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"dependencies = [ "crossbeam-epoch", "crossbeam-utils",][[package]]name = "crossbeam-epoch"version = "0.9.18"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"dependencies = [ "crossbeam-utils",][[package]]name = "crossbeam-utils"version = "0.8.21"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"[[package]]name = "crunchy"version = "0.2.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"[[package]]name = "csv"version = "1.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"dependencies = [ "csv-core", "itoa", "ryu", "serde_core",][[package]]name = "csv-core"version = "0.1.13"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"dependencies = [ "memchr",][[package]]name = "darling"version = "0.23.0"
@@ -393,6 +647,12 @@ dependencies = [ "syn",][[package]]name = "data-url"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"[[package]]name = "deranged"version = "0.5.8"
@@ -408,12 +668,70 @@ version = "1.6.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"[[package]]name = "displaydoc"version = "0.2.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "ecow"version = "0.2.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02"dependencies = [ "serde",][[package]]name = "either"version = "1.15.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"[[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 = "entities"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"[[package]]name = "enum-ordinalize"version = "4.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0"dependencies = [ "enum-ordinalize-derive",][[package]]name = "enum-ordinalize-derive"version = "4.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "equivalent"version = "1.0.2"
@@ -430,6 +748,15 @@ dependencies = [ "windows-sys",][[package]]name = "euclid"version = "0.22.14"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"dependencies = [ "num-traits",][[package]]name = "fancy-regex"version = "0.16.2"
@@ -441,12 +768,27 @@ dependencies = [ "regex-syntax",][[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.4.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"[[package]]name = "fdeflate"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"dependencies = [ "simd-adler32",][[package]]name = "find-msvc-tools"version = "0.1.9"
@@ -461,6 +803,22 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"dependencies = [ "crc32fast", "miniz_oxide", "zlib-rs",][[package]]name = "float-cmp"version = "0.9.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"[[package]]name = "float-cmp"version = "0.10.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"dependencies = [ "num-traits",][[package]]
@@ -470,10 +828,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"[[package]]name = "foldhash"version = "0.1.5"name = "font-types"version = "0.10.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5"dependencies = [ "bytemuck",][[package]]name = "fontconfig-parser"version = "0.5.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"dependencies = [ "roxmltree",][[package]]name = "fontdb"version = "0.23.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905"dependencies = [ "fontconfig-parser", "log", "memmap2", "slotmap", "tinyvec", "ttf-parser",][[package]]name = "form_urlencoded"
@@ -535,41 +919,159 @@ dependencies = [][[package]]name = "getrandom"version = "0.4.2"name = "gif"version = "0.13.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"dependencies = [ "cfg-if", "libc", "r-efi", "wasip2", "wasip3", "color_quant", "weezl",][[package]]name = "hashbrown"version = "0.15.5"name = "gif"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"dependencies = [ "foldhash", "color_quant", "weezl",][[package]]name = "hashbrown"version = "0.17.0"name = "glidesort"version = "0.1.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"checksum = "f2e102e6eb644d3e0b186fc161e4460417880a0a0b87d235f2e5b8fb30f2e9e0"[[package]]name = "heck"version = "0.5.0"name = "half"version = "2.7.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"[[package]]name = "http"checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"dependencies = [ "cfg-if", "crunchy", "zerocopy",][[package]]name = "hashbrown"version = "0.17.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"[[package]]name = "hayagriva"version = "0.9.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1cb69425736f184173b3ca6e27fcba440a61492a790c786b1c6af7e06a03e575"dependencies = [ "biblatex", "ciborium", "citationberg", "indexmap", "paste", "roman-numerals-rs", "serde", "serde_yaml", "thiserror", "unic-langid", "unicode-segmentation", "unscanny", "url",][[package]]name = "hayro"version = "0.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "048488ba88552bb0fb2a7e4001c64d5bed65d1a92167186a1bb9151571f32e60"dependencies = [ "bytemuck", "hayro-interpret", "image", "kurbo 0.12.0",][[package]]name = "hayro-font"version = "0.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "10e7e97ce840a6a70e7901e240ec65ba61106b66b37a4a1b899a2ce484248463"dependencies = [ "log", "phf",][[package]]name = "hayro-interpret"version = "0.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "56204c972d08e844f3db13b1e14be769f846e576699b46d4f4637cc4f8f70102"dependencies = [ "bitflags 2.11.1", "hayro-font", "hayro-syntax", "kurbo 0.12.0", "log", "moxcms 0.7.11", "phf", "rustc-hash", "siphasher", "skrifa", "smallvec", "yoke 0.8.2",][[package]]name = "hayro-svg"version = "0.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e8c673304cec6e0dfd3b4f71fccecd45646899aa70279b62d3f933842abc4ac5"dependencies = [ "base64", "hayro-interpret", "image", "kurbo 0.12.0", "siphasher", "xmlwriter",][[package]]name = "hayro-syntax"version = "0.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3f9e5c7dbc0f11dc42775d1a6cc00f5f5137b90b6288dd7fe5f71d17b14d10be"dependencies = [ "flate2", "kurbo 0.12.0", "log", "rustc-hash", "smallvec", "zune-jpeg 0.4.21",][[package]]name = "hayro-write"version = "0.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cc05d8b4bc878b9aee48d980ecb25ed08f1dd9fad6da5ab4d9b7c56ec03a0cf6"dependencies = [ "flate2", "hayro-syntax", "log", "pdf-writer",][[package]]name = "heck"version = "0.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"[[package]]name = "http"version = "1.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
@@ -654,6 +1156,12 @@ dependencies = [ "tower-service",][[package]]name = "hypher"version = "0.1.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bef68590049bab63a464eee1a1158ac04c6f6613a546d8d90f78636b8b94f171"[[package]]name = "iana-time-zone"version = "0.1.65"
@@ -679,271 +1187,872 @@ dependencies = [][[package]]name = "id-arena"version = "2.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"[[package]]name = "ident_case"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"[[package]]name = "indexmap"version = "2.14.0"name = "icu_collections"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"dependencies = [ "equivalent", "hashbrown 0.17.0", "displaydoc", "serde", "serde_core", "yoke 0.7.5", "zerofrom", "zerovec 0.10.4",][[package]]name = "is_terminal_polyfill"version = "1.70.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"[[package]]name = "itoa"version = "1.0.18"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"[[package]]name = "js-sys"version = "0.3.97"name = "icu_collections"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"dependencies = [ "cfg-if", "futures-util", "once_cell", "wasm-bindgen", "displaydoc", "potential_utf", "utf8_iter", "yoke 0.8.2", "zerofrom", "zerovec 0.11.6",][[package]]name = "leb128fmt"version = "0.1.0"name = "icu_locale_core"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"dependencies = [ "displaydoc", "litemap 0.8.2", "tinystr 0.8.3", "writeable 0.6.3", "zerovec 0.11.6",][[package]]name = "libc"version = "0.2.186"name = "icu_locid"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"dependencies = [ "displaydoc", "litemap 0.7.5", "tinystr 0.7.6", "writeable 0.5.5", "zerovec 0.10.4",][[package]]name = "linked-hash-map"version = "0.5.6"name = "icu_locid_transform"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider 1.5.0", "tinystr 0.7.6", "zerovec 0.10.4",][[package]]name = "linux-raw-sys"version = "0.12.1"name = "icu_locid_transform_data"version = "1.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d"[[package]]name = "lock_api"version = "0.4.14"name = "icu_normalizer"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"dependencies = [ "scopeguard", "icu_collections 2.2.0", "icu_normalizer_data", "icu_properties 2.2.0", "icu_provider 2.2.0", "smallvec", "zerovec 0.11.6",][[package]]name = "log"version = "0.4.29"name = "icu_normalizer_data"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"[[package]]name = "matchit"version = "0.8.4"name = "icu_properties"version = "1.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"dependencies = [ "displaydoc", "icu_collections 1.5.0", "icu_locid_transform", "icu_properties_data 1.5.1", "icu_provider 1.5.0", "serde", "tinystr 0.7.6", "zerovec 0.10.4",][[package]]name = "memchr"version = "2.8.0"name = "icu_properties"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"dependencies = [ "icu_collections 2.2.0", "icu_locale_core", "icu_properties_data 2.2.0", "icu_provider 2.2.0", "zerotrie 0.2.4", "zerovec 0.11.6",][[package]]name = "memo-map"version = "0.3.3"name = "icu_properties_data"version = "1.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b"checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2"[[package]]name = "mime"version = "0.3.17"name = "icu_properties_data"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"[[package]]name = "mime_guess"version = "2.0.5"name = "icu_provider"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"dependencies = [ "mime", "unicase", "displaydoc", "icu_locid", "icu_provider_macros", "postcard", "serde", "stable_deref_trait", "tinystr 0.7.6", "writeable 0.5.5", "yoke 0.7.5", "zerofrom", "zerovec 0.10.4",][[package]]name = "minijinja"version = "2.19.0"name = "icu_provider"version = "2.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d"checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"dependencies = [ "memo-map", "serde", "displaydoc", "icu_locale_core", "writeable 0.6.3", "yoke 0.8.2", "zerofrom", "zerotrie 0.2.4", "zerovec 0.11.6",][[package]]name = "miniz_oxide"version = "0.8.9"name = "icu_provider_adapters"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"checksum = "d6324dfd08348a8e0374a447ebd334044d766b1839bb8d5ccf2482a99a77c0bc"dependencies = [ "adler2", "simd-adler32", "icu_locid", "icu_locid_transform", "icu_provider 1.5.0", "tinystr 0.7.6", "zerovec 0.10.4",][[package]]name = "mio"version = "1.2.0"name = "icu_provider_blob"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"checksum = "c24b98d1365f55d78186c205817631a4acf08d7a45bdf5dc9dcf9c5d54dccf51"dependencies = [ "libc", "wasi", "windows-sys", "icu_provider 1.5.0", "postcard", "serde", "writeable 0.5.5", "zerotrie 0.1.3", "zerovec 0.10.4",][[package]]name = "num-conv"version = "0.2.1"name = "icu_provider_macros"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "num-traits"version = "0.2.19"name = "icu_segmenter"version = "1.5.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"dependencies = [ "autocfg", "core_maths", "displaydoc", "icu_collections 1.5.0", "icu_locid", "icu_provider 1.5.0", "icu_segmenter_data", "serde", "utf8_iter", "zerovec 0.10.4",][[package]]name = "once_cell"version = "1.21.4"name = "icu_segmenter_data"version = "1.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"checksum = "a1e52775179941363cc594e49ce99284d13d6948928d8e72c755f55e98caa1eb"[[package]]name = "once_cell_polyfill"version = "1.70.2"name = "ident_case"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"[[package]]name = "onig"version = "6.5.3"name = "idna"version = "1.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2"checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"dependencies = [ "bitflags", "libc", "once_cell", "onig_sys", "idna_adapter", "smallvec", "utf8_iter",][[package]]name = "onig_sys"version = "69.9.3"name = "idna_adapter"version = "1.2.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7"checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"dependencies = [ "cc", "pkg-config", "icu_normalizer", "icu_properties 2.2.0",][[package]]name = "parking_lot"version = "0.12.5"name = "image"version = "0.25.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"dependencies = [ "lock_api", "parking_lot_core", "bytemuck", "byteorder-lite", "color_quant", "gif 0.14.2", "image-webp", "moxcms 0.8.1", "num-traits", "png 0.18.1", "zune-core 0.5.1", "zune-jpeg 0.5.15",][[package]]name = "parking_lot_core"version = "0.9.12"name = "image-webp"version = "0.2.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", "byteorder-lite", "quick-error",][[package]]name = "percent-encoding"version = "2.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"[[package]]name = "pin-project-lite"version = "0.2.17"name = "imagesize"version = "0.13.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"[[package]]name = "pkg-config"version = "0.3.33"name = "imagesize"version = "0.14.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c"[[package]]name = "plist"version = "1.9.0"name = "indexmap"version = "2.14.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"dependencies = [ "base64", "indexmap", "quick-xml", "equivalent", "hashbrown", "serde", "time", "serde_core",][[package]]name = "powerfmt"version = "0.2.0"name = "infer"version = "0.19.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"[[package]]name = "ppv-lite86"version = "0.2.21"name = "is_terminal_polyfill"version = "1.70.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"[[package]]name = "itoa"version = "1.0.18"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"[[package]]name = "js-sys"version = "0.3.97"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"dependencies = [ "cfg-if", "futures-util", "once_cell", "wasm-bindgen",][[package]]name = "kamadak-exif"version = "0.6.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837"dependencies = [ "mutate_once",][[package]]name = "krilla"version = "0.6.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a0ddfec86fec13d068075e14f22a7e217c281f3ed69ddcb427bf3f5d504fd674"dependencies = [ "base64", "bumpalo", "comemo", "flate2", "float-cmp 0.10.0", "gif 0.13.3", "hayro-write", "image-webp", "imagesize 0.14.0", "indexmap", "once_cell", "pdf-writer", "png 0.17.16", "rayon", "rustc-hash", "rustybuzz", "siphasher", "skrifa", "smallvec", "subsetter", "tiny-skia-path", "xmp-writer", "yoke 0.8.2", "zune-jpeg 0.5.15",][[package]]name = "krilla-svg"version = "0.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f485e1a850201a01dcd8d73e7cf09f2cd4c4cc85c2cd296359094d49336d8ef7"dependencies = [ "flate2", "fontdb", "krilla", "png 0.17.16", "resvg", "tiny-skia", "usvg",][[package]]name = "kurbo"version = "0.11.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"dependencies = [ "arrayvec", "euclid", "smallvec",][[package]]name = "kurbo"version = "0.12.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32"dependencies = [ "arrayvec", "euclid", "smallvec",][[package]]name = "libc"version = "0.2.186"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"[[package]]name = "libm"version = "0.2.16"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"[[package]]name = "linked-hash-map"version = "0.5.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"[[package]]name = "linux-raw-sys"version = "0.12.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"[[package]]name = "lipsum"version = "0.9.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "636860251af8963cc40f6b4baadee105f02e21b28131d76eba8e40ce84ab8064"dependencies = [ "rand", "rand_chacha",][[package]]name = "litemap"version = "0.7.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"dependencies = [ "serde",][[package]]name = "litemap"version = "0.8.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"[[package]]name = "lock_api"version = "0.4.14"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"dependencies = [ "scopeguard",][[package]]name = "log"version = "0.4.29"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"[[package]]name = "matchit"version = "0.8.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"[[package]]name = "memchr"version = "2.8.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"[[package]]name = "memmap2"version = "0.9.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"dependencies = [ "libc",][[package]]name = "memo-map"version = "0.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b"[[package]]name = "mime"version = "0.3.17"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"[[package]]name = "mime_guess"version = "2.0.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"dependencies = [ "mime", "unicase",][[package]]name = "minijinja"version = "2.19.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d"dependencies = [ "memo-map", "serde",][[package]]name = "miniz_oxide"version = "0.8.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"dependencies = [ "adler2", "simd-adler32",][[package]]name = "mio"version = "1.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"dependencies = [ "libc", "wasi", "windows-sys",][[package]]name = "moxcms"version = "0.7.11"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"dependencies = [ "num-traits", "pxfm",][[package]]name = "moxcms"version = "0.8.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"dependencies = [ "num-traits", "pxfm",][[package]]name = "mutate_once"version = "0.1.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af"[[package]]name = "num-bigint"version = "0.4.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"dependencies = [ "num-integer", "num-traits",][[package]]name = "num-conv"version = "0.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"[[package]]name = "num-integer"version = "0.1.46"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"dependencies = [ "num-traits",][[package]]name = "num-traits"version = "0.2.19"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"dependencies = [ "autocfg",][[package]]name = "object"version = "0.37.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"dependencies = [ "memchr",][[package]]name = "once_cell"version = "1.21.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"[[package]]name = "once_cell_polyfill"version = "1.70.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"[[package]]name = "onig"version = "6.5.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2"dependencies = [ "bitflags 2.11.1", "libc", "once_cell", "onig_sys",][[package]]name = "onig_sys"version = "69.9.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7"dependencies = [ "cc", "pkg-config",][[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",][[package]]name = "parking_lot"version = "0.12.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"dependencies = [ "lock_api", "parking_lot_core",][[package]]name = "parking_lot_core"version = "0.9.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link",][[package]]name = "paste"version = "1.0.15"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"[[package]]name = "pdf-writer"version = "0.14.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "92a79477295a713c2ed425aa82a8b5d20cec3fdee203706cbe6f3854880c1c81"dependencies = [ "bitflags 2.11.1", "itoa", "memchr", "ryu",][[package]]name = "percent-encoding"version = "2.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"[[package]]name = "phf"version = "0.13.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"dependencies = [ "phf_macros", "phf_shared", "serde",][[package]]name = "phf_generator"version = "0.13.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"dependencies = [ "fastrand", "phf_shared",][[package]]name = "phf_macros"version = "0.13.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn",][[package]]name = "phf_shared"version = "0.13.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"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-lite"version = "0.2.17"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"[[package]]name = "pkg-config"version = "0.3.33"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"[[package]]name = "plist"version = "1.9.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1"dependencies = [ "base64", "indexmap", "quick-xml 0.39.3", "serde", "time",][[package]]name = "png"version = "0.17.16"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide",][[package]]name = "png"version = "0.18.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"dependencies = [ "bitflags 2.11.1", "crc32fast", "fdeflate", "flate2", "miniz_oxide",][[package]]name = "portable-atomic"version = "1.13.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"[[package]]name = "postcard"version = "1.1.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", "serde",][[package]]name = "potential_utf"version = "0.1.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"dependencies = [ "zerovec 0.11.6",][[package]]name = "powerfmt"version = "0.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"[[package]]name = "ppv-lite86"version = "0.2.21"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"dependencies = [ "zerocopy",]
@@ -958,6 +2067,12 @@ dependencies = [ "syn",][[package]]name = "proc-macro-hack"version = "0.5.20+deprecated"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"[[package]]name = "proc-macro2"version = "1.0.106"
@@ -967,6 +2082,44 @@ dependencies = [ "unicode-ident",][[package]]name = "psm"version = "0.1.31"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea"dependencies = [ "ar_archive_writer", "cc",][[package]]name = "pxfm"version = "0.1.29"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"[[package]]name = "qcms"version = "0.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa"[[package]]name = "quick-error"version = "2.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"[[package]]name = "quick-xml"version = "0.38.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"dependencies = [ "memchr", "serde",][[package]]name = "quick-xml"version = "0.39.3"
@@ -985,12 +2138,6 @@ dependencies = [ "proc-macro2",][[package]]name = "r-efi"version = "6.0.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"[[package]]name = "rand"version = "0.8.6"
@@ -1018,7 +2165,37 @@ version = "0.6.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"dependencies = [ "getrandom 0.2.17", "getrandom",][[package]]name = "rayon"version = "1.12.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"dependencies = [ "either", "rayon-core",][[package]]name = "rayon-core"version = "1.13.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"dependencies = [ "crossbeam-deque", "crossbeam-utils",][[package]]name = "read-fonts"version = "0.35.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358"dependencies = [ "bytemuck", "font-types",][[package]]
@@ -1027,7 +2204,7 @@ version = "0.5.18"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"dependencies = [ "bitflags", "bitflags 2.11.1",][[package]]
@@ -1059,13 +2236,67 @@ version = "0.8.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"[[package]]name = "resvg"version = "0.45.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43"dependencies = [ "gif 0.13.3", "image-webp", "log", "pico-args", "rgb", "svgtypes", "tiny-skia", "usvg", "zune-jpeg 0.4.21",][[package]]name = "rgb"version = "0.8.53"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"dependencies = [ "bytemuck",][[package]]name = "roman-numerals-rs"version = "3.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c85cd47a33a4510b1424fe796498e174c6a9cf94e606460ef022a19f3e4ff85e"[[package]]name = "roxmltree"version = "0.20.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"[[package]]name = "rust_decimal"version = "1.42.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0c5108e3d4d903e21aac27f12ba5377b6b34f9f44b325e4894c7924169d06995"dependencies = [ "arrayvec", "num-traits",][[package]]name = "rustc-hash"version = "2.1.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"[[package]]name = "rustix"version = "1.1.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"dependencies = [ "bitflags", "bitflags 2.11.1", "errno", "libc", "linux-raw-sys",
@@ -1078,6 +2309,24 @@ version = "1.0.22"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"[[package]]name = "rustybuzz"version = "0.20.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"dependencies = [ "bitflags 2.11.1", "bytemuck", "core_maths", "log", "smallvec", "ttf-parser", "unicode-bidi-mirroring", "unicode-ccc", "unicode-properties", "unicode-script",][[package]]name = "ryu"version = "1.0.23"
@@ -1099,12 +2348,6 @@ version = "1.2.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"[[package]]name = "semver"version = "1.0.28"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"[[package]]name = "serde"version = "1.0.228"
@@ -1159,6 +2402,15 @@ dependencies = [ "serde_core",][[package]]name = "serde_spanned"version = "0.6.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"dependencies = [ "serde",][[package]]name = "serde_urlencoded"version = "0.7.1"
@@ -1171,6 +2423,19 @@ dependencies = [ "serde",][[package]]name = "serde_yaml"version = "0.9.34+deprecated"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml",][[package]]name = "shell-words"version = "1.1.1"
@@ -1187,55 +2452,166 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"name = "signal-hook-registry"version = "1.4.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"dependencies = [ "errno", "libc",][[package]]name = "simd-adler32"version = "0.3.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"[[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.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"[[package]]name = "skrifa"version = "0.37.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841"dependencies = [ "bytemuck", "read-fonts",][[package]]name = "slab"version = "0.4.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"[[package]]name = "slotmap"version = "1.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"dependencies = [ "version_check",][[package]]name = "slug"version = "0.1.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"dependencies = [ "deunicode", "wasm-bindgen",][[package]]name = "smallvec"version = "1.15.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"[[package]]name = "socket2"version = "0.6.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"dependencies = [ "libc", "windows-sys",][[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.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"[[package]]name = "stacker"version = "0.1.24"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190"dependencies = [ "errno", "cc", "cfg-if", "libc", "psm", "windows-sys",][[package]]name = "simd-adler32"version = "0.3.9"name = "strict-num"version = "0.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"dependencies = [ "float-cmp 0.9.0",][[package]]name = "slab"version = "0.4.12"name = "strsim"version = "0.11.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"[[package]]name = "slug"version = "0.1.6"name = "strum"version = "0.27.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"dependencies = [ "deunicode", "wasm-bindgen", "strum_macros",][[package]]name = "smallvec"version = "1.15.1"name = "strum_macros"version = "0.27.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"dependencies = [ "heck", "proc-macro2", "quote", "syn",][[package]]name = "socket2"version = "0.6.3"name = "subsetter"version = "0.2.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"checksum = "cb6895a12ac5599bb6057362f00e8a3cf1daab4df33f553a55690a44e4fed8d0"dependencies = [ "libc", "windows-sys", "kurbo 0.12.0", "rustc-hash", "skrifa", "write-fonts",][[package]]name = "strsim"version = "0.11.1"name = "svgtypes"version = "0.15.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"dependencies = [ "kurbo 0.11.3", "siphasher",][[package]]name = "syn"
@@ -1254,6 +2630,17 @@ version = "1.0.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"[[package]]name = "synstructure"version = "0.13.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "syntect"version = "5.3.0"
@@ -1276,19 +2663,6 @@ dependencies = [ "yaml-rust",][[package]]name = "tempfile"version = "3.27.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", "rustix", "windows-sys",][[package]]name = "terminal_size"version = "0.4.4"
@@ -1299,6 +2673,12 @@ dependencies = [ "windows-sys",][[package]]name = "thin-vec"version = "0.2.18"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b0f7e269b48f0a7dd0146680fa24b50cc67fc0373f086a5b2f99bd084639b482"[[package]]name = "thiserror"version = "2.0.18"
@@ -1350,6 +2730,54 @@ dependencies = [ "time-core",][[package]]name = "tiny-skia"version = "0.11.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if", "log", "png 0.17.16", "tiny-skia-path",][[package]]name = "tiny-skia-path"version = "0.11.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"dependencies = [ "arrayref", "bytemuck", "strict-num",][[package]]name = "tinystr"version = "0.7.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"dependencies = [ "displaydoc", "serde", "zerovec 0.10.4",][[package]]name = "tinystr"version = "0.8.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"dependencies = [ "displaydoc", "serde_core", "zerovec 0.11.6",][[package]]name = "tinyvec"version = "1.11.0"
@@ -1406,6 +2834,47 @@ dependencies = [ "tokio",][[package]]name = "toml"version = "0.8.23"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit",][[package]]name = "toml_datetime"version = "0.6.11"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"dependencies = [ "serde",][[package]]name = "toml_edit"version = "0.22.27"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "toml_write", "winnow",][[package]]name = "toml_write"version = "0.1.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"[[package]]name = "tower"version = "0.5.3"
@@ -1428,7 +2897,7 @@ version = "0.6.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a28f0d049ccfaa566e14e9663d304d8577427b368cb4710a20528690287a738b"dependencies = [ "bitflags", "bitflags 2.11.1", "bytes", "futures-core", "futures-util",
@@ -1448,42 +2917,418 @@ dependencies = [][[package]]name = "tower-layer"version = "0.3.3"name = "tower-layer"version = "0.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"[[package]]name = "tower-service"version = "0.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"[[package]]name = "tracing"version = "0.1.44"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"dependencies = [ "log", "pin-project-lite", "tracing-core",][[package]]name = "tracing-core"version = "0.1.36"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"dependencies = [ "once_cell",][[package]]name = "ttf-parser"version = "0.25.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"dependencies = [ "core_maths",][[package]]name = "two-face"version = "0.4.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "39e51b6e60e545cfdae5a4639ff423818f52372211a8d9a3e892b4b0761f76b2"dependencies = [ "serde", "serde_derive", "syntect",][[package]]name = "typed-arena"version = "2.0.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"[[package]]name = "typst"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1f6511ee598476f4f322b4d13891083d96dbacb8f9c2b908604c7094ba390653"dependencies = [ "comemo", "ecow", "rustc-hash", "typst-eval", "typst-html", "typst-layout", "typst-library", "typst-macros", "typst-realize", "typst-syntax", "typst-timing", "typst-utils",][[package]]name = "typst-assets"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5613cb719a6222fe9b74027c3625d107767ec187bff26b8fc931cf58942c834f"[[package]]name = "typst-eval"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "687757487dfc0c1e941344d5024cf7a28364e70c3e304faad89ac65597f62526"dependencies = [ "comemo", "ecow", "indexmap", "rustc-hash", "stacker", "toml", "typst-library", "typst-macros", "typst-syntax", "typst-timing", "typst-utils", "unicode-segmentation",][[package]]name = "typst-html"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e29f8da4f964d4c90739c3c1e0288b0ba1bccc3cc50623a6d558300b86ca8aad"dependencies = [ "bumpalo", "comemo", "ecow", "palette", "rustc-hash", "time", "typst-assets", "typst-library", "typst-macros", "typst-svg", "typst-syntax", "typst-timing", "typst-utils",][[package]]name = "typst-kit"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "31476ec753e080ffdd543a0e74b6d319355449ff3eca3f216634f31cfd09a92a"dependencies = [ "ecow", "fontdb", "once_cell", "serde", "serde_json", "typst-assets", "typst-library", "typst-syntax", "typst-timing", "typst-utils",][[package]]name = "typst-layout"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4cab0200105831a9158e63718a0f6141c78cb2c1722ed17d19ad28941e3b8491"dependencies = [ "az", "bumpalo", "codex", "comemo", "ecow", "either", "hypher", "icu_properties 1.5.1", "icu_provider 1.5.0", "icu_provider_adapters", "icu_provider_blob", "icu_segmenter", "kurbo 0.12.0", "memchr", "rustc-hash", "rustybuzz", "smallvec", "ttf-parser", "typst-assets", "typst-library", "typst-macros", "typst-syntax", "typst-timing", "typst-utils", "unicode-bidi", "unicode-math-class", "unicode-script", "unicode-segmentation",][[package]]name = "typst-library"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e276a5de53020c43efe2111ec236252e54ea4480b5ac18063e663dfbe03d9d1b"dependencies = [ "az", "bitflags 2.11.1", "bumpalo", "chinese-number", "ciborium", "codex", "comemo", "csv", "ecow", "flate2", "fontdb", "glidesort", "hayagriva", "hayro-syntax", "icu_properties 1.5.1", "icu_provider 1.5.0", "icu_provider_blob", "image", "indexmap", "kamadak-exif", "kurbo 0.12.0", "lipsum", "memchr", "palette", "phf", "png 0.17.16", "qcms", "rayon", "regex", "regex-syntax", "roxmltree", "rust_decimal", "rustc-hash", "rustybuzz", "serde", "serde_json", "serde_yaml", "siphasher", "smallvec", "syntect", "time", "toml", "ttf-parser", "two-face", "typed-arena", "typst-assets", "typst-macros", "typst-syntax", "typst-timing", "typst-utils", "unicode-math-class", "unicode-normalization", "unicode-segmentation", "unscanny", "usvg", "utf8_iter", "wasmi", "xmlwriter",][[package]]name = "typst-macros"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "141cbd1027129fbf6bda1013f52a264df7befc7388cc8f47767d65e803fd3a59"dependencies = [ "heck", "proc-macro2", "quote", "syn",][[package]]name = "typst-pdf"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "37c8a4630754767cd10d48e8b8186e7dc784631a30a3a93521edf7d77aebd0c0"dependencies = [ "az", "bytemuck", "comemo", "ecow", "image", "indexmap", "infer", "krilla", "krilla-svg", "rustc-hash", "serde", "smallvec", "typst-assets", "typst-library", "typst-macros", "typst-syntax", "typst-timing", "typst-utils",][[package]]name = "typst-realize"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f7ffe964757fb93d2e98978aa2a74ee85b0f94c8643e8f3550737258b58f39d8"dependencies = [ "arrayvec", "bumpalo", "comemo", "ecow", "regex", "typst-library", "typst-macros", "typst-syntax", "typst-timing", "typst-utils",][[package]]name = "typst-svg"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e46b811837ade1f0243ef0d8bf3fb06d166443090eac22c28643f374c2ccdc9d"dependencies = [ "base64", "comemo", "ecow", "flate2", "hayro", "hayro-svg", "image", "rustc-hash", "ttf-parser", "typst-assets", "typst-library", "typst-macros", "typst-timing", "typst-utils", "xmlparser", "xmlwriter",][[package]]name = "typst-syntax"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a95d9192060e23b1e491b0b94dff676acddc92a4d672aeb8ca3890a5a734e879"dependencies = [ "ecow", "rustc-hash", "serde", "toml", "typst-timing", "typst-utils", "unicode-ident", "unicode-math-class", "unicode-script", "unicode-segmentation", "unscanny",][[package]]name = "typst-timing"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7be94f8faf19841b49574ef5c7fd7a12e2deb7c3d8deba5a596f35d2222024cd"dependencies = [ "parking_lot", "serde", "serde_json",][[package]]name = "typst-utils"version = "0.14.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"checksum = "a3966c92e8fa48c7ce898130d07000d985f18206d92b250f0f939287fbccdee3"dependencies = [ "once_cell", "portable-atomic", "rayon", "rustc-hash", "siphasher", "thin-vec", "unicode-math-class",][[package]]name = "tower-service"version = "0.3.3"name = "unic-langid"version = "0.9.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"dependencies = [ "unic-langid-impl", "unic-langid-macros",][[package]]name = "tracing"version = "0.1.44"name = "unic-langid-impl"version = "0.9.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"dependencies = [ "log", "pin-project-lite", "tracing-core", "serde", "tinystr 0.8.3",][[package]]name = "tracing-core"version = "0.1.36"name = "unic-langid-macros"version = "0.9.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"checksum = "d5957eb82e346d7add14182a3315a7e298f04e1ba4baac36f7f0dbfedba5fc25"dependencies = [ "once_cell", "proc-macro-hack", "tinystr 0.8.3", "unic-langid-impl", "unic-langid-macros-impl",][[package]]name = "typed-arena"version = "2.0.2"name = "unic-langid-macros-impl"version = "0.9.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5"dependencies = [ "proc-macro-hack", "quote", "syn", "unic-langid-impl",][[package]]name = "unicase"
@@ -1491,12 +3336,36 @@ version = "2.9.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"[[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.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"[[package]]name = "unicode-ccc"version = "0.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"[[package]]name = "unicode-ident"version = "1.0.24"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"[[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.25"
@@ -1507,10 +3376,28 @@ dependencies = [][[package]]name = "unicode-xid"version = "0.2.6"name = "unicode-properties"version = "0.1.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"[[package]]name = "unicode-script"version = "0.5.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee"[[package]]name = "unicode-segmentation"version = "1.13.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"[[package]]name = "unicode-vo"version = "0.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"[[package]]name = "unicode_categories"
@@ -1518,18 +3405,82 @@ version = "0.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"[[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 = "url"version = "2.5.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", "serde_derive",][[package]]name = "urlencoding"version = "2.1.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"[[package]]name = "usvg"version = "0.45.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef"dependencies = [ "base64", "data-url", "flate2", "fontdb", "imagesize 0.13.0", "kurbo 0.11.3", "log", "pico-args", "roxmltree", "rustybuzz", "simplecss", "siphasher", "strict-num", "svgtypes", "tiny-skia-path", "unicode-bidi", "unicode-script", "unicode-vo", "xmlwriter",][[package]]name = "utf8_iter"version = "1.0.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"[[package]]name = "utf8parse"version = "0.2.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"[[package]]name = "version_check"version = "0.9.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"[[package]]name = "walkdir"version = "2.5.0"
@@ -1546,24 +3497,6 @@ version = "0.11.1+wasi-snapshot-preview1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"[[package]]name = "wasip2"version = "1.0.3+wasi-0.2.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"dependencies = [ "wit-bindgen 0.57.1",][[package]]name = "wasip3"version = "0.4.0+wasi-0.3.0-rc-2026-01-06"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"dependencies = [ "wit-bindgen 0.51.0",][[package]]name = "wasm-bindgen"version = "0.2.120"
@@ -1610,39 +3543,57 @@ dependencies = [][[package]]name = "wasm-encoder"version = "0.244.0"name = "wasmi"version = "0.51.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"checksum = "bb321403ce594274827657a908e13d1d9918aa02257b8bf8391949d9764023ff"dependencies = [ "leb128fmt", "spin", "wasmi_collections", "wasmi_core", "wasmi_ir", "wasmparser",][[package]]name = "wasm-metadata"version = "0.244.0"name = "wasmi_collections"version = "0.51.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e9b8e98e45a2a534489f8225e765cbf1cb9a3078072605e58158910cf4749172"[[package]]name = "wasmi_core"version = "0.51.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"checksum = "c25f375c0cdf14810eab07f532f61f14d4966f09c747a55067fdf3196e8512e6"dependencies = [ "anyhow", "indexmap", "wasm-encoder", "wasmparser", "libm",][[package]]name = "wasmi_ir"version = "0.51.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "624e2a68a4293ecb8f564260b68394b29cf3b3edba6bce35532889a2cb33c3d9"dependencies = [ "wasmi_core",][[package]]name = "wasmparser"version = "0.244.0"version = "0.228.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3"dependencies = [ "bitflags", "hashbrown 0.15.5", "indexmap", "semver", "bitflags 2.11.1",][[package]]name = "weezl"version = "0.1.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"[[package]]name = "winapi-util"version = "0.1.11"
@@ -1721,136 +3672,267 @@ dependencies = [][[package]]name = "wit-bindgen"version = "0.51.0"name = "winnow"version = "0.7.15"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"dependencies = [ "memchr",][[package]]name = "write-fonts"version = "0.43.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "886614b5ce857341226aa091f3c285e450683894acaaa7887f366c361efef79d"dependencies = [ "font-types", "indexmap", "kurbo 0.12.0", "log", "read-fonts",][[package]]name = "writeable"version = "0.5.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"[[package]]name = "writeable"version = "0.6.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"[[package]]name = "xdg"version = "2.5.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"[[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 = "xmp-writer"version = "0.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"[[package]]name = "yaml-rust"version = "0.4.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"dependencies = [ "linked-hash-map",][[package]]name = "yoke"version = "0.7.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"dependencies = [ "wit-bindgen-rust-macro", "serde", "stable_deref_trait", "yoke-derive 0.7.5", "zerofrom",][[package]]name = "wit-bindgen"version = "0.57.1"name = "yoke"version = "0.8.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"dependencies = [ "stable_deref_trait", "yoke-derive 0.8.2", "zerofrom",][[package]]name = "wit-bindgen-core"version = "0.51.0"name = "yoke-derive"version = "0.7.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"dependencies = [ "anyhow", "heck", "wit-parser", "proc-macro2", "quote", "syn", "synstructure",][[package]]name = "wit-bindgen-rust"version = "0.51.0"name = "yoke-derive"version = "0.8.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"dependencies = [ "anyhow", "heck", "indexmap", "prettyplease", "proc-macro2", "quote", "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", "synstructure",][[package]]name = "wit-bindgen-rust-macro"version = "0.51.0"name = "zerocopy"version = "0.8.48"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"dependencies = [ "zerocopy-derive",][[package]]name = "zerocopy-derive"version = "0.8.48"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"dependencies = [ "anyhow", "prettyplease", "proc-macro2", "quote", "syn", "wit-bindgen-core", "wit-bindgen-rust",][[package]]name = "wit-component"version = "0.244.0"name = "zerofrom"version = "0.1.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"dependencies = [ "anyhow", "bitflags", "indexmap", "log", "serde", "serde_derive", "serde_json", "wasm-encoder", "wasm-metadata", "wasmparser", "wit-parser", "zerofrom-derive",][[package]]name = "wit-parser"version = "0.244.0"name = "zerofrom-derive"version = "0.1.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"dependencies = [ "anyhow", "id-arena", "indexmap", "log", "semver", "proc-macro2", "quote", "syn", "synstructure",][[package]]name = "zerotrie"version = "0.1.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f"dependencies = [ "displaydoc", "litemap 0.7.5", "serde", "serde_derive", "serde_json", "unicode-xid", "wasmparser", "zerovec 0.10.4",][[package]]name = "xdg"version = "2.5.2"name = "zerotrie"version = "0.2.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"dependencies = [ "displaydoc", "yoke 0.8.2", "zerofrom",][[package]]name = "yaml-rust"version = "0.4.5"name = "zerovec"version = "0.10.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"dependencies = [ "linked-hash-map", "serde", "yoke 0.7.5", "zerofrom", "zerovec-derive 0.10.3",][[package]]name = "zerocopy"version = "0.8.48"name = "zerovec"version = "0.11.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"dependencies = [ "zerocopy-derive", "serde", "yoke 0.8.2", "zerofrom", "zerovec-derive 0.11.3",][[package]]name = "zerocopy-derive"version = "0.8.48"name = "zerovec-derive"version = "0.10.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "zerovec-derive"version = "0.11.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"dependencies = [ "proc-macro2", "quote", "syn",][[package]]name = "zlib-rs"version = "0.6.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513"[[package]]name = "zmij"version = "1.0.21"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"[[package]]name = "zune-core"version = "0.4.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"[[package]]name = "zune-core"version = "0.5.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"[[package]]name = "zune-jpeg"version = "0.4.21"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"dependencies = [ "zune-core 0.4.12",][[package]]name = "zune-jpeg"version = "0.5.15"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"dependencies = [ "zune-core 0.5.1",]
modified Cargo.toml
@@ -16,9 +16,11 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }rand = "0.8"urlencoding = "2"once_cell = "1"tempfile = "3"anyhow = "1"mime_guess = "2"typst = "0.14"typst-pdf = "0.14"typst-kit = { version = "0.14", default-features = false, features = ["fonts", "embed-fonts"] }[profile.release]lto = true
modified Dockerfile
@@ -22,7 +22,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \FROM alpine:3.23RUN apk add --no-cache \    chromium font-jetbrains-mono ttf-dejavu    font-jetbrains-mono ttf-dejavu ttf-liberation fontconfigWORKDIR /app
modified src/main.rs
@@ -33,7 +33,7 @@ struct AppState {    posts: Arc<Vec<Post>>,    posts_by_slug: Arc<HashMap<String, usize>>,    content_dir: PathBuf,    server_base: String,    pdf_renderer: Arc<pdf::PdfRenderer>,}#[tokio::main]
@@ -60,12 +60,14 @@ async fn main() {        .and_then(|v| v.parse().ok())        .unwrap_or(8000);    let pdf_renderer = Arc::new(pdf::PdfRenderer::new(project_root.clone()));    let state = AppState {        env: Arc::new(env),        posts: Arc::new(posts),        posts_by_slug: Arc::new(posts_by_slug),        content_dir: content_dir.clone(),        server_base: format!("http://127.0.0.1:{port}"),        pdf_renderer,    };    let app = Router::new()
@@ -377,21 +379,18 @@ async fn blog_post(async fn blog_post_pdf(    State(state): State<AppState>,    AxumPath(slug): AxumPath<String>,    uri: Uri,    headers: HeaderMap,) -> Result<Response, AppError> {    let request = build_request(&uri, &headers);    let idx = state.posts_by_slug.get(&slug).copied().ok_or_else(AppError::not_found)?;    let post = state.posts[idx].clone();    if post.publish_date.as_str() > today().as_str() {        return Err(AppError::not_found());    }    let tmpl = state.env.get_template("blog_post_pdf.html")?;    let html = tmpl.render(context! { post => &post, request => &request })?;    let server_base = state.server_base.clone();    let pdf = tokio::task::spawn_blocking(move || pdf::html_to_pdf(&html, &server_base))    let source = build_typst_source(&post);    let renderer = state.pdf_renderer.clone();    let pdf = tokio::task::spawn_blocking(move || renderer.render(source))        .await        .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))??;        .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?        .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;    let mut h = HeaderMap::new();    h.insert(header::CONTENT_TYPE, "application/pdf".parse().unwrap());    h.insert(
@@ -401,6 +400,43 @@ async fn blog_post_pdf(    Ok((StatusCode::OK, h, Body::from(pdf)).into_response())}fn build_typst_source(post: &Post) -> String {    use std::fmt::Write;    let q = markdown::typst_str_lit;    let mut s = String::with_capacity(post.body_typst.len() + 512);    s.push_str("#import \"/templates/blog_post.typ\": render\n");    s.push_str("#render(\n");    writeln!(s, "  title: \"{}\",", q(&post.title)).ok();    writeln!(s, "  date: \"{}\",", q(&post.date)).ok();    writeln!(s, "  read_time: {},", post.read_time).ok();    s.push_str("  tags: (");    for (i, t) in post.tags.iter().enumerate() {        if i > 0 {            s.push_str(", ");        }        write!(s, "\"{}\"", q(t)).ok();    }    if post.tags.len() == 1 {        s.push(',');    }    s.push_str("),\n");    writeln!(s, "  description: \"{}\",", q(&post.description)).ok();    if post.cover_image.is_empty() {        s.push_str("  cover_image: none,\n");    } else {        writeln!(            s,            "  cover_image: \"/content/images/{}\",",            q(&post.cover_image)        )        .ok();    }    s.push_str("  body: [\n");    s.push_str(&post.body_typst);    s.push_str("\n  ],\n)\n");    s}async fn blog_post_md(    State(state): State<AppState>,    AxumPath(slug): AxumPath<String>,
modified src/markdown.rs
@@ -18,25 +18,25 @@ pub fn render_blog(md: &str) -> String {    let opts = options();    let root = comrak::parse_document(&arena, md, &opts);    let mut out = String::with_capacity(md.len() * 2);    render_node(root, &mut out, &opts, false);    render_node(root, &mut out, &opts);    out}pub fn render_pdf(md: &str) -> String {pub fn render_typst(md: &str) -> String {    let arena = Arena::new();    let opts = options();    let root = comrak::parse_document(&arena, md, &opts);    let mut out = String::with_capacity(md.len() * 2);    render_node(root, &mut out, &opts, true);    render_block_typst(root, &mut out);    out}fn render_node<'a>(node: &'a AstNode<'a>, out: &mut String, opts: &ComrakOptions, pdf: bool) {fn render_node<'a>(node: &'a AstNode<'a>, out: &mut String, opts: &ComrakOptions) {    let value = &node.data.borrow().value;    match value {        NodeValue::Document => {            for child in node.children() {                render_node(child, out, opts, pdf);                render_node(child, out, opts);            }        }        NodeValue::Paragraph => {
@@ -72,7 +72,7 @@ fn render_node<'a>(node: &'a AstNode<'a>, out: &mut String, opts: &ComrakOptions            let tag = if ordered { "ol" } else { "ul" };            write!(out, "<div class=\"block-rich-text\"><{tag}>").ok();            for child in node.children() {                render_node(child, out, opts, pdf);                render_node(child, out, opts);            }            write!(out, "</{tag}></div>\n").ok();        }
@@ -87,7 +87,7 @@ fn render_node<'a>(node: &'a AstNode<'a>, out: &mut String, opts: &ComrakOptions                            render_inline(c, out, opts);                        }                    }                    _ => render_node(child, out, opts, pdf),                    _ => render_node(child, out, opts),                }            }            out.push_str("</li>");
@@ -95,7 +95,7 @@ fn render_node<'a>(node: &'a AstNode<'a>, out: &mut String, opts: &ComrakOptions        NodeValue::BlockQuote => {            out.push_str("<div class=\"block-rich-text\"><blockquote>");            for child in node.children() {                render_node(child, out, opts, pdf);                render_node(child, out, opts);            }            out.push_str("</blockquote></div>\n");        }
@@ -104,23 +104,15 @@ fn render_node<'a>(node: &'a AstNode<'a>, out: &mut String, opts: &ComrakOptions        }        NodeValue::CodeBlock(c) => {            let mut lang = c.info.split_whitespace().next().unwrap_or("").to_string();            if !pdf && lang == "html" {            if lang == "html" {                lang = "htmlmixed".to_string();            }            let escaped = html_escape(&c.literal);            if pdf {                write!(                    out,                    "<div class=\"block-code\"><pre><code class=\"language-{lang}\">{escaped}</code></pre></div>\n"                )                .ok();            } else {                write!(                    out,                    "<div class=\"block-code\"><textarea data-language=\"{lang}\">{escaped}</textarea></div>\n"                )                .ok();            }            write!(                out,                "<div class=\"block-code\"><textarea data-language=\"{lang}\">{escaped}</textarea></div>\n"            )            .ok();        }        NodeValue::HtmlBlock(h) => {            out.push_str(&h.literal);
@@ -163,7 +155,7 @@ fn render_node<'a>(node: &'a AstNode<'a>, out: &mut String, opts: &ComrakOptions            let buf = RefCell::new(String::new());            // For simplicity, just iterate children            for child in node.children() {                render_node(child, out, opts, pdf);                render_node(child, out, opts);            }            drop(buf);        }
@@ -288,3 +280,262 @@ fn html_escape(s: &str) -> String {    }    out}// ----- Typst conversion (used for PDF export) -----fn render_block_typst<'a>(node: &'a AstNode<'a>, out: &mut String) {    let value = &node.data.borrow().value;    match value {        NodeValue::Document => {            for child in node.children() {                render_block_typst(child, out);            }        }        NodeValue::Paragraph => {            if is_paragraph_only_image(node) {                if let Some(child) = node.children().next() {                    if let NodeValue::Image(l) = &child.data.borrow().value {                        emit_block_image(l, child, out);                    }                }            } else {                for child in node.children() {                    render_inline_typst(child, out);                }                out.push_str("\n\n");            }        }        NodeValue::Heading(h) => {            for _ in 0..h.level {                out.push('=');            }            out.push(' ');            for child in node.children() {                render_inline_typst(child, out);            }            out.push_str("\n\n");        }        NodeValue::List(l) => {            let ordered = matches!(l.list_type, comrak::nodes::ListType::Ordered);            for child in node.children() {                emit_list_item_typst(child, out, ordered);            }            out.push('\n');        }        NodeValue::Item(_) => {            // Handled by parent List        }        NodeValue::BlockQuote => {            out.push_str("#quote(block: true)[\n");            for child in node.children() {                render_block_typst(child, out);            }            out.push_str("]\n\n");        }        NodeValue::ThematicBreak => {            out.push_str(                "#align(center)[#line(length: 30%, stroke: 0.5pt + gray)]\n\n",            );        }        NodeValue::CodeBlock(c) => {            let lang = c.info.split_whitespace().next().unwrap_or("");            out.push_str("#raw(block: true");            if !lang.is_empty() {                out.push_str(", lang: \"");                out.push_str(&typst_str_lit(lang));                out.push('"');            }            out.push_str(", \"");            out.push_str(&typst_str_lit(&c.literal));            out.push_str("\")\n\n");        }        NodeValue::HtmlBlock(_) => {            // Skip raw HTML in PDF output.        }        NodeValue::Table(t) => {            emit_table_typst(node, &t.alignments, out);        }        _ => {            for child in node.children() {                render_block_typst(child, out);            }        }    }}fn emit_list_item_typst<'a>(item: &'a AstNode<'a>, out: &mut String, ordered: bool) {    out.push_str(if ordered { "+ " } else { "- " });    for child in item.children() {        match &child.data.borrow().value {            NodeValue::Paragraph => {                for c in child.children() {                    render_inline_typst(c, out);                }            }            NodeValue::List(l) => {                let nested_ordered =                    matches!(l.list_type, comrak::nodes::ListType::Ordered);                out.push('\n');                for grandchild in child.children() {                    out.push_str("  ");                    emit_list_item_typst(grandchild, out, nested_ordered);                }            }            _ => render_block_typst(child, out),        }    }    out.push('\n');}fn emit_block_image<'a>(    l: &comrak::nodes::NodeLink,    node: &'a AstNode<'a>,    out: &mut String,) {    let url = strip_images_prefix(&l.url);    let mut alt = String::new();    for child in node.children() {        collect_text(child, &mut alt);    }    out.push_str("#align(center)[#image(\"/content/images/");    out.push_str(&typst_str_lit(&url));    out.push_str("\", width: 100%");    if !alt.is_empty() {        out.push_str(", alt: \"");        out.push_str(&typst_str_lit(&alt));        out.push('"');    }    out.push_str(")]\n\n");}fn emit_table_typst<'a>(    node: &'a AstNode<'a>,    alignments: &[TableAlignment],    out: &mut String,) {    let mut rows = node.children().peekable();    let columns = match rows.peek() {        Some(first) => first.children().count(),        None => return,    };    if columns == 0 {        return;    }    out.push_str("#table(columns: ");    out.push_str(&columns.to_string());    out.push_str(", align: (");    for (i, _) in (0..columns).enumerate() {        if i > 0 {            out.push_str(", ");        }        out.push_str(match alignments.get(i).copied() {            Some(TableAlignment::Right) => "right",            Some(TableAlignment::Center) => "center",            _ => "left",        });    }    out.push_str("),\n");    for row in rows {        for cell in row.children() {            out.push_str("  [");            for child in cell.children() {                render_inline_typst(child, out);            }            out.push_str("],\n");        }    }    out.push_str(")\n\n");}fn render_inline_typst<'a>(node: &'a AstNode<'a>, out: &mut String) {    let value = &node.data.borrow().value;    match value {        NodeValue::Text(t) => out.push_str(&typst_escape(t)),        NodeValue::SoftBreak => out.push(' '),        NodeValue::LineBreak => out.push_str(" \\\n"),        NodeValue::Code(c) => {            out.push_str("#raw(\"");            out.push_str(&typst_str_lit(&c.literal));            out.push_str("\")");        }        NodeValue::HtmlInline(_) => {            // Drop raw HTML inline in PDF output.        }        NodeValue::Emph => {            out.push_str("#emph[");            for child in node.children() {                render_inline_typst(child, out);            }            out.push(']');        }        NodeValue::Strong => {            out.push_str("#strong[");            for child in node.children() {                render_inline_typst(child, out);            }            out.push(']');        }        NodeValue::Strikethrough => {            out.push_str("#strike[");            for child in node.children() {                render_inline_typst(child, out);            }            out.push(']');        }        NodeValue::Link(l) => {            out.push_str("#link(\"");            out.push_str(&typst_str_lit(&l.url));            out.push_str("\")[");            for child in node.children() {                render_inline_typst(child, out);            }            out.push(']');        }        NodeValue::Image(l) => {            let url = strip_images_prefix(&l.url);            out.push_str("#image(\"/content/images/");            out.push_str(&typst_str_lit(&url));            out.push_str("\")");        }        _ => {            for child in node.children() {                render_inline_typst(child, out);            }        }    }}fn strip_images_prefix(url: &str) -> String {    url.strip_prefix("images/").unwrap_or(url).to_string()}/// Backslash-escape characters that have meaning in Typst markup mode.fn typst_escape(s: &str) -> String {    let mut out = String::with_capacity(s.len());    for c in s.chars() {        match c {            '\\' | '[' | ']' | '*' | '_' | '`' | '#' | '$' | '<' | '@' | '~' => {                out.push('\\');                out.push(c);            }            _ => out.push(c),        }    }    out}/// Escape a string for use inside a `"..."` Typst string literal.pub fn typst_str_lit(s: &str) -> String {    let mut out = String::with_capacity(s.len());    for c in s.chars() {        match c {            '\\' => out.push_str("\\\\"),            '"' => out.push_str("\\\""),            '\n' => out.push_str("\\n"),            '\r' => out.push_str("\\r"),            '\t' => out.push_str("\\t"),            _ => out.push(c),        }    }    out}
modified src/pdf.rs
@@ -1,106 +1,148 @@use std::path::PathBuf;use std::process::Command;use tempfile::{Builder, NamedTempFile};use std::path::{Path, PathBuf};use std::sync::Arc;/// Render an HTML string to a PDF using headless Chromium.////// Spawns chrome-headless-shell with --print-to-pdf. The HTML is written to a/// tempfile and loaded as a file:// URL so relative URLs (e.g. /content/images/...)/// can be rewritten to absolute http:// URLs pointing at the live server.pub fn html_to_pdf(html: &str, server_base: &str) -> anyhow::Result<Vec<u8>> {    // Rewrite relative absolute paths (/content/, /static/) to point at the    // running server so chromium can fetch them.    let rewritten = html        .replace("src=\"/content/", &format!("src=\"{server_base}/content/"))        .replace("href=\"/content/", &format!("href=\"{server_base}/content/"))        .replace("src=\"/static/", &format!("src=\"{server_base}/static/"))        .replace("href=\"/static/", &format!("href=\"{server_base}/static/"));use chrono::{Datelike, Local};use typst::{    Library, LibraryExt, World,    diag::{FileError, FileResult, SourceDiagnostic},    foundations::{Bytes, Datetime},    layout::PagedDocument,    syntax::{FileId, Source, VirtualPath},    text::{Font, FontBook},    utils::LazyHash,};use typst_kit::fonts::{FontSearcher, FontSlot, Fonts};    // Suffix matters: chromium decides HTML vs plain-text rendering by extension.    // Without .html the page is shown as raw source text instead of being rendered.    let mut html_file = Builder::new().suffix(".html").tempfile()?;    {        use std::io::Write;        html_file.write_all(rewritten.as_bytes())?;        html_file.flush()?;    }    let html_path = html_file.path().to_path_buf();    let pdf_file = NamedTempFile::new()?;    let pdf_path = pdf_file.path().to_path_buf();    let url = format!("file://{}", html_path.display());    let print_arg = format!("--print-to-pdf={}", pdf_path.display());    run_chromium(&url, &print_arg)?;    let bytes = std::fs::read(&pdf_path)?;    Ok(bytes)/// Pre-built renderer state. Fonts and the standard library are loaded once at/// startup and shared across renders.pub struct PdfRenderer {    library: Arc<LazyHash<Library>>,    book: Arc<LazyHash<FontBook>>,    fonts: Arc<Vec<FontSlot>>,    root: PathBuf,}fn run_chromium(url: &str, print_arg: &str) -> anyhow::Result<()> {    let bin = find_chromium().ok_or_else(|| {        anyhow::anyhow!("could not locate chromium; set CHROMIUM_BIN or install chromium on PATH")    })?;    let output = Command::new(&bin)        .arg("--headless=new")        .arg("--no-sandbox")        .arg("--disable-gpu")        .arg("--no-pdf-header-footer")        .arg("--hide-scrollbars")        .arg(print_arg)        .arg(url)        .output()?;    if !output.status.success() {        anyhow::bail!(            "chromium ({}) exited {}: {}",            bin.display(),            output.status,            String::from_utf8_lossy(&output.stderr)        );impl PdfRenderer {    /// Discover system + embedded fonts and build the renderer. `root` is the    /// project root that absolute paths in the Typst source resolve against    /// (e.g. `image("/content/images/foo.webp")` → `<root>/content/images/foo.webp`).    pub fn new(root: PathBuf) -> Self {        let Fonts { book, fonts } = FontSearcher::new()            .include_system_fonts(true)            .search();        Self {            library: Arc::new(LazyHash::new(Library::default())),            book: Arc::new(LazyHash::new(book)),            fonts: Arc::new(fonts),            root,        }    }    /// Compile `source` (Typst markup) into a PDF.    pub fn render(&self, source: String) -> anyhow::Result<Vec<u8>> {        let main_id = FileId::new(None, VirtualPath::new("/main.typ"));        let main = Source::new(main_id, source);        let world = PdfWorld {            library: self.library.clone(),            book: self.book.clone(),            fonts: self.fonts.clone(),            root: self.root.clone(),            main,        };        let warned = typst::compile::<PagedDocument>(&world);        let document = warned            .output            .map_err(|errs| format_diagnostics("compile", &errs))?;        let bytes = typst_pdf::pdf(&document, &typst_pdf::PdfOptions::default())            .map_err(|errs| format_diagnostics("pdf export", &errs))?;        Ok(bytes)    }    Ok(())}/// Locate a chromium binary across environments:/// 1. `CHROMIUM_BIN` env var (explicit override)/// 2. PATH search for common chromium binary names (production install)/// 3. Glob under `/opt/playwright-browsers/` (dev container with Playwright)fn find_chromium() -> Option<PathBuf> {    if let Ok(p) = std::env::var("CHROMIUM_BIN") {        let path = PathBuf::from(p);        if path.is_file() {            return Some(path);fn format_diagnostics(stage: &str, errs: &[SourceDiagnostic]) -> anyhow::Error {    let mut s = String::new();    for e in errs {        if !s.is_empty() {            s.push('\n');        }        s.push_str(&e.message);        for h in &e.hints {            s.push_str("\n  hint: ");            s.push_str(h);        }    }    anyhow::anyhow!("typst {stage}: {s}")}    let names = [        "chromium",        "chromium-browser",        "chrome-headless-shell",        "google-chrome",        "chrome",    ];    if let Some(path_var) = std::env::var_os("PATH") {        for dir in std::env::split_paths(&path_var) {            for name in &names {                let candidate = dir.join(name);                if candidate.is_file() {                    return Some(candidate);                }            }struct PdfWorld {    library: Arc<LazyHash<Library>>,    book: Arc<LazyHash<FontBook>>,    fonts: Arc<Vec<FontSlot>>,    root: PathBuf,    main: Source,}impl World for PdfWorld {    fn library(&self) -> &LazyHash<Library> {        &self.library    }    fn book(&self) -> &LazyHash<FontBook> {        &self.book    }    fn main(&self) -> FileId {        self.main.id()    }    fn source(&self, id: FileId) -> FileResult<Source> {        if id == self.main.id() {            return Ok(self.main.clone());        }        let path = self.resolve(id)?;        let text =            std::fs::read_to_string(&path).map_err(|err| FileError::from_io(err, &path))?;        Ok(Source::new(id, text))    }    fn file(&self, id: FileId) -> FileResult<Bytes> {        let path = self.resolve(id)?;        let bytes = std::fs::read(&path).map_err(|err| FileError::from_io(err, &path))?;        Ok(Bytes::new(bytes))    }    fn font(&self, index: usize) -> Option<Font> {        self.fonts.get(index)?.get()    }    fn today(&self, _offset: Option<i64>) -> Option<Datetime> {        let now = Local::now();        Datetime::from_ymd(now.year(), now.month() as u8, now.day() as u8)    }}    if let Ok(entries) = std::fs::read_dir("/opt/playwright-browsers") {        for entry in entries.flatten() {            let candidate = entry                .path()                .join("chrome-headless-shell-linux64/chrome-headless-shell");            if candidate.is_file() {                return Some(candidate);            }impl PdfWorld {    fn resolve(&self, id: FileId) -> FileResult<PathBuf> {        if id.package().is_some() {            return Err(FileError::Other(Some(                "remote packages not supported".into(),            )));        }        id.vpath()            .resolve(&self.root)            .ok_or(FileError::AccessDenied)            .and_then(|p| {                if path_within(&p, &self.root) {                    Ok(p)                } else {                    Err(FileError::AccessDenied)                }            })    }}    Nonefn path_within(path: &Path, root: &Path) -> bool {    let canon = match path.canonicalize() {        Ok(p) => p,        Err(_) => return false,    };    let canon_root = match root.canonicalize() {        Ok(p) => p,        Err(_) => return false,    };    canon.starts_with(canon_root)}
modified src/posts.rs
@@ -16,7 +16,7 @@ pub struct Post {    pub description: String,    pub cover_image: String,    pub body_html: String,    pub body_html_pdf: String,    pub body_typst: String,    pub read_time: usize,}
@@ -75,7 +75,7 @@ pub fn load_posts(content_dir: &PathBuf) -> Vec<Post> {        let date = meta.get("date").cloned().unwrap_or_default();        let publish_date = meta.get("publish_date").cloned().unwrap_or_else(|| date.clone());        let body_html = markdown::render_blog(body);        let body_html_pdf = markdown::render_pdf(body);        let body_typst = markdown::render_typst(body);        let word_count = body.split_whitespace().count();        let read_time = ((word_count as f64) / 200.0).ceil() as usize;        let read_time = read_time.max(1);
@@ -95,7 +95,7 @@ pub fn load_posts(content_dir: &PathBuf) -> Vec<Post> {            description: meta.get("description").cloned().unwrap_or_default(),            cover_image: meta.get("cover_image").cloned().unwrap_or_default(),            body_html,            body_html_pdf,            body_typst,            read_time,        });    }
added templates/blog_post.typ
@@ -0,0 +1,112 @@#let render(  title: "",  date: "",  read_time: 0,  tags: (),  description: "",  cover_image: none,  body: [],) = {  set page(    paper: "a4",    margin: (top: 2cm, bottom: 2.5cm, left: 2cm, right: 2cm),    header: context {      let page_num = counter(page).get().first()      if page_num > 1 {        set text(size: 7pt, fill: rgb("#999999"))        grid(          columns: (1fr, 1fr),          align(left)[#title],          align(right)[blog.bythewood.me],        )      }    },    footer: context {      set align(center)      set text(size: 7pt, fill: rgb("#999999"))      [#counter(page).display() / #counter(page).final().first()]    },  )  set text(    font: ("Inter", "DejaVu Sans", "Liberation Sans", "Arial"),    size: 10pt,    fill: rgb("#1a1a1a"),  )  set par(leading: 0.65em, justify: false)  show raw: set text(    font: ("JetBrains Mono", "DejaVu Sans Mono", "Liberation Mono"),    size: 8.5pt,  )  show raw.where(block: true): it => block(    fill: rgb("#f5f5f5"),    inset: 8pt,    radius: 4pt,    width: 100%,    breakable: true,    it,  )  show raw.where(block: false): it => box(    fill: rgb("#f0f0f0"),    inset: (x: 3pt, y: 1pt),    radius: 3pt,    outset: (y: 2pt),    it,  )  show link: set text(fill: rgb("#0e3ff4"))  show link: underline  show heading: set block(above: 1.4em, below: 0.6em)  show heading.where(level: 1): set text(size: 1.8em, weight: "bold")  show heading.where(level: 2): set text(size: 1.4em, weight: "bold")  show heading.where(level: 3): set text(size: 1.15em, weight: "bold")  show heading.where(level: 4): set text(size: 1.0em, weight: "bold")  show quote.where(block: true): it => block(    inset: (left: 1em),    stroke: (left: 3pt + rgb("#dddddd")),  )[#set text(fill: rgb("#555555")); #it.body]  // Title  text(size: 1.8em, weight: "bold")[#title]  // Meta line + tags + bottom border  block(    above: 0.4em,    below: 1.5em,    stroke: (bottom: 2pt + rgb("#eeeeee")),    inset: (bottom: 0.8em),    width: 100%,  )[    #set text(size: 0.9em, fill: rgb("#666666"))    Isaac Bythewood · #date · #read_time min read    #if tags.len() > 0 {      v(0.4em)      for tag in tags {        box(          fill: rgb("#eeeeee"),          inset: (x: 0.5em, y: 0.1em),          radius: 3pt,          outset: (y: 1pt),        )[#text(size: 0.85em)[#tag]]        h(0.3em)      }    }  ]  if cover_image != none {    align(center)[#image(cover_image, width: 100%)]    v(1.5em)  }  if description != "" {    block(below: 1.5em)[      #set text(size: 1.1em, fill: rgb("#555555"))      #description    ]  }  body}
deleted templates/blog_post_pdf.html
@@ -1,158 +0,0 @@<!doctype html><html lang="en"><head>  <meta charset="utf-8">  <title>{{ post.title }}</title>  <style>    @page {      size: A4;      margin: 2cm 2cm 2.5cm 2cm;      @top-left {        content: "{{ post.title }}";        font-size: 7pt;        color: #999;        font-family: sans-serif;      }      @top-right {        content: "blog.bythewood.me";        font-size: 7pt;        color: #999;        font-family: sans-serif;      }      @bottom-center {        content: counter(page) " / " counter(pages);        font-size: 7pt;        color: #999;        font-family: sans-serif;      }    }    @page :first {      @top-left { content: none; }      @top-right { content: none; }    }    body {      font-family: sans-serif;      font-size: 10pt;      line-height: 1.5;      color: #1a1a1a;    }    h1 {      font-size: 1.8em;      margin-bottom: 0.2em;    }    .meta {      color: #666;      font-size: 0.9em;      margin-bottom: 2em;      padding-bottom: 1em;      border-bottom: 2px solid #eee;    }    .meta .tags {      margin-top: 0.3em;    }    .meta .tag {      background: #eee;      padding: 0.1em 0.5em;      border-radius: 3px;      font-size: 0.85em;    }    .cover-image {      margin-bottom: 1.5em;      text-align: center;    }    .cover-image img {      max-width: 100%;      border-radius: 4px;    }    .description {      font-size: 1.1em;      color: #555;      margin-bottom: 1.5em;    }    .block-rich-text {      margin-bottom: 0.5em;    }    .block-code {      margin: 1em 0;      page-break-inside: avoid;    }    .block-code pre {      background: #f5f5f5;      border-radius: 4px;      padding: 0.8em;      margin: 0;      white-space: pre-wrap;      word-wrap: break-word;      overflow-wrap: break-word;    }    .block-code code {      font-family: "JetBrains Mono", monospace;      font-size: 8.5pt;      line-height: 1.4;      background: none;      padding: 0;      border-radius: 0;    }    .block-image {      margin: 1.5em 0;      text-align: center;    }    .block-image img {      max-width: 100%;      max-height: 500px;      object-fit: contain;      border-radius: 4px;    }    code {      font-family: "JetBrains Mono", monospace;      background: #f0f0f0;      padding: 0.1em 0.3em;      border-radius: 3px;      font-size: 0.9em;    }    a {      color: #0e3ff4;    }    blockquote {      border-left: 3px solid #ddd;      margin-left: 0;      padding-left: 1em;      color: #555;    }    hr {      border: none;      border-top: 2px solid #eee;      margin: 2em auto;      max-width: 300px;    }  </style></head><body>  <h1>{{ post.title }}</h1>  <div class="meta">    <div>Isaac Bythewood &middot; {{ post.date }} &middot; {{ post.read_time }} min read</div>    {% if post.tags %}    <div class="tags">      {% for tag in post.tags %}      <span class="tag">{{ tag }}</span>      {% endfor %}    </div>    {% endif %}  </div>  {% if post.cover_image %}  <div class="cover-image">    <img src="/content/images/{{ post.cover_image }}" alt="{{ post.title }}">  </div>  {% endif %}  {% if post.description %}  <div class="description">{{ post.description }}</div>  {% endif %}  <article>    {{ post.body_html_pdf|safe }}  </article></body></html>