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-toolsCopy the required WASM and Perl assets to your public directory:
npx wasm-latex-tools copy-assetsThis copies WebAssembly files and Perl scripts to ./public/core/ by default.
For a custom location:
npx wasm-latex-tools copy-assets ./static/wasmimport { 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 documentimport { 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 documentconst 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/coreimport { WebPerlRunner, TexCount } from 'wasm-latex-tools';
const runner = new WebPerlRunner();
await runner.initialize();npx wasm-latex-tools copy-assets ./public/coreSame 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-toolsconst 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 buildTo run the interactive demo locally:
npm install
npm run build
npm run exampleThen 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-assetsMake 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