@drghaliasri/butex
v4.2.0
Published
MathJax extension layer for Arabic-friendly math notation (XeLaTeX-like customs beyond plain MathJax)
Maintainers
Readme
BuTeX
Integrators: treat Integration contract (host apps) as the source of truth for wiring MathJax and BuTeX. The npm package ships dist/ and this README.md only; design notes and ADRs live in the GitHub repo under docs/ (they are not included in the published tarball).
BuTeX is a browser-side foundation for Arabic mathematical typography and equation editing on top of MathJax. It ships thin MathJax extensions such as \arabsqrt (Arabic-style mirrored radical; CommonHTML uses CSS mirror/unmirror, SVG uses the MathJax SVG pipeline plus BuTeX SVG helpers where applicable), optional GUI/document layers, and helpers such as parseBuTeX. The longer-term direction is a GUI editor driven by structured equation ASTs.
The vendored MathJax-src/ folder in this repo is reference only — runtime integration uses the mathjax npm package.
Project direction
The intended editor model is AST-first:
- A GUI edits equation nodes rather than raw LaTeX strings.
- BuTeX will maintain local TypeScript ASTs for English and Arabic equation structures so users can switch views.
- A remote service may send equations as JSON compatible with the local ASTs; the browser imports that JSON and renders/edit it locally.
- Arabic/English TeX strings are generated from the AST for MathJax rendering. Remote-rendered strings can be useful for testing/debugging, but should not be the editor state.
- The Python files in references/ are reference material for the emerging JSON shape and conversion behavior.
Rendering outputs and MVP scope
- CommonHTML (
chtml) and SVG are supported. Load the matching MathJax 4 bundle (tex-chtml.jsvstex-svg.js) and pass the same mode asoutputtorenderBuTeXMathIsland/mountBuTeXMathIsland.<ButexEditor />and document math preview follow the loaded bundle when possible (tex2chtmlvstex2svg). \arabsqrt: optional index + mandatory radicand (same argument shape as\sqrt).- Compatibility macros:
\arsqrtaliases\arabsqrt;\unit{...}and\idx{...}are browser-side passthrough wrappers for imported/reference TeX. - Arabic surface parser (
parseBuTeX) — optional helper that maps a tiny subset of Arabic command names to MathJax-safe TeX before rendering (see below). Long-term editor state is structured AST/JSON, not this string transform alone.
Arabic preprocessor (parseBuTeX)
Convert Arabic-friendly math surface syntax to plain MathJax TeX:
| Input | Output |
| ------------------- | -------------------------------- |
| \جذر[3]{س} | \arabsqrt[3]{\text{س}} |
| \كسر{1}{2} | \frac{1}{2} |
| \كسر{\جذر{س}}{10} | \frac{\arabsqrt{\text{س}}}{10} |
| \جتا | \arcos |
Pure string transform — call parseBuTeX(tex), pass the result to MathJax.
import { parseBuTeX } from 'butex';
const mjTex = parseBuTeX(String.raw`\جذر[3]{س}`);
// await renderBuTeXMathIsland(mjTex, { display: true, output: 'chtml' }) ...Run tests with npm test. Live parser demo: demo/parser.html (after npm run build; serve the repo root).
Install
npm install butex mathjaxPeer dependency: mathjax ^4.x (aligned with MathJax 4 components). If you use butex/react, also install react and react-dom (^18 or ^19).
Usage (browser)
- Load MathJax (e.g.
tex-chtml.jsortex-svg.js) after settingwindow.MathJaxconfig. - Load BuTeX’s IIFE bundle (
dist/index.global.jsexposes globalBuTeX). - In
MathJax.startup.ready, callBuTeX.registerBuTeX(MathJax)beforeMathJax.startup.defaultReady(). - Inject styles once:
BuTeX.injectBuTeXStyles()(or embedBuTeX.BUTEX_CHROME_CSSyourself). - When using SVG (
tex-svg.js), callBuTeX.registerBuTeXSvgTextWrapper(MathJax)afterdefaultReady()so Takween/Diwani text,\ad, and Arabic atomic commands (\arsin, etc.) render in the preview with the correct fonts. CommonHTML (tex-chtml.js) uses the injected CSS classes instead. - Render math with
renderBuTeXMathIsland(tex, options?)or mount into a host element viamountBuTeXMathIsland(host, tex, options?). Passoutput: 'svg'when the host loadstex-svg.js, oroutput: 'chtml'withtex-chtml.js. For raw Arabic TeX that did not come from the editor AST, passmirrorOperators: trueto wrap directional operators according to BuTeX's shared operator table.ButexEditorpicks SVG vs CHTML automatically from the loaded MathJax bundle (tex2svgvstex2chtml).
Ensure TeX packages includes butex-arabic-math (use BUTEX_TEX_PACKAGE in config when using { '[+]': [...] }).
Usage (npm / bundler)
MathJax entrypoints (bundlers)
Exact import strings depend on your bundler and how it resolves the mathjax package. Typical ESM imports for MathJax 4 components:
| Desired output | Typical import |
| -------------- | -------------- |
| CommonHTML | import MathJax from 'mathjax/tex-chtml.js' |
| SVG | import MathJax from 'mathjax/tex-svg.js' |
Use the same mode in renderBuTeXMathIsland / mountBuTeXMathIsland via output: 'chtml' or output: 'svg'. If resolution fails, point your import at whatever path your build resolves to the same component bundle (see MathJax’s docs for your version).
import MathJax from 'mathjax/tex-chtml.js'; // or tex-svg.js — match `output` in render calls
import {
registerBuTeX,
injectBuTeXStyles,
renderBuTeXMathIsland,
BUTEX_TEX_PACKAGE,
} from 'butex';
// Before startup resolves — same timing rules as browser:
MathJax.startup.ready = () => {
registerBuTeX(MathJax);
injectBuTeXStyles();
MathJax.startup.defaultReady();
};
MathJax.config.tex = {
packages: { '[+]': ['ams', BUTEX_TEX_PACKAGE] },
};Maintainer-facing design notes live in the GitHub repo under docs/ (not shipped on npm).
Integration contract (host apps)
BuTeX supports two integration modes. Keep this contract for stable behavior.
1) Render-only contract (MathJax + BuTeX macros)
Use this when you only need to render LaTeX strings:
- Register BuTeX with MathJax via
registerBuTeX(MathJax). - Include
butex-arabic-mathin TeX packages. - Inject BuTeX MathJax styles once via
injectBuTeXStyles(document)(or embedBUTEX_CHROME_CSS). - Render expressions with
renderBuTeXMathIsland/mountBuTeXMathIsland(setoutputto match the host bundle:tex-svg.jsvstex-chtml.js). - For raw Arabic TeX, set
mirrorOperators: trueor callmirrorBuTeXOperatorsInTex(tex)before non-island MathJax typesetting. Leave it off for editor-generated TeX because the editor already emits\butexmirror{...}. - For Arabic-friendly surface strings, optionally preprocess with
parseBuTeX(...)before rendering.
This mode does not require the GUI editor runtime.
2) GUI editor contract (shippable editor UX)
Use this when you want the same editor UX as the demo:
- Inject editor styles once via
injectBuTeXEditorStyles(document)(or embedBUTEX_EDITOR_CSS). - Create runtime with
Editor.createEditorRuntime({ ... }). - Pass your editor surface element as
surfaceEl. - Optionally pass
buttonElements(undo/redo/copy/cut/split toggle) for auto button state refresh. - Wire your toolbar/actions to runtime methods (
toggleSide,insertDelimiterByKind,addSup,addSub,removeSup,removeSub,deleteStructure,performUndo,performRedo,performCopy,performCut,performPaste). - Use
onSessionChange(session)to render external previews (e.g., MathJax pane, status labels). - Imported
MathObject/CharObjectfor the editor: document LaTeX preview treatsCharObject.expras already-rendered Arabic TeX (e.g.\text{م}). When opening an imported equation in<ButexEditor />viamathObjectToEditorSession, BuTeX unwraps supported LaTeX text/font wrappers into plain glyphexprplus editorcharacterFont. Supported patterns:\text{…},\takween{…},\diwani{…},\butextakween{…},\butexdiwani{…},\butexdiwanioutline{…}, and nested forms such as\text{\takween{…}}. On save, default-font chars stay plainCharObjectnodes; non-default fonts are stored as fontCommandObjectwrappers so editor round-trip preservescharacterFont. Upstream converters may still emit wrappedexpron import.
Minimal browser example:
BuTeX.injectBuTeXEditorStyles(document);
const runtime = BuTeX.Editor.createEditorRuntime({
surfaceEl: document.getElementById('surface'),
onSessionChange: (session) => {
// host-render math preview/status here
},
});React widget (butex/react)
Shipped as a separate entry so apps that do not use React never pull it in.
- Import:
import { ButexEditor } from 'butex/react'. - Peer dependencies when using this entry:
reactandreact-dom(^18 or ^19). - The component wraps
Editor.createEditorRuntime(toolbar, surface, MathJax preview strip, optional dev panels viadebug). - Load MathJax and register BuTeX before relying on the preview (same timing as the GUI contract above). Equation preview in
ButexEditoruses the samerenderBuTeXMathIslandpath as document math islands (output follows the loaded bundle: SVG whentex2svgis available, otherwise CHTML). Optional legacymountBuTeXMathTypeset(typesetPromise) remains exported for hosts that still rely on it. - Next.js App Router: put BuTeX in a client component (
'use client').
'use client';
import { ButexEditor } from 'butex/react';
export default function Page() {
return <ButexEditor uiLocale="en" defaultSide="english" />;
}uiLocale selects Arabic ("ar", the default) or English ("en") GUI labels, tooltips, errors, accessibility text, and chrome direction. defaultSide independently selects the initial "english" or "arabic" equation side; users can still switch equation sides from the toolbar.
Document AST (butex/document)
Use this headless entry when a host app or remote service already has DocumentObject JSON and needs a structured import/export layer.
import {
fromDocumentJson,
createEmptyDocument,
renderDocumentLatex,
buildDocumentPreview,
} from 'butex/document';
const documentNode = fromDocumentJson({
node_type: 'DocumentObject',
blocks: [{ command: '\\section', value: 'Intro $x$' }],
});
const emptyDocument = createEmptyDocument();
const latex = renderDocumentLatex(documentNode);
const preview = buildDocumentPreview(documentNode);V1 supports simple headings, paragraphs, itemize / enumerate, tabular, includegraphics, raw blocks, and math spans detected inside text. The document layer detects math delimiters and can align them with ordered imported MathObject JSON; it does not parse arbitrary equation LaTeX into chain nodes in the browser.
Document React widget (butex/react-document)
Use this entry for the first document-editor UI. It edits supported document blocks, shows a live semantic preview, and opens the equation editor for supported imported math islands.
'use client';
import { ButexDocumentEditor } from 'butex/react-document';
export default function Page() {
return (
<ButexDocumentEditor
debug
onLatexChange={(latex) => console.log(latex)}
/>
);
}The preview is semantic HTML for document structure. Math is emitted as escaped per-span islands, so host apps still need the normal MathJax + BuTeX registration when they want typeset math preview rather than TeX placeholders.
Document AST v2 (butex/document2)
Use this parallel v2 headless entry for the token-owned document model. Imported delimited math is converted into math tokens between prose tokens; normal editing should mutate text tokens and math tokens separately rather than treating raw delimited TeX as one textarea value.
import {
fromDocumentJson2,
document2Latex,
document2Preview,
} from 'butex/document2';
const documentNode = fromDocumentJson2({
node_type: 'DocumentObject',
blocks: [{ command: '\\paragraph', value: 'نص $x$' }],
});
const latex = document2Latex(documentNode);
const preview = document2Preview(documentNode, 'svg');V2 exports Arabic TeX by default for equations saved from the embedded editor. Imported raw-only math is preserved as raw source and marked non-editable until a structured equation object is attached.
Document React widget v2 (butex/react-document2)
Use this entry for the new document editor integration. It opens embedded <ButexEditor /> sessions in Arabic/RTL mode by default and renders document preview math islands with MathJax SVG by default (mathOutput defaults to 'svg'; pass mathOutput="chtml" only if the host loads tex-chtml.js).
'use client';
import { ButexDocumentEditor2 } from 'butex/react-document2';
export default function Page() {
return (
<ButexDocumentEditor2
debug
uiLocale="en"
documentDirection="ltr"
equationSide="english"
editableEquations
onLatexChange={(latex) => console.log(latex)}
/>
);
}Host apps still register BuTeX with MathJax before preview rendering. uiLocale selects Arabic ("ar", the default) or English ("en") document-editor chrome and is passed to the embedded equation editor. documentDirection independently controls prose inputs and preview flow without creating a second document tree. equationSide independently controls whether structured equations open, render, and save from the "english" or "arabic" side. Set editableEquations={false} to show math islands without equation insertion, deletion, or editor access. Raw-only equations keep their original source because the browser does not parse raw LaTeX into equation ASTs. Document editor v2 defaults to tex-svg.js; use tex-chtml.js only if you pass mathOutput="chtml".
Styling/theming contract
- Core editor classes (
.surface,.chain,.slot,.node,.scripts,.delim*, etc.) are shipped fromsrc/editor/styles.ts. - Theme with one namespace,
--butex-*, on.butex-widget(or an ancestor of the editor surface). Defaults are set onButexEditor’s root; hosts override accents, borders,--butex-surface-bg/--butex-surface-fg,--butex-preview-bg/--butex-preview-fg, focus/slot/selection, scripts, optional--butex-dev-*/--butex-dev-inset-*when usingdebug, etc., without redefining structure classes. - Document editor theming is scoped under
.butex-document-widget. Override--butex-document-*variables on that root or an ancestor, especially--butex-document-bg,--butex-document-fg,--butex-document-panel,--butex-document-border,--butex-document-accent,--butex-document-preview-bg,--butex-document-drawer-bg,--butex-document-input-bg,--butex-document-table-border,--butex-document-math-bg, and--butex-document-dev-bg. - Document editor v2 theming is scoped under
.butex-document2-widget. Override--butex-document2-*variables on that root or an ancestor; defaults inherit from the existing--butex-*variables where practical, including accent, panel, border, preview, focus, danger/error, and debug colors. The embedded equation editor opens as a centered modal (backdrop + panel); its BuTeX chrome reads the same--butex-document2-*tokens via a scoped bridge, so hosts normally theme once on.butex-document2-widgetwithout a second equation theme. The built-in editor uses a sticky compact icon toolbar (heading glyphs, inline$…$vs display\[…\]math buttons with tooltips, table size popover), focus-aware block insertion after the active block, ↑/↓ block reorder, math delete (chip × and drawer button), document undo/redo (toolbar + Ctrl/⌘Z, Ctrl/⌘Shift+Z, Ctrl+Y) via AST snapshots, and theme-tinted block cards. Preview tables use fixed black cell borders; raw\rawblocks stay out of preview until dedicated rendering exists.
Demo
From repo root (after npm run build):
npm run demoOpen:
http://localhost:4173/demo/(MathJax +\arabsqrt)http://localhost:4173/demo/parser.html(parser-only normalize preview)- Equation editor (React): run
cd demo/editor-app && npm install && npm run dev, then open the URL Vite prints (seedemo/editor.htmlfor a short pointer). The equation editor demo loads MathJax SVG frompublic/vendor/, matching document editor v2. - Document editor (React): run
cd demo/document-editor-app && npm install && npm run dev, then open the URL Vite prints (seedemo/document.htmlfor a short pointer). - Document editor v2 (React): run
cd demo/document-editor-app2 && npm install && npm run dev, then open the URL Vite prints. The v2 demo loads MathJax SVG and includes grouped insert controls (sections, lists, tables, figures, equations), per-block delete, list item controls, and editor/preview toggles. Optional bottom dev panels (LaTeX + AST) appear only if you append?debug=1to the URL or pass thedebugprop from your host app.
Editor MVP notes (demo/editor-app)
- Editing is AST-first (not raw LaTeX text editing).
- End-user UI is Arabic-first.
- Supported structures in this MVP: chars, numbers, operators, delimiter pairs
(),[],{}, fractions (\frac), and sup/sub chains. - Caret: Left/Right walk every insertion gap in a fixed depth-first order (baseline and nested chains). Up/Down move between superscript and subscript where applicable (from the baseline, Up prefers superscript and Down prefers subscript when both exist). On the Arabic surface, arrow keys follow RTL progression.
- Char/number typing: by default keystrokes merge into the same leaf along the caret gap (digits only extend
number, letters only extendchar). Use the Split leaf typing toolbar control to toggle split mode (one new leaf per keystroke). Delete/Backspace trim inside the merged string when split mode is off. - Structural edits are mirrored across Arabic/English trees; text edits apply to the active side.
- Undo/redo: full-session snapshots (
createUndoRedoStacks,pushUndoRedoSnapshot,restoreUndo,restoreRedo). The demo exposes toolbar buttons plus Ctrl+Z / ⌘Z for undo and Ctrl+Shift+Z / ⌘⇧Z / Ctrl+Y for redo while the editing surface is focused. Caret moves and language switching are not recorded so undo targets content edits only. History caps at ~100 steps by default (DEFAULT_UNDO_HISTORY_MAX_DEPTH). - Selection + copy/cut/paste: range selection lives at the slot level inside a single chain (no cross-chain selection in the MVP). Whole nodes are the copy unit, so superscripts/subscripts/inner subtrees always travel with their owner.
- Keyboard: Shift+ArrowLeft/ArrowRight extend selection (RTL-aware), Ctrl/⌘+C/X/V copy/cut/paste, Backspace/Delete and typing replace an active selection.
- Mouse: click-drag from one slot to another inside the same chain. Cross-chain mouse moves are ignored.
- Clipboard format:
application/x-butex-fragment+jsonwith both EN and AR mirrored nodes (EditorFragmentv1) plus a plain-text LaTeX fallback for cross-app pasting. External plain text re-enters through the typing path so split/merge mode applies. - Future node types: the single helper
nodeChildChains(node)enumerates a node's nested chains. Adding command, env, or math-object kinds (see[references/nodes.py](references/nodes.py)) only requires extending this helper; selection, copy, paste, and id-remap stay unchanged.
- Debug panels are dev-only (
debugprop on<ButexEditor />, or?debug=1in the Vite demo URL).- Includes LaTeX dump, passive chain preview, and copyable command/render log.
Editor theming (CSS variables)
Override these on :root (or a host container) to customize colors:
--butex-bg--butex-fg--butex-muted--butex-accent--butex-border--butex-panel--butex-caret--butex-selected--butex-error--butex-error-bg--butex-accent-hover(optional, demo toolbar)--butex-shadow-sm/--butex-shadow-md(optional, panels)--butex-dev-bg/--butex-dev-border/--butex-dev-badge/--butex-dev-muted(optional, developer-only strips whendebugis on)
License
ISC (BuTeX package). MathJax is Apache-2.0 — see upstream.
