Run LaTeX tools (texcount, latexdiff, latexpand, tex-fmt) directly in your browser using WebAssembly. This package bundles WebPerl WASM for Perl-based tools and Rust WASM (wasm-bindgen) for tex-fmt, providing a complete LaTeX toolchain without server-side dependencies.
- TexCount: Count words in LaTeX documents with support for multi-file projects
- LaTeX Diff: Generate diff documents showing changes between two LaTeX files
- Latexpand: Expand LaTeX documents by inlining included files
- TeX-Fmt: Format and beautify LaTeX documents using Rust WASM (wasm-bindgen)
All tools run entirely in the browser with no server required.
npm install wasm-latex-tools
Copy the required WASM and Perl assets to your public directory:
npx wasm-latex-tools copy-assets
This copies WebAssembly files and Perl scripts to ./public/core/
by default.
For a custom location:
npx wasm-latex-tools copy-assets ./static/wasm
import { WebPerlRunner, TexCount, TexFmt } from 'wasm-latex-tools';
// Initialize the WebPerl runner
const runner = new WebPerlRunner();
await runner.initialize();
// Count words in a LaTeX document
const texCount = new TexCount(runner);
const countResult = await texCount.count({
input: '\\documentclass{article}\\n\\begin{document}\\nHello World\\n\\end{document}'
});
console.log(texCount.parseOutput(countResult.output));
// Format a LaTeX document with tex-fmt (wasm-bindgen)
const texFmt = new TexFmt();
const formatResult = await texFmt.format({
input: '\\documentclass{article}\\n\\begin{document}\\nHello World\\n\\end{document}',
wrap: true,
wraplen: 80
});
console.log(formatResult.output);
If you copied assets to a custom location, configure the paths:
const runner = new WebPerlRunner({
webperlBasePath: '/wasm/webperl',
perlScriptsPath: '/wasm/perl'
});
const texCount = new TexCount(runner);
const result = await texCount.count({
input: mainFileContent,
includeFiles: true,
merge: false,
additionalFiles: [
{ path: 'chapter1.tex', content: chapter1Content },
{ path: 'chapter2.tex', content: chapter2Content }
]
});
const parsed = texCount.parseOutput(result.output);
console.log(`Words: ${parsed.words}, Headers: ${parsed.headers}`);
import { LatexDiff } from 'wasm-latex-tools';
const latexDiff = new LatexDiff(runner);
const result = await latexDiff.diff(oldContent, newContent, {
type: 'UNDERLINE',
flatten: true
});
console.log(result.output); // Diff LaTeX document
import { Latexpand } from 'wasm-latex-tools';
const latexpand = new Latexpand(runner);
const result = await latexpand.expand({
input: mainFileContent,
keepComments: true,
additionalFiles: [
{ path: 'intro.tex', content: introContent },
{ path: 'conclusion.tex', content: conclusionContent }
]
});
console.log(result.output); // Expanded LaTeX document
const texFmt = new TexFmt();
const result = await texFmt.format({
input: latexContent,
wrap: true,
wraplen: 100,
tabsize: 4,
usetabs: false
});
The core runner for Perl-based tools.
const runner = new WebPerlRunner({
webperlBasePath?: string; // Default: '/core/webperl'
perlScriptsPath?: string; // Default: '/core/perl'
verbose?: boolean; // Default: false
});
await runner.initialize();
Count words in LaTeX documents.
const texCount = new TexCount(runner, verbose?);
const result = await texCount.count({
input: string;
brief?: boolean;
total?: boolean;
sum?: boolean;
verbose?: number;
includeFiles?: boolean;
merge?: boolean;
additionalFiles?: { path: string; content: string }[];
});
const parsed = texCount.parseOutput(result.output);
// Returns: { words: number, headers: number, captions: number, raw: string }
Generate diff documents between two LaTeX files.
const latexDiff = new LatexDiff(runner, verbose?);
const result = await latexDiff.diff(oldContent, newContent, {
type?: 'UNDERLINE' | 'CTRADITIONAL' | 'CFONT' | 'CHANGEBAR';
subtype?: string;
floattype?: 'FLOATSAFE' | 'IDENTICAL';
encoding?: string;
mathMarkup?: number;
allowSpaces?: boolean;
flatten?: boolean;
});
Expand LaTeX documents by inlining includes.
const latexpand = new Latexpand(runner, verbose?);
const result = await latexpand.expand({
input: string;
keepComments?: boolean;
emptyComments?: boolean;
expandUsepackage?: boolean;
makeatletter?: boolean;
showGraphics?: boolean;
fatal?: boolean;
additionalFiles?: { path: string; content: string }[];
});
Format LaTeX documents with Rust WASM (wasm-bindgen), with no runner needed.
const texFmt = new TexFmt(verbose?, wasmBasePath?);
const result = await texFmt.format({
input: string;
wrap?: boolean;
wraplen?: number;
tabsize?: number;
usetabs?: boolean;
});
npx wasm-latex-tools copy-assets ./public/core
// pages/index.tsx or app/page.tsx
'use client'; // For App Router
import { WebPerlRunner, TexCount } from 'wasm-latex-tools';
import { useEffect, useState } from 'react';
export default function Page() {
const [runner, setRunner] = useState<WebPerlRunner | null>(null);
useEffect(() => {
const initRunner = async () => {
const r = new WebPerlRunner();
await r.initialize();
setRunner(r);
};
initRunner();
}, []);
// Use runner...
}
npx wasm-latex-tools copy-assets ./public/core
import { WebPerlRunner, TexCount } from 'wasm-latex-tools';
const runner = new WebPerlRunner();
await runner.initialize();
npx wasm-latex-tools copy-assets ./public/core
Same usage as Vite example above.
For custom static file servers, copy assets to your static directory and configure paths:
npx wasm-latex-tools copy-assets ./static/latex-tools
const runner = new WebPerlRunner({
webperlBasePath: '/latex-tools/webperl',
perlScriptsPath: '/latex-tools/perl'
});
git clone https://github.com/TeXlyre/wasm-latex-tools.git
cd wasm-latex-tools
npm install
npm run build
To run the interactive demo locally:
npm install
npm run build
npm run example
Then open http://localhost:3000
in your browser.
To run the GitHub Pages example:
npm run build:pages-example
npm run pages-example
# Default location (./public/core)
npx wasm-latex-tools copy-assets
# Custom location
npx wasm-latex-tools copy-assets ./static/wasm
# In package.json scripts
{
"scripts": {
"postinstall": "wasm-latex-tools copy-assets"
}
}
After running copy-assets
, your directory will contain:
public/core/
├── webperl/
│ ├── emperl.js
│ ├── emperl.wasm
│ ├── perlrunner.html
│ └── webperl.js
├── perl/
│ ├── texcount.pl
│ ├── latexdiff.pl
│ └── latexpand.pl
└── texfmt/
├── tex_fmt.js
└── tex_fmt_bg.wasm
- First Load: WebPerl WASM initialization takes 1-2 seconds
- Subsequent Runs: Tool execution is fast (milliseconds for small documents)
- Large Documents: Multi-file projects with hundreds of pages process in seconds
- Memory: Each tool creates temporary files in WASM filesystem
Ensure you've run the copy command and configured paths correctly:
npx wasm-latex-tools copy-assets
Make sure your development server serves the assets directory. Most frameworks handle this automatically for the public/
directory.
For very large documents, you may need to increase timeouts. The default is 60 seconds for script execution.
WebPerl runs in WASM with limited memory. For very large projects, consider splitting into smaller chunks.
- WebPerl - Perl 5 compiled to WebAssembly
- tex-fmt - LaTeX formatter in Rust
- texcount - Word counting for LaTeX in Perl
- latexdiff - LaTeX diff tool in Perl
- latexpand - LaTeX expander in Perl
Contributions are welcome! Please feel free to submit a Pull Request.
AGPL-3.0 License © 2025 Fares Abawi