Skip to content

CND as a typst compile output format - #4

Merged
Maxanox merged 11 commits into
mainfrom
feat/cli-cnd-format
Sep 25, 2026
Merged

Maxanox merged 11 commits into
mainfrom
feat/cli-cnd-format

Conversation

@Maxanox

@Maxanox Maxanox commented Sep 25, 2026

Copy link
Copy Markdown

Summary

  • typst compile --format cnd (or a .cnd output path) runs the CND exporter through the standard CLI, so --root, --font-path, --ignore-system-fonts and --deps apply to CND.
  • --inputs-file: a JSON object of string values merged into sys.inputs (--input wins on a key collision), for every format.
  • --fallback-dir: a missing project file is looked up by file name in that directory; project files win, packages never fall back, escaping paths still fail, symlinks cannot lead outside the directory, only regular files are served.
  • --deps now also lists the font files the compilation loaded, by canonical path (all deps formats; the watcher does not watch fonts).
  • CND state_metadata is serialized in a stable key order (it followed a per-process random order before); the JSON shape is unchanged.
  • The standalone typst-cnd binary is unchanged apart from its help text.

Test plan

  • cargo test -p typst-cli (new tests/cnd.rs: format selection, stdout, CND semantics feature, --root, parity with the standalone exporter on every example, --inputs-file, --fallback-dir incl. escape and symlink cases, font deps; hermetic against TYPST_* env vars)
  • cargo test -p typst-cnd
  • cargo test --workspace --no-fail-fast locally (Linux)
  • CI (Windows and 32-bit legs not run locally)

🤖 Generated with Claude Code

Maxanox and others added 11 commits September 25, 2026 16:06
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The example-suite parity test (added next) caught complex_multipage.typ
producing textually different CND output across separate `typst compile`
runs of the same document. Root cause: `NodeBase::state_metadata` was a
`std::collections::HashMap<String, serde_json::Value>`, whose iteration
order is randomized per process — confirmed by running the CLI three
times on the same input and diffing the key order of one node's
state_metadata each time.

Switch it (and every construction site: metadata.rs, emit/convert.rs,
emit/figure.rs, emit/table.rs) to `serde_json::Map<String,
serde_json::Value>`. Without serde_json's `preserve_order` feature
(deliberately not enabled anywhere in this workspace, since Cargo
feature unification would change map ordering for every crate in the
build, including upstream ones), `Map` is backed by a `BTreeMap`, so
keys always serialize in the same alphabetical order regardless of
process or machine. Nested dicts already went through `serde_json::Map`
in `value_to_json`; this makes the top level consistent with them.

Adds a regression unit test in metadata.rs asserting alphabetical key
order from a dict inserted in non-alphabetical order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…suite

Adds CndWorld::new_with_options(input, inputs, system_fonts), with
new_with_inputs now a thin wrapper over it (system_fonts: true). This
lets a test build the same font set --ignore-system-fonts gives the CLI
path, so both entry points are comparable with system fonts off — they
otherwise discover fonts in a different order, which is a font-setup
difference, not an exporter one.

cnd_format_matches_the_standalone_exporter_on_every_example compiles
every .typ file in typst-cnd/examples/ through both entry points
(typst compile --format cnd, and CndWorld + cnd_from_document/
cnd_to_json directly) and asserts the two CNDs are identical after
normalizing built_at and node UUIDs (order of first appearance). This
caught the state_metadata ordering bug fixed in the previous commit;
all 21 examples now agree byte-for-byte, verified stable across five
repeated runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Loads a JSON object of string values into sys.inputs, merged with
--input pairs (--input wins on a key collision since it is applied
last). Ports build_inputs/load_inputs_file/json_kind from
typst-cnd's standalone binary, with errors as String instead of
SourceResult so they compose with WorldCreationError.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A project file that does not exist is looked up by its file name alone
in the fallback directory. A project file that exists always wins, a
directory in the project stays an error, escaping paths fail before the
lookup, and package files never fall back. Only regular files whose real
path lies inside the fallback directory are served, so a symlink cannot
lead out of it. `--deps` names the fallback file that was read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Upstream's --deps only lists files read through the file store; fonts
served by FontStore were not reported. SystemWorld now tracks which
font indices World::font served since the last reset and exposes them
as font_dependencies() — sorted, deduplicated file paths, embedded
fonts (no file) omitted. Every --deps format (json, make, zero)
includes them via relative_dependencies(); watch.rs is untouched, so
typst watch still does not watch font files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Font files were reported by the path the font search found them at, so a
--font-path given through a symlink or as a relative path was reported
differently from a file dependency in the same directory, and two
spellings of one file were listed twice. They are now canonicalized like
file dependencies, then made relative to the root when inside it.

The integration tests of `typst compile --format cnd` also clear every
environment variable the CLI reads as a flag default, so a TYPST_ROOT or
TYPST_FONT_PATHS in the environment cannot change their outcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops stale planning notes, local paths and links to private notes; keeps the
hook points, the pipeline and the contributor rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Maxanox
Maxanox merged commit 326e997 into main Sep 25, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant