typst-pack 0.4.0

Portable single-file packs of Typst projects: sources, resources, packages, and fonts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# typst-pack

[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/sagikazarmark/typst-pack/dagger.yaml?style=flat-square)](https://github.com/sagikazarmark/typst-pack/actions/workflows/dagger.yaml)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/sagikazarmark/typst-pack/badge?style=flat-square)](https://securityscorecards.dev/viewer/?uri=github.com/sagikazarmark/typst-pack)
[![crates.io](https://img.shields.io/crates/v/typst-pack?style=flat-square)](https://crates.io/crates/typst-pack)
[![docs.rs](https://img.shields.io/docsrs/typst-pack?style=flat-square)](https://docs.rs/typst-pack)

**Portable single-file packs of Typst projects: sources, resources, packages, and fonts.**

A *pack* (`.typk`) captures the compilation contract of one Typst project:

- the packed project files: the entrypoint, other Typst sources, images, and
  data files,
- optionally the files of the [Typst Universe]https://typst.app/universe
  packages the project imports, so compiling needs no network access,
- optionally the fonts the document uses, so compiling produces identical
  output on machines without those fonts.

Use it as a CLI to distribute finished Typst projects, or as a library to
produce and consume packs programmatically (e.g. offering a "download
project" pack in a web-based Typst editor).

Note: this is unrelated to Typst's own *bundle export* (the `typst-bundle`
crate), which is a multi-file **output** target. A pack is an **input**
archive: a portable form of a project's sources and resources.

## Features

- **Portable project archives**: bundle Typst sources, resources, packages, and
  fonts into one `.typk` file.
- **Structural project closure**: include every eligible regular file beneath
  the selected project root, independently of compiler control flow.
- **Reproducible compilation**: compile without network or system font access,
  with support for fixed timestamps and vendored packages.
- **Pack Overrides**: replace any contained project file for one compilation
  without mutating the Pack.
- **Library and CLI interfaces**: create, inspect, compile, and extract packs in
  memory or on the file system.

## CLI

Install the command-line tool:

```console
cargo install typst-pack-cli
```

```console
# Pack a named source file, vendoring all observed packages:
typst-pack create path/to/project/main.typ

# Pack a specific entrypoint, embedding the fonts the document uses:
typst-pack create letter.typ --embed-fonts

# See what a pack contains:
typst-pack inspect project.typk

# Compile a pack without network access:
typst-pack compile project.typk output.pdf

# Replace a contained placeholder for one compilation:
typst-pack compile invoice.typk customer.pdf --override assets/logo.png customer-logo.png

# PNG or SVG output, page selection, reproducible builds:
typst-pack compile project.typk "page-{0p}.png" --ppi 300 --pages 1-3
typst-pack compile project.typk reproducible.pdf --creation-timestamp 1700000000

# Guarantee no network access (fails instead of downloading packages):
typst-pack compile project.typk --offline

# Experimental HTML export (the output format enables its required feature):
typst-pack compile project.typk out.html

# An HTML representative creation compile still selects the feature explicitly:
typst-pack create project/main.typ --target html --features html

# Unpack a pack back into an editable project directory:
typst-pack extract project.typk -o project/
```

For Page Formats, `{p}` expands to the one-based Source Page Number, `{0p}` and
`{n}` are zero-padded aliases, and `{t}` is the total source-document page
count before page selection. Multi-page output requires an explicit `{p}`,
`{0p}`, or `{n}` template. All target paths are checked for duplicates before
writing. Document Format output paths are literal.

### Project files

`create` stabilizes every eligible regular file beneath the physical project
root before compiling. Project membership is independent of the representative
compile's target, inputs, date, features, and control flow. The root
`.typkignore` applies Gitignore-style ordered rules; it is always packed, nested
`.typkignore` files are ordinary project files, and every `.typk` path is always
excluded. Symlinks and other unignored non-regular entries are rejected.

Creation runs one representative compile from those stabilized bytes to select
exact package and font dependencies. `--target paged|html` is optional and
defaults to `paged`; it does not restrict later output formats. This concrete
evaluation is a temporary dependency-selection mechanism because Typst does not
report every package or font a different request might reach.

Every project path in a Pack has contained bytes. For per-document variation,
pack a valid placeholder and use compile-time `--override PACK_PATH FILE`.
Overrides may replace source, assets, data, or the entrypoint, but cannot add or
delete paths or authorize undeclared packages and fonts.

### Packages

All observed package dependencies are vendored into the pack by default.
With `--no-vendor-packages`, each dependency is instead recorded as an exact
package specification and Complete Package Tree identity. Compilation acquires
the whole tree from the configured package directory, cache, or Typst Universe,
verifies it before invoking Typst, and exposes only the verified paths and bytes.
Undeclared package locations and ambient caches cannot satisfy imports.

`--offline` (on both `create` and `compile`) disables the download step
entirely: dependencies must come from the pack or the local package
directories, and anything else fails as not found. Use
`typst-pack compile --offline` to verify that a pack is truly
self-contained.

### Fonts

Every selected face is recorded in the ordered Pack Font Catalog with its exact
container identity. Fonts are *not* embedded by default: compilation must find
the declared exact containers among the configured system, Typst-embedded, or
`--font-path` sources. Other available fonts are not exposed to Typst.

With `--embed-fonts`, selected containers are stored in the pack, except those
identical to Typst's embedded fonts. Pass `--include-typst-embedded-fonts` to
store those too. Mind font licenses when redistributing embedded containers;
licensing and acquisition metadata do not change font selection.

### Output formats

PDF and HTML are Document Formats and produce one Compilation Output Artifact
without a Source Page Number. PNG and SVG are Page Formats and produce one
artifact per selected source page. Page artifacts retain their original Source
Page Number and are emitted once each in source-document order.

HTML export is experimental in Typst itself, and Typst emits a warning that its
behavior may change. Pack compilation derives the required engine feature from
`CompilationOutputSpecification::Html`; HTML creation still requires
`--features html` (or `TYPST_FEATURES=html`).

The Dagger `compile` function returns a directory for every format. Document
Formats use `output.pdf` or `output.html`; Page Formats use deterministic names
such as `page-2.png`, derived from Source Page Numbers. Its typed mapping,
staging, failure boundary, and intentional transport omissions are documented
in the [Dagger adapter contract](docs/dagger-adapter.md).

Maintainers changing the embedded compiler must follow the
[embedded Typst upgrade procedure](docs/embedded-typst-upgrade.md). CI enforces
the approved crate graph, classified differential matrix, official CLI oracle,
and the packaged release binary.

## Library

Add the crate with filesystem-backed packing support and Typst's embedded
fonts:

```toml
[dependencies]
typst-pack = { version = "0.4", features = ["embedded-fonts", "fs"] }
```

The core in-memory packing and compilation APIs require no crate features.

```rust,ignore
use typst_pack::{
    compile, CompilationOutputSpecification, OutputFormat, Pack,
    PackCompilationRequest, Packer, PdfOutputSpecification,
};

// Pack a project directory (requires the `fs` feature).
let outcome = Packer::new("path/to/project", "main.typ")
    .embed_fonts(true)
    .pack()?;
let bytes = outcome.pack.to_bytes()?;

// ... ship the bytes somewhere, then compile without a file system:
let pack = Pack::from_bytes(bytes)?;
let request = PackCompilationRequest::new(
    pack,
    CompilationOutputSpecification::Pdf(PdfOutputSpecification::default()),
);
let report = compile(request)?;
let output = report.result().expect("semantic compilation result");
assert_eq!(output.engine_identity().implementation(), "typst");
assert_eq!(output.exporter_identity().implementation(), "typst-pdf");
let artifact = output.artifacts().first().expect("PDF artifact");
assert_eq!(artifact.format(), OutputFormat::Pdf);
assert_eq!(artifact.source_page_number(), None);
let pdf = artifact.bytes();
```

`PackOutcome::warnings` retains warnings from the representative creation
compile. Inspect `PackOutcome::pack` for authoritative project files, package
requirements and their embedding disposition, and the Pack Font Catalog; that
static inventory is not duplicated in the creation outcome.

`compile` always returns a `CompilationReport` after accepting the semantic
request. Its outcome contains either the immutable semantic result or an
operational dependency failure, and its fulfillment report retains
caller-supplied package and font provenance, cache disposition, and licensing
metadata without including those operational values in Compilation Identity or
Compilation Result Identity. Request rejection is the outer error and retains
the complete request inventory. Every semantic result also exposes its document
summary and canonical Compilation Access Trace.

For PNG and SVG, `source_page_number()` identifies each artifact independently
of its collection position. `bytes()` borrows the artifact bytes and
`into_bytes()` extracts them without cloning.

Packs can also be assembled fully in memory, with no file system involved, which
is what a web editor wants:

```rust,ignore
use typst_pack::Pack;

let pack = Pack::builder("main.typ")
    .file("main.typ", source_text.as_bytes().to_vec())?
    .file("figure.png", image_bytes)?
    .build()?;
let bytes = pack.to_bytes()?;
```

Compilation-time Pack Overrides replace contained project-file bytes in memory:

```rust,ignore
let pack = Pack::builder("main.typ")
    .file("main.typ", source_text.as_bytes().to_vec())?
    .file("assets/logo.png", placeholder_png)?
    .build()?;
let overrides = PackOverrideSet::new(&pack)
    .replace("assets/logo.png", customer_png)?;
let request = PackCompilationRequest::new(
    pack,
    CompilationOutputSpecification::Pdf(PdfOutputSpecification::default()),
).overrides(overrides);
let report = compile(request)?;
let output = report.result().expect("semantic compilation result");
```

### Compilation authority

The public compilation boundary accepts only a validated `Pack` bound into a
`PackCompilationRequest`. The Pack-backed Typst `World`, compilation kernel,
and embedded compiler and exporter adapter are private. In particular, callers
cannot substitute a `typst::World`, language library, compiler, or exporter:

```compile_fail
use typst_pack::PackWorld;
```

```compile_fail
use typst_pack::compile_pack;
```

```compile_fail
use typst_pack::compile;

fn arbitrary_world(world: &dyn typst::World) {
    let _ = compile(world);
}
```

Typst 0.15.0 owns language evaluation, layout, official diagnostics, document
structures, and PDF, PNG, SVG, and HTML export behavior. typst-pack owns Pack
creation and validity, the fixed set of contained project paths, exact package
and font verification, Pack Overrides, request identities and reports, and later
CLI or Dagger publication. Artifact bytes and official diagnostics are not
reinterpreted by destination, transport, cache, or presentation code.

Intentional differences from `typst compile` are Pack confinement, Pack input
instead of a source root, a fixed contained project namespace, exact dependency
fulfillment, Pack Overrides, unsupported Bundle output, and publication rules
for immutable artifacts. The complete version-bound inventory is in
[`docs/cli-parity.md`](docs/cli-parity.md).

### Migrating to 0.4

Version 0.4 makes clean naming and invariant-boundary breaks without retaining
compatibility aliases:

- Remove Resource Slot and Resource Provider APIs; pack valid baseline
  placeholders and replace contained files with Pack Overrides.
- Rename Dagger arguments: `source` -> `project`, `entrypoint` -> `input`,
  `inputs` -> `sysInputs`, `noPackages` -> `noVendorPackages`,
  `sourceDateEpoch` -> `creationTimestamp`, and `CreationTarget` ->
  `TypstTarget`. Removed resource and inclusion arguments have no replacements.
- Change creation from a directory plus `--entrypoint`/`--output` to
  `create <INPUT> [OUTPUT]`.
- Replace `compile_pack(request)` with `compile(request)`. The provisional
  arbitrary-`World` `compile` overload and public `PackWorld` builder are
  removed; configure semantic values on `PackCompilationRequest`.
- `compile` returns `CompilationReport`; inspect `report.outcome()` or
  `report.result()`. `compile_report`, `PackCompileError`, `CompilationAttempt`,
  and the empty `CompilationExecutionControls` are removed. Request rejection
  now owns its inventory and ordered `CompilationRequestIssue` values.
- Replace `CreationTarget` and `CompilationTarget` with `TypstTarget`.
- Configure document time with one `DocumentTime` value. `Absent`, `Fixed`, and
  `UnixTimestamp` replace the former date/timestamp fields and setters.
- Read representative-compile warnings from `PackOutcome::warnings`; the
  one-field `PackReport` is removed.
- Pack Manifest fields and `PackFont` fields are read-only. Use accessors such
  as `manifest.project()`, `project.entrypoint()`, `font.manifest()`, and
  `font.data()`. Package declarations are reached only through
  `manifest.packages().vendored()` and `.unvendored()`.
- Shared Pack consistency failures are available as `PackInvariantError`,
  wrapped by `PackBuildError::Invariant` or `PackReadError::Invariant`.
- Replace `OutputFormat` plus `CompileOptions` request construction with the
  corresponding `CompilationOutputSpecification` variant and format-specific
  structure. PDF creation time is configured through
  `PdfOutputSpecification::creation_timestamp`; use `CreationTimestamp::Omit`
  to suppress PDF creation datetime metadata.
- `ExtractError` adds `PlannedPathConflict` and `DestinationConflict`; exhaustive
  matches must handle both variants.

The unstable Pack format remains version 1, but discovery and Resource Slot
fields are removed in place. Old fields and aliases are not accepted.

### Feature flags

- `fs`: `Packer`, `extract`, package download and caching,
  system font scanning. Requires a file system, so disable this for wasm
  targets.
- `embedded-fonts`: make Typst's bundled fonts available as intentional
  creation and external-fulfillment sources.
- `diagnostics`: retain source context for first-party diagnostic presentation
  adapters.
- `parallel`: export independent page artifacts in parallel.

All library crate features are opt-in. Fixed timestamp conversion for `DocumentTime`
is part of the featureless core and remains available on wasm targets.

## Pack format

A pack is a Zip archive (Deflate), conventionally named `*.typk`, with this
layout:

```text
typst-pack.toml                     manifest (always first)
project/<path>                      project files, root-relative
packages/<ns>/<name>/<version>/<path>   vendored package files
fonts/<file>                        embedded font files
```

The manifest looks like this:

```toml
format-version = 1

[project]
entrypoint = "main.typ"

[[packages.vendored]]
spec = "@preview/cetz:0.3.4"
tree-digest = "0123456789abcdef0123456789abcdef"
tree-identity-kind = "complete-package-tree"
tree-identity-schema = "typst-pack-complete-package-tree-v1"
tree-identity-algorithm = "typst-hash128-0.15"
file-count = 12
byte-length = 34567

[[packages.unvendored]]
spec = "@preview/tablex:0.0.9"
tree-digest = "fedcba9876543210fedcba9876543210"
tree-identity-kind = "complete-package-tree"
tree-identity-schema = "typst-pack-complete-package-tree-v1"
tree-identity-algorithm = "typst-hash128-0.15"
file-count = 8
byte-length = 23456

[[fonts]]
path = "fonts/ibm-plex-sans.ttf"
families = ["IBM Plex Sans"]

[metadata]
name = "Quarterly report"
authors = ["Jane Doe"]
```

Readers ignore unknown top-level archive entries and reject manifests whose
`format-version` is not the exact supported version. Paths inside the archive
are validated, root-relative virtual paths. Extraction rejects existing
symlinked entries within the selected destination before writing.

The format version remains 1 and is explicitly unstable: readers reject old
discovery, Resource Slot, `external-resources`, and `packages.external` fields
rather than retaining aliases.

## Development

Minimum verification:

- `cargo fmt --all -- --check`
- `cargo clippy --workspace --all-targets --all-features -- -D warnings`
- `cargo test --workspace --all-features`

Run CI's containerized checks with [Dagger](https://dagger.io):

- `dagger check`

The containerized suite includes the
[embedded Typst CLI differential gate](docs/cli-parity.md), pinned to the exact
official release used by the library.

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT]LICENSE-MIT or <http://opensource.org/licenses/MIT>)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.