JavaScript API
Loading
| How | You get |
|---|---|
<script src="dist/hatex.js"> |
window.HaTeX |
require('hatex') (CommonJS) |
HaTeX, from dist/hatex.js |
import HaTeX from 'hatex' (ES module) |
HaTeX, from dist/hatex.mjs |
import { render, parse } from 'hatex' |
The same functions as named exports: use, parse, render, enhance, layout, lint, images, tikzSvgs and Bib |
The stylesheet is dist/hatex.css, or hatex/hatex.css through the package's exports.
In every case, the library also sets a few globals that its modules share: window.parseLatex, window.hatexExtend, window.lintSource, window.referencedImages, window.Bib and window.TIKZ_NN_PREAMBLE. Outside a browser, window means globalThis.
Overview
| Function | Needs a DOM | Summary |
|---|---|---|
HaTeX.render(target, source, options?) |
Yes | Parse into an element and enhance it |
HaTeX.parse(source, options?) |
No | LaTeX → HTML string |
HaTeX.enhance(root, options?) |
Yes | Page behaviour for HTML already on the page |
HaTeX.layout(root) |
Yes | Redo the width-dependent layout |
HaTeX.lint(source, options?) |
No | Problems in a source |
HaTeX.images(source) |
No | Local image paths in a source |
HaTeX.tikzSvgs(root) |
Yes | TikZ pictures compiled in this session, as files |
HaTeX.use({ katex, Prism }) |
No | Supply KaTeX and Prism |
HaTeX.Bib |
No (lookup needs fetch) |
BibTeX parsing, formatting and DOI lookup |
HaTeX.version |
No | The version string, e.g. "1.5.0" |
HaTeX.render
HaTeX.render(target: Element | string, source: string, options?: Options): Element
Renders source into target and returns the element.
targetis an element or a CSS selector; a selector uses the first match. The element's content is replaced.- The element gets the class
hatex, which the stylesheet is scoped to. - It is equivalent to
el.innerHTML = HaTeX.parse(source, options); HaTeX.enhance(el, options).
Calling render again on the same element replaces the document and is cheap: an editor can call it on every keystroke (debounced). TikZ pictures that were already compiled come back from the session cache without compiling again.
const el = HaTeX.render('#post', tex, { zoom: false });
HaTeX.parse
HaTeX.parse(source: string, options?: { bib?, bbl?, titleBlock? }): string
Converts a LaTeX source to an HTML string. It is pure: no DOM, no network, and it works in Node and in workers. KaTeX must be available (as a global, or through use) for maths, and Prism for highlighting; otherwise formulas come out as their source in <code> and code as plain text.
The HTML is the document body only. Put it in an element with the class hatex:
article.className = 'hatex';
article.innerHTML = HaTeX.parse(tex);
HaTeX.enhance(article); // optional: see below
Each call gives its element ids a new random prefix, so two documents on one page never clash; see Target ids.
What parse alone does not do is listed under enhance. Without enhance, the HTML is a complete, readable document in one column, with no TikZ pictures (their boxes stay empty), and in-document links work as plain anchor jumps.
HaTeX.enhance
HaTeX.enhance(root: Element, options?: Options): Element
Adds the page behaviour to HTML produced by parse, e.g. HTML rendered at build time. It returns root and:
- adds the class
hatextorootand remembersoptionsfor it; - sets up slide decks: the deck bar with the Present button (unless
deckBar: false), double-click to present; - runs
layout: two columns, fitting\resizeboxcontent and wide equations, and slide scaling; and runs it again once web fonts have loaded; - adds copy buttons to code blocks (unless
copyButtons: false); - makes images and TikZ pictures zoomable (unless
zoom: false); - loads TikZ pictures: from the session cache, from
tikzSvgBase, or live (TikZ).
Calling it again on the same root is safe. Decks, copy buttons, zoom handlers and pictures that are already set up are skipped.
A few things work for every .hatex element on the page, whether it was enhanced or not, because HaTeX listens on document from the moment it loads:
- clicks on
\ref,\eqref,\citeand footnote links scroll to the target and flash it; - window resizes re-run
layouton every.hatexelement; - finished TikZJax compiles are picked up.
HaTeX.layout
HaTeX.layout(root: Element): void
Recomputes everything that depends on the rendered width:
- the split into columns in two-column documents and
multicols; - the scale of display equations in columns;
- the scale of
\resizeboxandadjustboxcontent; - the scale of slides, and the shrinking of slides whose content is too tall;
- the splitting of
\jiazhunotes inguji.
It runs on the next animation frame, not synchronously. HaTeX runs it by itself after render/enhance, when fonts load, on window resize and when a TikZ picture arrives. Call it yourself when the element changes width in any other way:
sidebarToggle.addEventListener('click', () => {
document.body.classList.toggle('sidebar-open');
HaTeX.layout(document.querySelector('#post'));
});
For a container whose size changes often, a ResizeObserver can do it:
new ResizeObserver(() => HaTeX.layout(el)).observe(el);
HaTeX.lint
HaTeX.lint(source: string, options?: { frontMatter?: boolean }): Problem[]
type Problem = { line: number, severity: 'error' | 'warning', message: string }
Returns the structural problems in a source, sorted by line, with errors before warnings on the same line. frontMatter: true also checks the % --- front-matter block. The checks and messages are listed in Checking sources.
for (const p of HaTeX.lint(tex)) console.log(`${p.line}: ${p.severity}: ${p.message}`);
HaTeX.images
HaTeX.images(source: string): { path: string, line: number }[]
Lists the paths of the \includegraphics in a source, with their lines, leaving out http(s): and data: URLs. Commented-out images and those in verbatim blocks are not listed. Use it to check that the files exist; see Checking sources.
HaTeX.tikzSvgs
HaTeX.tikzSvgs(root: Element): { hash: string, file: string, svg: string }[]
Returns the TikZ pictures in root that were compiled live during this session, one entry per distinct picture: file is <hash>.svg, and svg is the file content (exactly what TeX produced, ending with a newline). Save them under your tikzSvgBase so that readers skip compiling; see Pre-rendering. Pictures that were loaded from files are not included.
HaTeX.use
HaTeX.use(libs: { katex?: object, Prism?: object }): HaTeX
Supplies KaTeX and Prism when they aren't globals, as in Node or with a bundler. It sets window.katex and window.Prism (i.e. globalThis outside a browser) and returns HaTeX, so calls can be chained. Call it before parse or render.
import katex from 'katex';
import Prism from 'prismjs';
import 'prismjs/components/prism-python.js';
import HaTeX from 'hatex';
HaTeX.use({ katex, Prism });
HaTeX.Bib
Helpers for turning references into \bibitem lines. None of them needs a DOM.
Bib.parse(text)
Bib.parse(text: string): { type: string, key: string, fields: Record<string, string> }[]
Parses BibTeX. Entry types and field names are lowercased, braces in values are removed and whitespace is collapsed.
HaTeX.Bib.parse('@article{ho2020, author = {Ho, Jonathan and Jain, Ajay}, title = {{Denoising} Diffusion}, year = 2020}');
// → [{ type: 'article', key: 'ho2020', fields: { author: 'Ho, Jonathan and Jain, Ajay', title: 'Denoising Diffusion', year: '2020' } }]
Bib.format(entry)
Bib.format(entry): string
Formats a parsed entry as a \bibitem line: \bibitem{key} Authors. Title. In \emph{Venue}, Year.
- Authors are abbreviated to initials ("J. Ho"). More than six authors become the first three and "et al.".
- The venue is the first of
booktitle,journal,journaltitle,school,institutionandpublisher(unless the publisher is arXiv). For an arXiv paper without a venue, it isarXiv:<id>, found fromeprint, the DOI or the URL. - The key is the entry's own key, unless it is a URL or a DOI; then
Bib.keymakes one.
Bib.key(entry)
Returns the entry's key, or makes a readable one from the first author's last name, the year and the first significant word of the title: ho2020denoising.
Bib.lookup(text)
Bib.lookup(text: string): Promise<Entry[]>
Fetches BibTeX for a DOI or an arXiv id and parses it. It accepts 10.1000/xyz, https://doi.org/10.1000/xyz, 2006.11239, arXiv:2006.11239v2 and https://arxiv.org/abs/2006.11239. Every lookup goes to https://doi.org/<doi> with Accept: application/x-bibtex; arXiv ids go through arXiv's DOIs (10.48550/arXiv.<id>), because browsers can reach doi.org but not arXiv's own API. It rejects with an Error when the text is neither a DOI nor an arXiv id, when nothing is found (404) or when the response isn't BibTeX.
const [entry] = await HaTeX.Bib.lookup('10.48550/arXiv.2006.11239');
source += '\n' + HaTeX.Bib.format(entry);
Bib.isLookup(text)
Returns true when the text looks like a DOI or an arXiv id and not like BibTeX (BibTeX contains @). Use it to decide whether pasted text should be looked up or parsed.
Bib.authorList(raw)
Splits a BibTeX author field into names in reading order: "Ho, Jonathan and Jain, Ajay" → ["Jonathan Ho", "Ajay Jain"].
Options
Options are passed to render and enhance, and apply to that root.
| Option | Default | Description |
|---|---|---|
tikzSvgBase |
'tikz/' |
URL prefix of pre-rendered TikZ pictures, loaded as <tikzSvgBase><hash>.svg. Relative prefixes are relative to the page. null skips the lookup, and every picture is compiled live. |
tikzLive |
true |
Compile pictures that have no SVG in the browser with TikZJax. false leaves them empty (data-tikz-state="missing"). |
tikzErrors |
false |
Show TeX's error in place of a picture that fails to compile, and fire hatex:tikz-error. It works by wrapping console.log/warn/error (TikZJax prints TeX's log there), so it is meant for editors. |
tikzjaxBase |
'https://cdn.jsdelivr.net/npm/@drgrice1/tikzjax@1.0.0-beta24/dist/' |
Where tikzjax.js and fonts.css come from, for self-hosting. Keep the trailing slash. |
copyButtons |
true |
Add a "copy" button to each code block. |
deckBar |
true |
Show the bar above a slide deck (slide count, "double-click one to present from it", Present button). With false the deck starts with its first slide; a double-click on a slide still presents from it. |
zoom |
true |
Open an image or TikZ picture in a full-window overlay when it is clicked. |
bib |
none | The text of a .bib file. \bibliography{…} becomes the list of the entries the text cites; see Papers. For render and parse. |
bbl |
none | The text of a .bbl file, which replaces \bibliography{…} as it is. For render and parse. |
titleBlock |
true |
false leaves out the title block that \maketitle shows (Papers). For render and parse. |
animate |
true |
false makes in-document links jump without smooth scrolling or flashing, and the zoom overlay open and close without a fade. The reader's prefers-reduced-motion setting has the same effect. |
Events
Both events are dispatched on the TikZ picture's box (.latex-tikz) and bubble, so they can be caught on the root or on document.
| Event | detail |
When |
|---|---|---|
hatex:tikz |
{ hash, svg } |
A picture has been compiled live. svg is the file content, as in tikzSvgs. |
hatex:tikz-error |
{ hash, message } |
A picture failed to compile. Only with tikzErrors: true. |
el.addEventListener('hatex:tikz', (e) => saveFile(`tikz/${e.detail.hash}.svg`, e.detail.svg));
Source lines
The output keeps track of where everything came from, so a preview can be linked back to the source:
- every block element (paragraph, heading, list, table, float, theorem, …) has
data-line, the line in the source where it starts; - list items, table rows, bibliography entries and captions have their own
data-line; - inside a block, an empty
<span class="latex-line" data-line="n">marks where each later source line begins.
Lines count from 1, and they stay correct in two-column documents, slides and vertical text.
To find the source line of a click, take the last [data-line] element that starts at or before the clicked node:
out.addEventListener('dblclick', (e) => {
let line = null;
for (const m of out.querySelectorAll('[data-line]')) {
const before = m.contains(e.target) ||
(m.compareDocumentPosition(e.target) & Node.DOCUMENT_POSITION_FOLLOWING);
if (!before) break;
line = Number(m.dataset.line);
}
if (line) goToLine(line);
});
Building an editor
examples/editor.html is a complete editor in about 150 lines. Its core:
let timer;
textarea.addEventListener('input', () => {
clearTimeout(timer);
timer = setTimeout(() => {
const keep = out.scrollTop;
HaTeX.render(out, textarea.value, { tikzSvgBase: null, tikzErrors: true });
out.scrollTop = keep; // re-rendering keeps the reader's place
showProblems(HaTeX.lint(textarea.value));
}, 300);
});
tikzSvgBase: nullcompiles every picture live, so that you see your edits rather than an old SVG; the session cache means that each version of a picture compiles only once.tikzErrors: trueshows TeX errors in place of broken pictures.HaTeX.lintgives the problem list, and the source lines let a double-click jump to the source.HaTeX.tikzSvgs(out)gives the compiled pictures to save for publishing.
Several documents on one page
Each render or parse call is independent: numbering, labels, macros and footnotes don't leak from one document to another, and the ids can't clash. Options are remembered per root. The TikZ session cache is shared, so the same picture in two documents compiles once.
Previous: Vertical Chinese · Contents · Next: Theming