Skip to content

A fast WebAssembly LaTeX compiler. TeX Live 2025 in your browser with lazy loading and on-demand package resolution.

License

Notifications You must be signed in to change notification settings

SiglumProject/siglum

Repository files navigation

Siglum

npm version license

A browser-based LaTeX compiler. TeX Live 2025 running in WebAssembly, with lazy bundle loading and on-demand package fetching.

On initialization, the engine downloads:

  • WASM binary (~29MB): the TeX engine
  • Core bundles (~16MB for pdfLaTeX): format files, fonts, base packages

Common packages (amsmath, tikz, biblatex, etc.) are pre-bundled for fast loading. Less common packages are fetched from TexLive/CTAN automatically during compilation. Everything is cached in the browser for offline use.

The full bundle is ~195MB to deploy, but clients only download what their documents need.

Guides:

Quick Start (Local Demo)

Clone the repo, download the WASM engine and pre-built bundles from cdn.siglum.org (or GitHub Releases), then start the dev server.

We use Bun for development, it's faster than Node.js and runs TypeScript directly. Install it with:

curl -fsSL https://bun.sh/install | bash

Then:

git clone [email protected]:SiglumProject/siglum-engine.git
cd siglum-engine
bun install

# Download WASM and bundles
mkdir -p busytex/build/wasm
curl -Lo busytex/build/wasm/busytex.wasm https://cdn.siglum.org/tl2025/busytex.wasm
curl -Lo busytex/build/wasm/busytex.js https://cdn.siglum.org/tl2025/busytex.js
curl -LO https://cdn.siglum.org/tl2025/siglum-bundles-v0.1.0.tar.gz
tar -xzf siglum-bundles-v0.1.0.tar.gz -C packages/

# Start dev server
bun serve-local.ts

Open http://localhost:8787 to try the playground.

For disk-persistent package caching, run the CTAN proxy in a separate terminal:

bun packages/ctan-proxy.ts

Installation

npm install @siglum/engine

Download Runtime Assets

Download WASM and bundles from cdn.siglum.org or GitHub Releases:

curl -LO https://cdn.siglum.org/tl2025/busytex.wasm
curl -LO https://cdn.siglum.org/tl2025/busytex.js
curl -LO https://cdn.siglum.org/tl2025/siglum-bundles-v0.1.0.tar.gz

# Extract to your public directory
tar -xzf siglum-bundles-v0.1.0.tar.gz -C public/
mv busytex.wasm busytex.js public/

Then configure:

import { SiglumCompiler } from '@siglum/engine';

const compiler = new SiglumCompiler({
    bundlesUrl: '/bundles',
    wasmUrl: '/busytex.wasm',
});

Or use the CDN directly (no self-hosting required):

const compiler = new SiglumCompiler({
    bundlesUrl: 'https://cdn.siglum.org/tl2025/bundles',
    wasmUrl: 'https://cdn.siglum.org/tl2025/busytex.wasm',
});

Usage

import { SiglumCompiler } from '@siglum/engine';

const compiler = new SiglumCompiler({
    bundlesUrl: '/bundles',
    wasmUrl: '/wasm/busytex.wasm',
});

await compiler.init();

const result = await compiler.compile(`
\\documentclass{article}
\\begin{document}
Hello, World!
\\end{document}
`);

if (result.success) {
    const blob = new Blob([result.pdf], { type: 'application/pdf' });
    window.open(URL.createObjectURL(blob));
}

API

new SiglumCompiler(options)

const compiler = new SiglumCompiler({
    // URLs
    bundlesUrl: '/bundles',           // URL to bundle files
    wasmUrl: '/wasm/busytex.wasm',    // URL to WASM binary
    jsUrl: null,                      // URL to busytex.js (derived from wasmUrl if null)
    ctanProxyUrl: null,               // CTAN proxy URL (enables CTAN fetching when set)
    workerUrl: null,                  // Custom worker URL (uses embedded worker if null)

    // Feature flags
    enableCtan: false,                // Auto-enabled when ctanProxyUrl is set
    enableLazyFS: true,               // Load files on-demand (faster startup)
    enableDocCache: true,             // Cache compiled documents by preamble hash

    // Performance tuning
    maxRetries: 15,                   // Max retries for CTAN/bundle fetches per compile
    eagerBundles: {},                 // Bundles to load immediately (see below)
    verbose: false,                   // Log TeX stdout (disable for performance)

    // Callbacks
    onLog: (msg) => {},               // Log callback
    onProgress: (stage, detail) => {},  // Progress callback
});

eagerBundles

By default, large bundles like cm-super (fonts) are loaded on-demand when TeX requests them. This saves bandwidth but adds latency on first use. To pre-load specific bundles:

// Load cm-super fonts eagerly for all engines
const compiler = new SiglumCompiler({
    eagerBundles: ['cm-super'],
});

// Or per-engine configuration
const compiler = new SiglumCompiler({
    eagerBundles: {
        pdflatex: ['cm-super'],
        xelatex: [],  // XeLaTeX uses system fonts
    },
});

maxRetries

Controls how many times the compiler retries when a package or bundle fetch fails. With pre-scanning (which batch-fetches most packages before compilation), retries are rare. Lower values fail faster if something is broken:

const compiler = new SiglumCompiler({
    maxRetries: 5,  // Fail fast (default: 15)
});

verbose

Controls whether TeX stdout is sent to the onLog callback. Disabled by default for performance—a typical compilation generates ~4,000 log lines, each requiring a postMessage call from the worker.

const compiler = new SiglumCompiler({
    verbose: true,  // Enable TeX stdout logging (default: false)
    onLog: (msg) => console.log(msg),
});

// Can also be changed after instantiation
compiler.verbose = true;   // Enable for next compile
await compiler.compile(source);
compiler.verbose = false;  // Disable again

When verbose: false:

  • TeX stdout ([TeX] ...) is suppressed
  • TeX errors ([TeX ERR] ...) are always logged
  • Worker status messages are always logged
  • Error detection still works (stdout is captured internally)

compiler.compile(source, options?)

const result = await compiler.compile(source, {
    engine: 'pdflatex',  // 'pdflatex' | 'xelatex' | 'auto'
    additionalFiles: {   // Include custom files
        'mypackage.sty': '\\ProvidesPackage{mypackage}...',
        'image.png': uint8Array,
    },
});

// result.success  — boolean
// result.pdf      — Uint8Array (if successful)
// result.log      — TeX log output
// result.error    — error message (if failed)

compiler.clearCache()

Clear all cached packages and compiled PDFs.

compiler.unload()

Free memory by unloading the WASM module. Call init() again to reload.

createBatchedLogger(onFlush)

Helper to batch log messages and avoid DOM thrashing. The TeX compiler emits hundreds of log lines during compilation and updating the DOM on each message can cause significant slowdowns.

import { SiglumCompiler, createBatchedLogger } from '@siglum/engine';

const compiler = new SiglumCompiler({
    bundlesUrl: '/bundles',
    wasmUrl: '/wasm/busytex.wasm',
    onLog: createBatchedLogger((messages) => {
        // Called once per animation frame with all buffered messages
        logDiv.textContent += messages.join('\n') + '\n';
        logDiv.scrollTop = logDiv.scrollHeight;
    }),
});

Performance Tips

Batch log updates

If you're displaying compiler logs in the UI, always use createBatchedLogger or implement your own batching. Unbatched DOM updates can add 2-3 seconds to compilation time.

Pre-warm the compiler

Call compiler.init() early (e.g., on page load) so bundles are ready when the user compiles:

// On page load
const compiler = new SiglumCompiler(options);
compiler.init(); // Fire and forget — bundles download in background

// Later, when user clicks compile
await compiler.compile(source); // Already warmed up

Engines

Engine Status
pdfLaTeX Full support, format caching
XeLaTeX Full support, custom fonts via fontspec

Use engine: 'auto' to auto-detect based on document content.

Hosting / Production

Download assets from cdn.siglum.org or GitHub Releases:

Asset Size Description
busytex.wasm 29 MB WebAssembly TeX engine
busytex.js 292 KB Emscripten glue code
siglum-bundles-v0.1.0.tar.gz ~195 MB LaTeX packages & fonts

Serve with these headers (required for SharedArrayBuffer):

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Access-Control-Allow-Origin: *
Cross-Origin-Resource-Policy: cross-origin

Expected structure after extracting:

your-server.com/
├── busytex.wasm
├── busytex.js
└── bundles/
    ├── bundles.json
    ├── file-manifest.json
    ├── file-to-package.json
    ├── package-deps.json
    └── *.data.gz (54 bundle files)

CTAN Proxy

The CTAN proxy fetches missing LaTeX packages on-demand:

bun packages/ctan-proxy.ts

Packages are cached permanently. The proxy tries TexLive 2025 archives first, then falls back to CTAN mirrors.

For configuration and deployment options, see docs/ctan-proxy.md.

Browser Requirements

  • Modern browser with WebAssembly support
  • SharedArrayBuffer (requires COOP/COEP headers)
  • ~512MB RAM for compilation (max heap size)

Acknowledgments

Built on BusyTeX.

License

MIT

About

A fast WebAssembly LaTeX compiler. TeX Live 2025 in your browser with lazy loading and on-demand package resolution.

Topics

Resources

License

Stars

Watchers

Forks