rustyfi

SATySFi, reimplemented in Rust. One
binary takes a .saty document and writes a PDF — same language, same packages,
same output, faster compilation, no OCaml toolchain to install.
It speaks both dialects: 0.0 (upstream v0.0.x) and 0.1 (dev-0-1-0),
and a document in one may use packages from the other.
Install
From latest release
$ # User-wide installation (~/.local/)
$ curl -fsSL https://raw.githubusercontent.com/yasuo-ozu/rustyfi/main/install.sh | bash
$ # System-wide installation (/usr/*)
$ curl -fsSL https://raw.githubusercontent.com/yasuo-ozu/rustyfi/main/install.sh | sudo bash
$ # Or manual prefix
$ curl -fsSL https://raw.githubusercontent.com/yasuo-ozu/rustyfi/main/install.sh | sudo bash -s -- --prefix /opt/rustyfi
$ curl -fsSL https://raw.githubusercontent.com/yasuo-ozu/rustyfi/main/install.sh | PREFIX=/opt/rustyfi sudo bash
$ rustyfi --version
From source
install.sh doubles as the installer for a checkout — run inside one, it uses
the binary you just built instead of downloading anything:
$ git clone https://github.com/yasuo-ozu/rustyfi && cd rustyfi
$ cargo build --release --bin rustyfi
$ sh download-fonts.sh # IPAex, Junicode, Latin Modern — pinned, ~175 MB
$ ./install.sh # --prefix DIR to put it elsewhere
Compile a document
@require: stdja-mini
document (|
title = {Milestone One};
author = {yasuo};
|) '<
+p { Hello, world! This is \emph{SATySFi-in-Rust}. }
>
$ rustyfi doc.saty
output written on doc.pdf (1 page(s), 2 line(s)).
A project with a Satyristes
Describe the project in a Satyristes (no opam file is required by rustyfi):
(library …) says what the project publishes: (packageDir "src") installs
every .satyh/.satyg under src/ as the package mylib. Install it into a
project-local root (.rustyfi/) and it becomes @require:-able:
$ rustyfi install . --dest .rustyfi
installed mylib 0.1.0 (1 path(s)):
dist/packages/mylib
$ rustyfi list --dest .rustyfi
mylib 0.1.0 (lang 0.0, 1 files)
.rustyfi/dist/packages/mylib
(libraryDoc …) is a build target rather than a package: rustyfi build runs
its (build …) commands in (workingDirectory …), then installs what
(sources …) names.
$ rustyfi build
rustyfi manual.saty
Package management
@require: resolves against lib roots (<root>/dist/packages/). Name one
with --lib-root or $RUSTYFI_LIB_ROOT and it is used alone; name none and
they are discovered, nearest first:
lib-rustyfi/above your document — a checked-out source tree.rustyfi/beside aSatyristes— a project-local install<exe>/../lib/rustyfi— the install this binary belongs to~/.local/lib/rustyfi, then/usr/local/lib/rustyfiand/usr/lib/rustyfi
All of them are searched in that order, so a package a project installed for itself layers over the system one rather than hiding it — and a clone needs no configuration at all.
To install someone else's package, the same binary is a Satyrographos analog:
$ rustyfi search font theano # keywords narrow: every one must match
$ rustyfi install ./satysfi-xpath # a local path, a .tar.gz, or a registry name
$ rustyfi install xpath easytable # install/uninstall take several at once
$ rustyfi install https://example.org/pkg.tar.gz#sha256=… # or a URL
$ rustyfi list
The default repository can live in your own config, so search and install
work outside any project:
# ~/.config/rustyfi/config.toml
[[]]
= "https://github.com/na4zagin3/satyrographos-repo"
[[]]
= "https://example.org/another-index"
Useful options
| flag | what it does |
|---|---|
-o <path> |
output path (default: the input with a .pdf extension) |
--format <fmt> |
pdf (HTML is on the html-support branch) |
--lib-root <dir> |
where @require: looks for packages |
--lang <v> |
0.0 (default) or 0.1; a use header auto-selects 0.1 |
--font <file> |
use a TrueType/OpenType file as the regular face |
--font-dir <dir> |
font root holding dist/hash/fonts.satysfi-hash |
--no-cache |
bypass the compile cache |
--no-aux |
do not read or write the .satysfi-aux cross-reference file |
--timing |
per-phase timing to stderr (load / typecheck / eval / render) |
Performance
Minimum CPU time over three interleaved runs against SATySFi
0.0.11, all five configurations measured in one pass (benchmark.py):
| doc | pages | rustyfi cold | rustyfi cached | SATySFi | --bytecomp |
warm aux |
|---|---|---|---|---|---|---|
| latexcmds | 12 | 0.22 s | 0.08 s | 1.07 s | 1.04 s | 0.67 s |
| enumitem | 27 | 0.96 s | 0.12 s | 2.54 s | 2.33 s | 1.46 s |
| easytable | 19 | 1.34 s | 0.14 s | 2.91 s | 2.85 s | 1.19 s |
| figbox | 21 | 1.28 s | 0.16 s | 2.55 s | 2.43 s | 1.12 s |
| slydifi | 30 | 1.21 s | 0.13 s | 1.74 s | 1.28 s | 1.16 s |
| xpath | 11 | 3.00 s | 0.10 s | 9.49 s | 2.65 s | 3.37 s |
Known gaps
- Fonts are named by file or hash entry, not by package: a document asking for
fonts-junicode:Junicode-Boldfalls back to a name heuristic. - Cross-version
decocrosses both ways now, including through optional arguments and nested module signatures — but not through an open optional row (nothing names the labels to forward) or a functor signature member. fontand 0.1'sparencross in neither direction, and no bridge would change that: both are representation forks rather than missing features. 0.0.6 has nofonttype at all, and 0.1'sparentakes a context where 0.0.6 takes three explicit scalars, with no way to recover the axis.- A 0.0.6 package that WRITES
codein atypedeclaration is refused rather than crossing: 0.0.6 has nocodespelling, so that text would silently acquire 0.1's meaning on the way in. Ordinary staged exports — the inferred kind, from&e— are unaffected and cross both ways.
The manual
The manual is written in SATySFi and typeset by the port itself, so every feature it uses is one that has to keep working.
- manual.pdf · source
manual/logo.saty— the logo above is not an image file but a document, drawn entirely withsatysfi-xpath(notes)
$ make -C manual # manual.pdf, logo.pdf, logo.png
Development
crates/
rustyfi-syntax/ mode-stack lexer and grammar (CST) for both dialects
rustyfi-lang/ elaboration, typechecker, evaluator, primitives
rustyfi-backend/ boxes and glue, line and page breaking, math
rustyfi-loader/ @require/@import resolution and load order
rustyfi-pdf/ PDF writer, font embedding
rustyfi-satyrographos/ package manager
rustyfi/ the binary
lib-rustyfi/ bundled packages: dist/ (0.0) and dist-v01/ (0.1)
layout-tests/ layout fidelity gate, corpus, probes, measurement
install.sh, download-fonts.sh, benchmark.py
License
MIT — see LICENSE.
Two sets of files bundled here are not covered by it and keep their own terms.
The fonts download-fonts.sh fetches carry the IPA Font License v1.0,
SIL OFL 1.1, the GUST Font License and DejaVu's, each copied next to the font it
covers. The SATySFi packages under lib-rustyfi/ are upstream's, LGPL-3.0.