diff --git a/.github/workflows/release-crates.yml b/.github/workflows/release-crates.yml index 266dc311..021cc6bf 100644 --- a/.github/workflows/release-crates.yml +++ b/.github/workflows/release-crates.yml @@ -51,7 +51,7 @@ jobs: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: | # 拓扑序:依赖在前,被依赖在后(与 workspace 的 path 依赖一致) - for crate in ratex-types ratex-font ratex-lexer ratex-parser ratex-layout ratex-ffi ratex-render ratex-wasm; do + for crate in ratex-types ratex-font ratex-lexer ratex-parser ratex-layout ratex-svg ratex-ffi ratex-render ratex-wasm; do echo "Publishing $crate..." cargo publish -p "$crate" done diff --git a/README.md b/README.md index 732a1877..71e59fe3 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,24 @@ [简体中文](README.zh-CN.md) | **English** -**Project site:** [erweixin.github.io/RaTeX](https://erweixin.github.io/RaTeX/) (landing page + Math / Chemistry / Physics galleries) - **KaTeX-compatible math rendering engine in pure Rust — no JavaScript, no WebView, no DOM.** -Parse LaTeX, lay it out with TeX rules, and render it natively on any platform. Glue layers are ready — use out of the box on every platform. +One Rust core, one display list, every platform renders natively. ``` -\frac{-b \pm \sqrt{b^2 - 4ac}}{2a} → iOS · Android · Flutter · React Native · Web · PNG · SVG +\frac{-b \pm \sqrt{b^2-4ac}}{2a} → iOS · Android · Flutter · React Native · Web · PNG · SVG ``` +**[→ Live Demo](https://erweixin.github.io/RaTeX/demo/live.html)** — type LaTeX and compare RaTeX vs KaTeX side-by-side · +**[→ Support table](https://erweixin.github.io/RaTeX/demo/support-table.html)** — RaTeX vs KaTeX across all test formulas + --- ## Why RaTeX? -Every major cross-platform math renderer today runs LaTeX through a browser or a JavaScript engine. That means: +Every major cross-platform math renderer today runs LaTeX through a browser or JavaScript engine — a hidden WebView eating 50–150 MB RAM, startup latency before the first formula, no offline guarantee. -- A hidden WebView eating 50–150 MB of RAM -- JavaScript startup latency before the first formula appears -- No offline guarantee, no predictable performance - -RaTeX cuts the web stack out entirely. One Rust core, one display list, every platform renders natively. +RaTeX cuts the web stack out entirely: | | KaTeX (web) | MathJax | **RaTeX** | |---|---|---|---| @@ -30,38 +27,24 @@ RaTeX cuts the web stack out entirely. One Rust core, one display list, every pl | Mobile | WebView | WebView | **Native** | | Offline | Depends | Depends | **Yes** | | Bundle overhead | ~280 kB JS | ~500 kB JS | **0 kB JS** | -| Memory model | GC / heap | GC / heap | **Predictable** | +| Memory | GC / heap | GC / heap | **Predictable** | | Syntax coverage | 100% | ~100% | **~99%** | --- -## Key facts - -- **~99%** of KaTeX formula syntax — **intended** coverage: parse + layout on the same path as KaTeX for that share of the language. **The ~80% golden score below is a poor proxy for “how much formula support exists”** — it only measures PNG raster similarity. -- **~80%** aggregate golden score vs KaTeX reference PNGs (ink-coverage metric — **not** formula coverage or project completeness; **many** cases already score very high; see **Project status** below) -- **One display list** output: flat, serializable drawing commands consumed by any renderer -- **C ABI** (`ratex-ffi`) for FFI from Swift, Kotlin, Dart, Go, C++ -- **Platform glue layers**: iOS / Android / Flutter / React Native bindings ready — **out of the box** -- **WASM** (`ratex-wasm`) for drop-in browser use via `` Web Component -- **Server-side PNG** via tiny-skia — no browser needed -- **SVG export** (`ratex-svg`) — vector output, self-contained or KaTeX-webfont-based - -**[→ Live Demo](https://erweixin.github.io/RaTeX/demo/live.html)** — type LaTeX and compare RaTeX (Rust/WASM) vs KaTeX side-by-side · -**[→ Support table](https://erweixin.github.io/RaTeX/demo/support-table.html)** — RaTeX vs KaTeX across all 916 test formulas - ---- +## What it renders -## Project status +**Math** — ~99% of KaTeX syntax: fractions, radicals, integrals, matrices, environments, stretchy delimiters, and more. -**Intended formula coverage is broad** — the ~**99%** figure is the **goal** for how much of KaTeX’s formula surface RaTeX should handle the same way; **implementation is ongoing**, and you should **validate** against your own LaTeX. The headline **~80%** number **does not** summarize how far along that is; it is **only** the outcome of an automated **PNG vs PNG** golden pass. +**Chemistry** — full mhchem support via `\ce` and `\pu`: -**Syntax coverage and pixel-level parity are different.** For sources in scope, the aim is the same parse/layout path as KaTeX; **there are still** syntax and layout edge cases to fix, but that backlog **should not** be inferred from the ~80% raster score alone. - -**Golden tests** rasterize RaTeX and KaTeX to PNG and score pairs with an **ink-coverage** metric (ink-pixel overlap / IoU-style signals, plus geometry checks). The **suite-wide aggregate** is about **~80%** — that is **not** 1:1 pixel agreement with KaTeX for every case, and it is **not** “only 80% of formulas are supported” or “the project is ~80% finished.” **Many** individual `test_cases` already reach **very high** similarity to KaTeX; the average reflects harder cases (metrics, paths, hinting, anti-aliasing, and other raster/layout details). - -**Why the ink score punishes tiny shifts:** The metric is computed on a **pixel grid**: it compares which pixels count as “ink” in the reference vs candidate. A **small** layout delta—**spacing or kerning between two letters** nudged by even a **sub-pixel** amount—moves glyph outlines on that grid, so the two ink masks **misalign** and IoU-style overlap **falls** even when the formula still **looks** almost the same to the eye. **Anti-aliasing** makes it worse: stroke edges are partially transparent, so slight positional differences change many border pixels at once. The ~80% headline is therefore a **strict raster** summary: it mixes real layout gaps with **pixel-level strictness**, not a plain “visual quality percentage.” +```latex +\ce{H2SO4 + 2NaOH -> Na2SO4 + 2H2O} +\ce{Fe^{2+} + 2e- -> Fe} +\pu{1.5e-3 mol//L} +``` -**LaTeX math is complex** — edge cases stack quickly. **We need real-world feedback:** if something looks wrong, please open an issue with the **exact LaTeX** (and a screenshot if you can) so we can turn it into a regression test. See [`CONTRIBUTING.md`](CONTRIBUTING.md). +**Physics units** — `\pu` for value + unit expressions following IUPAC conventions. --- @@ -69,111 +52,51 @@ RaTeX cuts the web stack out entirely. One Rust core, one display list, every pl | Platform | How | Status | |---|---|---| +| **iOS** | XCFramework + Swift / CoreGraphics | Out of the box | +| **Android** | JNI + Kotlin + Canvas · AAR | Out of the box | +| **Flutter** | Dart FFI + `CustomPainter` | Out of the box | +| **React Native** | Native module + C ABI · iOS/Android views | Out of the box | | **Web** | WASM → Canvas 2D · `` Web Component | Working | | **Server / CI** | tiny-skia → PNG rasterizer | Working | -| **iOS** | Swift/ObjC bindings to C ABI · XCFramework | Out of the box | -| **Android** | JNI → Kotlin/Java · AAR | Out of the box | -| **React Native** | Native module via C ABI · iOS/Android views | Out of the box | -| **Flutter** | Dart FFI via C ABI | Out of the box | - -> Rust core and all platform glue layers (iOS, Android, Flutter, React Native, Web) are ready; integrate and ship. +| **SVG** | `ratex-svg` → self-contained vector SVG | Working | --- ## Architecture -### Pipeline overview - -Rendering a LaTeX formula goes through four stages: **tokenization** → **parsing** → **layout** → **display list**. The display list is a flat list of drawing commands (glyphs, lines, rectangles, paths) with absolute coordinates; it is consumed either by native UI (iOS/Android/Flutter/RN) or by the server-side rasterizer (tiny-skia → PNG). - ```mermaid flowchart LR - subgraph input[" "] - A[LaTeX string] - end + A["LaTeX string\n(math · \\ce · \\pu)"] subgraph core["Rust core"] - B[ratex-lexer
Tokenization] - C[ratex-parser
AST] - D[ratex-layout
LayoutBox tree] - E[to_display_list
DisplayList] - end - subgraph output[" "] - F[Native render
iOS / Android / Flutter / RN] - G[ratex-render
PNG] - H[ratex-svg
SVG] + B[ratex-lexer] + C[ratex-parser\nmhchem \\ce / \\pu] + D[ratex-layout] + E[DisplayList] end + F[ratex-ffi\niOS · Android · Flutter · RN] + G[ratex-wasm\nWeb / Canvas 2D] + H[ratex-render\nPNG · tiny-skia] + I[ratex-svg\nSVG] A --> B --> C --> D --> E E --> F E --> G E --> H + E --> I ``` -### Data flow (detailed) - -```mermaid -flowchart TB - subgraph types["ratex-types"] - T1[Color, PathCommand
DisplayItem, DisplayList
MathStyle] - end - subgraph font["ratex-font"] - F1[KaTeX font metrics
symbol tables] - end - LEX[ratex-lexer
Token stream] --> PARSE[ratex-parser
ParseNode AST] - F1 -.-> LEX - F1 -.-> PARSE - PARSE --> LAYOUT[ratex-layout
layout → LayoutBox] - F1 -.-> LAYOUT - LAYOUT --> TODISP[to_display_list
LayoutBox → DisplayList] - TODISP --> DL[DisplayList] - T1 -.-> DL - DL --> FFI[ratex-ffi
C ABI] - DL --> RENDER[ratex-render
tiny-skia → PNG] - DL --> WASM[ratex-wasm
JSON for web] - DL --> SVG[ratex-svg
SVG] -``` - -- **ratex-lexer**: Turns the LaTeX source string into a stream of tokens (commands, braces, symbols, etc.). -- **ratex-parser**: Builds a **ParseNode** AST (KaTeX-compatible), with macro expansion and function dispatch. -- **ratex-layout**: Takes the AST and produces a **LayoutBox** tree (horizontal/vertical boxes, glyphs, rules, fractions, etc.) using TeX-style metrics and rules. Then **to_display_list** converts the LayoutBox tree into a flat **DisplayList**. -- **DisplayList**: Serializable list of `DisplayItem`s (GlyphPath, Line, Rect, Path). Consumed by: - - **ratex-ffi**: Exposes the pipeline via C ABI for iOS/Android/RN/Flutter to render natively. - - **ratex-render**: Rasterizes the display list to PNG using tiny-skia (server-side). - - **ratex-wasm**: Exposes the same pipeline to the browser; returns DisplayList as JSON for Canvas 2D (or other) rendering. - - **ratex-svg**: Converts the display list to SVG — either referencing KaTeX webfonts (`` elements) or embedding glyph outlines as `` for a fully self-contained file. - -### Crate roles - -| Crate | Role | -|----------------|------| -| `ratex-types` | Shared types: `Color`, `PathCommand`, `DisplayItem`, `DisplayList`, `MathStyle`. | -| `ratex-font` | Font metrics and symbol tables (KaTeX-compatible fonts). | -| `ratex-lexer` | LaTeX lexer → token stream. | -| `ratex-parser` | LaTeX parser → ParseNode AST (KaTeX-compatible syntax). | -| `ratex-layout` | Math layout engine: AST → LayoutBox tree → **to_display_list** → DisplayList. | -| `ratex-render` | Server-side only: rasterize DisplayList to PNG (tiny-skia + ab_glyph). | -| `ratex-ffi` | C ABI: full pipeline → DisplayList for iOS, Android, RN, Flutter to render natively. | -| `ratex-wasm` | WebAssembly: parse + layout → DisplayList as JSON for browser rendering. | -| `ratex-svg` | SVG export: DisplayList → SVG string (webfont `` or embedded `` outlines). | - -### Text pipeline (summary) +| Crate | Role | +|---|---| +| `ratex-types` | Shared types: `DisplayItem`, `DisplayList`, `Color`, `MathStyle` | +| `ratex-font` | KaTeX-compatible font metrics and symbol tables | +| `ratex-lexer` | LaTeX → token stream | +| `ratex-parser` | Token stream → ParseNode AST; includes mhchem `\ce` / `\pu` | +| `ratex-layout` | AST → LayoutBox tree → DisplayList | +| `ratex-ffi` | C ABI: exposes the full pipeline for native platforms | +| `ratex-wasm` | WASM: pipeline → DisplayList JSON for the browser | +| `ratex-render` | Server-side: DisplayList → PNG (tiny-skia) | +| `ratex-svg` | SVG export: DisplayList → SVG string | -``` -LaTeX formula string - ↓ -ratex-lexer → tokenization - ↓ -ratex-parser → ParseNode AST - ↓ -ratex-layout → LayoutBox tree → to_display_list → DisplayList - ↓ -ratex-ffi → display list (iOS / Android / RN / Flutter → native render) - or -ratex-render → server-side rasterize to PNG (tiny-skia) - or -ratex-wasm → DisplayList JSON (web) - or -ratex-svg → SVG string (vector output) -``` +--- ## Quick start @@ -190,54 +113,48 @@ cargo build --release ```bash echo '\frac{1}{2} + \sqrt{x}' | cargo run --release -p ratex-render -# With custom font and output directories -echo '\sum_{i=1}^n i = \frac{n(n+1)}{2}' | cargo run --release -p ratex-render -- \ - --font-dir /path/to/katex/fonts \ - --output-dir ./out +echo '\ce{H2SO4 + 2NaOH -> Na2SO4 + 2H2O}' | cargo run --release -p ratex-render ``` ### Render to SVG ```bash -# Default: glyphs as elements (requires KaTeX webfonts to display correctly) +# Default: glyphs as elements (correct display requires KaTeX webfonts) echo '\frac{1}{2} + \sqrt{x}' | cargo run --release -p ratex-svg --features cli -# Self-contained SVG: embed glyph outlines as (no external fonts needed) -echo '\sum_{i=1}^n i = \frac{n(n+1)}{2}' | cargo run --release -p ratex-svg --features cli -- \ - --font-dir /path/to/katex/fonts \ - --output-dir ./out +# Standalone: embed glyph outlines as — no external fonts needed +echo '\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}' | \ + cargo run --release -p ratex-svg --features cli -- \ + --font-dir /path/to/katex/fonts --output-dir ./out ``` -The `standalone` feature (enabled by `cli`) embeds glyph outlines from KaTeX TTF files, producing SVG files that render correctly without any CSS or web fonts. +The `standalone` feature (enabled by `cli`) reads KaTeX TTF files and embeds glyph outlines directly into the SVG, producing a fully self-contained file that renders correctly without any CSS or web fonts. -### Use in the browser (WASM) +### Browser (WASM) ```bash npm install ratex-wasm ``` ```html - - - - + ``` -See [`platforms/web/README.md`](platforms/web/README.md) for the full WASM + web-render setup. +See [`platforms/web/README.md`](platforms/web/README.md) for the full setup. -### Platform glue layers (out of the box) +### Platform glue layers | Platform | Docs | -|----------|------| -| iOS | [`platforms/ios/README.md`](platforms/ios/README.md) — XCFramework + Swift/CoreGraphics | -| Android | [`platforms/android/README.md`](platforms/android/README.md) — AAR + Kotlin/Canvas | -| Flutter | [`platforms/flutter/README.md`](platforms/flutter/README.md) — Dart FFI | -| React Native | [`platforms/react-native/README.md`](platforms/react-native/README.md) — Native module + Fabric/Bridge views | -| Web | [`platforms/web/README.md`](platforms/web/README.md) — WASM + Web Component | +|---|---| +| iOS | [`platforms/ios/README.md`](platforms/ios/README.md) | +| Android | [`platforms/android/README.md`](platforms/android/README.md) | +| Flutter | [`platforms/flutter/README.md`](platforms/flutter/README.md) | +| React Native | [`platforms/react-native/README.md`](platforms/react-native/README.md) | +| Web | [`platforms/web/README.md`](platforms/web/README.md) | ### Run tests @@ -247,38 +164,15 @@ cargo test --all --- -## Crate map - -| Crate | Role | -|---|---| -| `ratex-types` | Shared types: `DisplayItem`, `DisplayList`, `Color`, `MathStyle` | -| `ratex-font` | KaTeX-compatible font metrics and symbol tables | -| `ratex-lexer` | LaTeX → token stream | -| `ratex-parser` | Token stream → ParseNode AST (KaTeX-compatible) | -| `ratex-layout` | AST → LayoutBox tree → DisplayList | -| `ratex-ffi` | C ABI: exposes the full pipeline for native platforms | -| `ratex-wasm` | WASM: pipeline → DisplayList JSON for the browser | -| `ratex-render` | Server-side: DisplayList → PNG (tiny-skia) | -| `ratex-svg` | SVG export: DisplayList → SVG string (vector output) | - ---- - -## KaTeX compatibility - -- **Formula support (~99%):** The same LaTeX source is **intended** to work in KaTeX (browser) and RaTeX (native / WASM). Coverage is **wide** for KaTeX-aligned formulas, but **not** every edge case is settled yet — and **none** of that is well summarized by the ~80% golden figure. -- **Golden / visual score (~80% aggregate):** PNG pairs are scored with an **ink-coverage** metric (ink-pixel overlap, recall, aspect and width similarity). This measures **raster** likeness across the suite — **not** how complete formula support is; **many** cases score much higher than the average. See **Project status** above. - ---- - -## Acknowledgement: KaTeX +## Acknowledgements -Ratex owes a great debt to [KaTeX](https://katex.org/). KaTeX is the de facto reference for fast, rigorous LaTeX math on the web; its parser, symbol tables, and layout semantics follow Donald Knuth's TeX standard. We use KaTeX’s font metrics and golden outputs to validate Ratex, and we aim for **syntax and visual compatibility** so that the same LaTeX source can be rendered consistently by KaTeX in the browser and by Ratex on native platforms. We thank the KaTeX project and contributors for their open, well-documented work—without it, this engine would not exist. +RaTeX owes a great debt to [KaTeX](https://katex.org/) — its parser architecture, symbol tables, font metrics, and layout semantics are the foundation of this engine. Chemistry notation (`\ce`, `\pu`) is powered by a Rust port of the [mhchem](https://mhchem.github.io/MathJax-mhchem/) state machine. --- ## Contributing -See [`CONTRIBUTING.md`](CONTRIBUTING.md). To report a security issue privately, see [`SECURITY.md`](SECURITY.md). +See [`CONTRIBUTING.md`](CONTRIBUTING.md). To report a security issue, see [`SECURITY.md`](SECURITY.md). --- diff --git a/README.zh-CN.md b/README.zh-CN.md index b762637a..62336f0b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,27 +2,24 @@ **简体中文** | [English](README.md) -**项目站点:** [erweixin.github.io/RaTeX](https://erweixin.github.io/RaTeX/)(首页与 Math / Chemistry / Physics 公式画廊) - **纯 Rust 实现的 KaTeX 兼容数学渲染引擎 — 无 JavaScript、无 WebView、无 DOM。** -解析 LaTeX,按 TeX 规则排版,在任意平台原生渲染。**胶水层已就绪,各平台开箱即用。** +一个 Rust 核心,一套显示列表,各平台原生渲染。 ``` -\frac{-b \pm \sqrt{b^2 - 4ac}}{2a} → iOS · Android · Flutter · React Native · Web · PNG · SVG +\frac{-b \pm \sqrt{b^2-4ac}}{2a} → iOS · Android · Flutter · React Native · Web · PNG · SVG ``` +**[→ 在线演示](https://erweixin.github.io/RaTeX/demo/live.html)** — 输入 LaTeX,对比 RaTeX vs KaTeX · +**[→ 支持表](https://erweixin.github.io/RaTeX/demo/support-table.html)** — 全量测试公式的 RaTeX vs KaTeX 对比 + --- ## 为什么选 RaTeX? -目前主流的跨平台数学渲染方案都依赖浏览器或 JavaScript 引擎跑 LaTeX,带来的问题是: +目前主流的跨平台数学渲染方案都依赖浏览器或 JavaScript 引擎跑 LaTeX,带来隐藏 WebView 占用 50–150 MB 内存、首屏公式要等 JS 启动、无法保证离线等问题。 -- 隐藏的 WebView 占用 50–150 MB 内存 -- 首屏公式要等 JavaScript 启动 -- 无法保证离线、性能不可预期 - -RaTeX 完全去掉 Web 栈:一个 Rust 核心、一套显示列表,各平台原生渲染。 +RaTeX 完全去掉 Web 栈: | | KaTeX (Web) | MathJax | **RaTeX** | |---|---|---|---| @@ -35,33 +32,19 @@ RaTeX 完全去掉 Web 栈:一个 Rust 核心、一套显示列表,各平台 --- -## 要点 - -- **~99%** 的 KaTeX 公式语法 — **目标**是在该范围内的 LaTeX 与 KaTeX 走同一套解析与排版语义;实现仍在推进,**请在自身业务语料上自行验证**。**请不要把下面的 ~80% 理解成「项目/公式只完成了八成」** —— 它只是 PNG 光栅对比的汇总指标。 -- **~80%** 黄金测试相对 KaTeX 参考 PNG 的**汇总**得分(**泼墨 / 墨迹覆盖度**度量 — **不是**语法覆盖率,也 **不是**引擎或公式支持的完成度;**大量**用例已与 KaTeX **非常接近**;详见下文 **项目现状**) -- **单一显示列表**:扁平的、可序列化的绘图指令,供任意渲染器消费 -- **C ABI**(`ratex-ffi`)供 Swift、Kotlin、Dart、Go、C++ 等 FFI 调用 -- **各平台胶水层**:iOS / Android / Flutter / React Native 绑定就绪,**开箱即用** -- **WASM**(`ratex-wasm`)通过 `` Web 组件在浏览器中即插即用 -- **服务端 PNG** 通过 tiny-skia — 无需浏览器 -- **SVG 导出**(`ratex-svg`)— 矢量输出,支持嵌入字形轮廓(自包含)或引用 KaTeX 网络字体 - -**[→ 在线演示](https://erweixin.github.io/RaTeX/demo/live.html)** — 输入 LaTeX,对比 RaTeX (Rust/WASM) 与 KaTeX -**[→ 支持表](https://erweixin.github.io/RaTeX/demo/support-table.html)** — 916 条测试公式的 RaTeX vs KaTeX 对比 - ---- +## 能渲染什么 -## 项目现状 +**数学公式** — ~99% 的 KaTeX 语法:分数、根号、积分、矩阵、各类环境、伸缩定界符等。 -**公式侧覆盖面较广,但与黄金分不是一回事。** ~**99%** 表示我们**希望**与 KaTeX 公式子集对齐的范围;具体是否满足你的场景,仍建议用真实公式与视觉结果核对。醒目的 **~80%** **不能**概括「公式支持做到哪一步」—— 它只是自动化 **PNG 对 PNG** 黄金测试的汇总结果。 +**化学方程式** — 通过 `\ce` 和 `\pu` 完整支持 mhchem: -**语法覆盖与像素级 1:1 仍是两件事。** 对纳入范围的源码,目标是与 KaTeX 一致的解析与排版路径;**仍有**语法/排版边界在修补,但**不宜**仅凭 ~80% 光栅分推断剩余工作量。 - -**黄金测试**将 RaTeX 与 KaTeX 各自光栅化为 PNG,再用 **泼墨(墨迹覆盖度)** 类指标打分(墨迹像素重叠 / 类 IoU 信号,并配合几何项)。**整套用例的汇总**得分约为 **~80%** —— 这 **不是** 每条 `test_cases` 都已与 KaTeX 像素级 1:1,也 **不是**「只有 80% 的公式能用」,更 **不是**「整个项目只完成了约八成」。**许多**单条用例的相似度已经 **很高**;平均值会被更难的对齐问题拉低(度量、路径、hinting、抗锯齿以及其它光栅/排版细节)。 - -**为何细微间距也会明显拉低泼墨分:** 算法是在 **像素栅格** 上统计「哪些像素算墨迹」,再比较参考图与待测图的墨迹集合重叠(类 IoU)。**两个字母之间**的 **间距或字偶** 只差 **不到一像素**(亚像素级排版舍入),字形轮廓在栅格上的落点就会整体偏移,墨迹掩膜 **对不齐**,重叠度会 **明显下降** —— 肉眼有时仍觉得「几乎一样」。**抗锯齿**会放大这种效应:笔画边缘是半透明像素,轻微错位就会牵动一圈边界像素。因此汇总的 ~80% 是 **偏严的像素级** 指标:里面 **既有** 真实排版差异,也 **包含**「差一点没对齐像素」带来的惩罚,**不能**直接当成「看起来有多像」的百分比。 +```latex +\ce{H2SO4 + 2NaOH -> Na2SO4 + 2H2O} +\ce{Fe^{2+} + 2e- -> Fe} +\pu{1.5e-3 mol//L} +``` -**LaTeX 数学公式本身很复杂**,边界情况叠加很快。**非常需要大家的反馈**:若实际使用中发现渲染不对,请带上 **完整 LaTeX 源码**(如能附截图更好)提 Issue,便于我们固化成回归用例。见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。 +**物理单位** — `\pu` 支持符合 IUPAC 规范的数值+单位表达式。 --- @@ -69,111 +52,51 @@ RaTeX 完全去掉 Web 栈:一个 Rust 核心、一套显示列表,各平台 | 平台 | 方式 | 状态 | |---|---|---| +| **iOS** | XCFramework + Swift / CoreGraphics | 开箱即用 | +| **Android** | JNI + Kotlin + Canvas · AAR | 开箱即用 | +| **Flutter** | Dart FFI + `CustomPainter` | 开箱即用 | +| **React Native** | C ABI Native 模块 · iOS/Android 原生视图 | 开箱即用 | | **Web** | WASM → Canvas 2D · `` Web 组件 | 可用 | | **服务端 / CI** | tiny-skia → PNG 光栅化 | 可用 | -| **iOS** | Swift/ObjC 绑定 C ABI · XCFramework | 开箱即用 | -| **Android** | JNI → Kotlin/Java · AAR | 开箱即用 | -| **React Native** | C ABI Native 模块 · iOS/Android 原生视图 | 开箱即用 | -| **Flutter** | Dart FFI 调用 C ABI | 开箱即用 | - -> Rust 核心与全部平台胶水层(iOS、Android、Flutter、React Native、Web)均已就绪,可直接集成使用。 +| **SVG** | `ratex-svg` → 自包含矢量 SVG 导出 | 可用 | --- ## 架构 -### 流水线概览 - -LaTeX 公式渲染经历四个阶段:**词法** → **解析** → **排版** → **显示列表**。显示列表是一组带绝对坐标的绘图指令(字形、线段、矩形、路径);由原生 UI(iOS/Android/Flutter/RN)或服务端光栅器(tiny-skia → PNG)消费。 - ```mermaid flowchart LR - subgraph input[" "] - A[LaTeX string] - end - subgraph core["Rust core"] - B[ratex-lexer
Tokenization] - C[ratex-parser
AST] - D[ratex-layout
LayoutBox tree] - E[to_display_list
DisplayList] - end - subgraph output[" "] - F[Native render
iOS / Android / Flutter / RN] - G[ratex-render
PNG] - H[ratex-svg
SVG] + A["LaTeX 字符串\n(数学 · \\ce · \\pu)"] + subgraph core["Rust 核心"] + B[ratex-lexer] + C[ratex-parser\nmhchem \\ce / \\pu] + D[ratex-layout] + E[DisplayList] end + F[ratex-ffi\niOS · Android · Flutter · RN] + G[ratex-wasm\nWeb / Canvas 2D] + H[ratex-render\nPNG · tiny-skia] + I[ratex-svg\nSVG] A --> B --> C --> D --> E E --> F E --> G E --> H + E --> I ``` -### 数据流(详) - -```mermaid -flowchart TB - subgraph types["ratex-types"] - T1[Color, PathCommand
DisplayItem, DisplayList
MathStyle] - end - subgraph font["ratex-font"] - F1[KaTeX font metrics
symbol tables] - end - LEX[ratex-lexer
Token stream] --> PARSE[ratex-parser
ParseNode AST] - F1 -.-> LEX - F1 -.-> PARSE - PARSE --> LAYOUT[ratex-layout
layout → LayoutBox] - F1 -.-> LAYOUT - LAYOUT --> TODISP[to_display_list
LayoutBox → DisplayList] - TODISP --> DL[DisplayList] - T1 -.-> DL - DL --> FFI[ratex-ffi
C ABI] - DL --> RENDER[ratex-render
tiny-skia → PNG] - DL --> WASM[ratex-wasm
JSON for web] - DL --> SVG[ratex-svg
SVG] -``` - -- **ratex-lexer**:将 LaTeX 源码转为 token 流(命令、括号、符号等)。 -- **ratex-parser**:构建 **ParseNode** AST(兼容 KaTeX),含宏展开与函数分发。 -- **ratex-layout**:根据 AST 生成 **LayoutBox** 树(横/竖盒、字形、分数线、分式等),使用 TeX 风格度量与规则;**to_display_list** 将 LayoutBox 树转为扁平 **DisplayList**。 -- **DisplayList**:可序列化的 `DisplayItem` 列表(GlyphPath、Line、Rect、Path)。由以下模块消费: - - **ratex-ffi**:通过 C ABI 暴露流水线,供 iOS/Android/RN/Flutter 原生渲染。 - - **ratex-render**:用 tiny-skia 将显示列表光栅化为 PNG(服务端)。 - - **ratex-wasm**:在浏览器中暴露同一流水线;返回 DisplayList 的 JSON,供 Canvas 2D(或其他)渲染。 - - **ratex-svg**:将显示列表转换为 SVG 字符串 —— 支持引用 KaTeX 网络字体(`` 元素)或将字形轮廓嵌入为 ``,生成完全自包含的 SVG 文件。 - -### Crate 职责 - | Crate | 职责 | -|--------|------| -| `ratex-types` | 共享类型:`Color`、`PathCommand`、`DisplayItem`、`DisplayList`、`MathStyle`。 | -| `ratex-font` | 字体度量与符号表(兼容 KaTeX 字体)。 | -| `ratex-lexer` | LaTeX 词法 → token 流。 | -| `ratex-parser` | LaTeX 解析 → ParseNode AST(兼容 KaTeX 语法)。 | -| `ratex-layout` | 数学排版:AST → LayoutBox 树 → **to_display_list** → DisplayList。 | -| `ratex-render` | 仅服务端:DisplayList 光栅化为 PNG(tiny-skia + ab_glyph)。 | -| `ratex-ffi` | C ABI:完整流水线 → DisplayList,供 iOS、Android、RN、Flutter 原生渲染。 | -| `ratex-wasm` | WebAssembly:解析 + 排版 → DisplayList 的 JSON,供浏览器渲染。 | -| `ratex-svg` | SVG 导出:DisplayList → SVG 字符串(网络字体 `` 或嵌入 `` 轮廓)。 | - -### 文本流水线(小结) +|---|---| +| `ratex-types` | 共享类型:`DisplayItem`、`DisplayList`、`Color`、`MathStyle` | +| `ratex-font` | 兼容 KaTeX 的字体度量与符号表 | +| `ratex-lexer` | LaTeX → token 流 | +| `ratex-parser` | token 流 → ParseNode AST;含 mhchem `\ce` / `\pu` | +| `ratex-layout` | AST → LayoutBox 树 → DisplayList | +| `ratex-ffi` | C ABI:向各原生平台暴露完整流水线 | +| `ratex-wasm` | WASM:流水线 → DisplayList JSON(浏览器) | +| `ratex-render` | 服务端:DisplayList → PNG(tiny-skia) | +| `ratex-svg` | SVG 导出:DisplayList → SVG 字符串 | -``` -LaTeX 公式字符串 - ↓ -ratex-lexer → 词法 - ↓ -ratex-parser → ParseNode AST - ↓ -ratex-layout → LayoutBox 树 → to_display_list → DisplayList - ↓ -ratex-ffi → 显示列表(iOS / Android / RN / Flutter → 原生渲染) - 或 -ratex-render → 服务端光栅化为 PNG(tiny-skia) - 或 -ratex-wasm → DisplayList JSON(Web) - 或 -ratex-svg → SVG 字符串(矢量输出) -``` +--- ## 快速开始 @@ -190,10 +113,7 @@ cargo build --release ```bash echo '\frac{1}{2} + \sqrt{x}' | cargo run --release -p ratex-render -# 指定字体与输出目录 -echo '\sum_{i=1}^n i = \frac{n(n+1)}{2}' | cargo run --release -p ratex-render -- \ - --font-dir /path/to/katex/fonts \ - --output-dir ./out +echo '\ce{H2SO4 + 2NaOH -> Na2SO4 + 2H2O}' | cargo run --release -p ratex-render ``` ### 渲染为 SVG @@ -202,13 +122,13 @@ echo '\sum_{i=1}^n i = \frac{n(n+1)}{2}' | cargo run --release -p ratex-render - # 默认模式:字形输出为 元素(正确显示需要 KaTeX 网络字体) echo '\frac{1}{2} + \sqrt{x}' | cargo run --release -p ratex-svg --features cli -# 自包含 SVG:将字形轮廓嵌入为 (无需外部字体) -echo '\sum_{i=1}^n i = \frac{n(n+1)}{2}' | cargo run --release -p ratex-svg --features cli -- \ - --font-dir /path/to/katex/fonts \ - --output-dir ./out +# 自包含模式:将字形轮廓嵌入为 ,无需外部字体 +echo '\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}' | \ + cargo run --release -p ratex-svg --features cli -- \ + --font-dir /path/to/katex/fonts --output-dir ./out ``` -启用 `standalone` feature(即 `cli` 所依赖的特性)后,SVG 导出会从 KaTeX TTF 文件中提取字形轮廓并内嵌,生成无需任何 CSS 或网络字体即可正确渲染的 SVG 文件。 +`standalone` feature(即 `cli` 所依赖的特性)会从 KaTeX TTF 文件中提取字形轮廓并内嵌到 SVG,生成无需任何 CSS 或网络字体即可正确渲染的完全自包含文件。 ### 在浏览器中使用(WASM) @@ -217,27 +137,24 @@ npm install ratex-wasm ``` ```html - - - - + ``` -完整 WASM + Web 渲染说明见 [`platforms/web/README.md`](platforms/web/README.md)。 +完整说明见 [`platforms/web/README.md`](platforms/web/README.md)。 -### 各平台胶水层(开箱即用) +### 各平台胶水层 | 平台 | 文档 | -|------|------| -| iOS | [`platforms/ios/README.md`](platforms/ios/README.md) — XCFramework + Swift/CoreGraphics | -| Android | [`platforms/android/README.md`](platforms/android/README.md) — AAR + Kotlin/Canvas | -| Flutter | [`platforms/flutter/README.md`](platforms/flutter/README.md) — Dart FFI | -| React Native | [`platforms/react-native/README.md`](platforms/react-native/README.md) — Native 模块 + Fabric / Bridge 视图 | -| Web | [`platforms/web/README.md`](platforms/web/README.md) — WASM + Web 组件 | +|---|---| +| iOS | [`platforms/ios/README.md`](platforms/ios/README.md) | +| Android | [`platforms/android/README.md`](platforms/android/README.md) | +| Flutter | [`platforms/flutter/README.md`](platforms/flutter/README.md) | +| React Native | [`platforms/react-native/README.md`](platforms/react-native/README.md) | +| Web | [`platforms/web/README.md`](platforms/web/README.md) | ### 运行测试 @@ -247,38 +164,15 @@ cargo test --all --- -## Crate 一览 - -| Crate | 职责 | -|-------|------| -| `ratex-types` | 共享类型:DisplayItem、DisplayList、Color、MathStyle | -| `ratex-font` | 兼容 KaTeX 的字体度量与符号表 | -| `ratex-lexer` | LaTeX → token 流 | -| `ratex-parser` | token 流 → ParseNode AST(兼容 KaTeX) | -| `ratex-layout` | AST → LayoutBox 树 → DisplayList | -| `ratex-ffi` | C ABI:向各原生平台暴露完整流水线 | -| `ratex-wasm` | WASM:流水线 → DisplayList JSON(浏览器) | -| `ratex-render` | 服务端:DisplayList → PNG(tiny-skia) | -| `ratex-svg` | SVG 导出:DisplayList → SVG 字符串(矢量输出) | - ---- - -## KaTeX 兼容性 - -- **公式支持(~99%):** 同一 LaTeX 源码**设计上**应在浏览器(KaTeX)与设备 / WASM(RaTeX)上均可渲染。与 KaTeX 对齐的公式**覆盖面较广**,但**并非**所有边角都已收敛;**这些都不适合**用 ~80% 黄金分来概括。 -- **黄金 / 视觉得分(汇总 ~80%):** PNG 成对比较采用 **泼墨(墨迹覆盖度)** 指标(墨迹像素重叠、召回、宽高比与宽度相似度等),衡量的是**光栅**层面的整体接近程度 —— **不是**「公式支持完成了百分之几」;**大量**单条用例得分远高于汇总均值。详见上文 **项目现状**。 - ---- - -## 致谢:KaTeX +## 致谢 -RaTeX 深受 [KaTeX](https://katex.org/) 启发。KaTeX 是 Web 上快速、严谨的 LaTeX 数学渲染的事实标准;其解析器、符号表与排版语义遵循 Donald Knuth 的 TeX 规范。我们使用 KaTeX 的字体度量和黄金输出来验证 RaTeX,并追求**语法与视觉兼容**,使同一 LaTeX 源码在浏览器中用 KaTeX、在原生平台用 RaTeX 都能一致渲染。感谢 KaTeX 项目与贡献者的开放与文档 — 没有它,本引擎无法存在。 +RaTeX 深受 [KaTeX](https://katex.org/) 启发——其解析器架构、符号表、字体度量与排版语义是本引擎的基础。化学符号(`\ce`、`\pu`)由 [mhchem](https://mhchem.github.io/MathJax-mhchem/) 状态机的 Rust 移植实现。 --- ## 参与贡献 -见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。若需**私下**报告安全问题,见 [`SECURITY.md`](SECURITY.md)。 +见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。安全问题报告见 [`SECURITY.md`](SECURITY.md)。 --- diff --git a/docs/KATEX_SVG_PATH_PLAN.md b/docs/KATEX_SVG_PATH_PLAN.md deleted file mode 100644 index 75996685..00000000 --- a/docs/KATEX_SVG_PATH_PLAN.md +++ /dev/null @@ -1,148 +0,0 @@ -# Plan: Use KaTeX's Exact SVG Paths for All Stretchy Elements - -## Context - -Many **stretchy** math elements still differ from KaTeX because of SVG path data; the **current** set of worst golden scores (not only arrows) is tracked in [`LOW_SCORE_CASES.md`](./LOW_SCORE_CASES.md). Historically, a large share of low scores were arrow/brace path issues. Currently: -- `katex_stretchy_arrow_path()` only handles 5 arrow types (rightarrow, leftarrow, twoheadright, twoheadleft, xmapsto) -- All other stretchy arrows fall back to `stretchy_accent_path()` in engine.rs, which draws **custom simplified arrows** that look very different from KaTeX -- `horiz_brace_path()` uses custom quadratic curves instead of KaTeX's actual brace paths -- `\vec` uses a custom filled arrow instead of KaTeX's glyph-based path - -**Solution**: Import ALL SVG path data from KaTeX's `path` object and use KaTeX's `katexImagesData` table to generalize the rendering for all stretchy elements. - -## Files to Modify - -1. **`crates/ratex-layout/src/katex_svg.rs`** — Main changes (add paths, add lookup table, generalize function) -2. **`crates/ratex-layout/src/engine.rs`** — Update callers to use generalized function - -## Step 1: Add All Missing KaTeX Path Constants - -Add these path constants to `katex_svg.rs` (copy directly from KaTeX's `path` object in `katex.js:744-822`): - -| Path Name | Used By | -|-----------|---------| -| `doubleleftarrow` | `\xLeftarrow`, `\xLeftrightarrow` | -| `doublerightarrow` | `\xRightarrow`, `\Overrightarrow`, `\xLeftrightarrow` | -| `leftharpoon` | `\xleftharpoonup`, `\overleftharpoon` | -| `leftharpoondown` | `\xleftharpoondown` | -| `leftharpoonplus` | `\xleftrightharpoons` | -| `leftharpoondownplus` | `\xrightleftharpoons` | -| `rightharpoon` | `\xrightharpoonup`, `\overrightharpoon` | -| `rightharpoondown` | `\xrightharpoondown` | -| `rightharpoonplus` | `\xrightleftharpoons` | -| `rightharpoondownplus` | `\xleftrightharpoons` | -| `lefthook` | `\xhookrightarrow` | -| `righthook` | `\xhookleftarrow` | -| `leftbrace` | `\overbrace` | -| `midbrace` | `\overbrace` | -| `rightbrace` | `\overbrace` | -| `leftbraceunder` | `\underbrace` | -| `midbraceunder` | `\underbrace` | -| `rightbraceunder` | `\underbrace` | -| `leftToFrom` | `\xtofrom`, `\xrightleftarrows` | -| `rightToFrom` | `\xtofrom` | -| `baraboveleftarrow` | `\xrightleftarrows` | -| `rightarrowabovebar` | `\xrightleftarrows` | -| `longequal` | `\xlongequal` | -| `leftlinesegment` | `\overlinesegment`, `\underlinesegment` | -| `rightlinesegment` | `\overlinesegment`, `\underlinesegment` | -| `vec` (KaTeX's actual path) | `\vec` | - -## Step 2: Create KaTeX Images Data Lookup Table - -Add a struct and lookup function mirroring KaTeX's `katexImagesData`: - -```rust -struct KatexImageData { - paths: &'static [&'static str], // 1, 2, or 3 path names - min_width: f64, // minimum width in em - vb_height: f64, // viewBox height (÷1000 = em height) - align: Option<&'static str>, // "xMinYMin" or "xMaxYMin" (only for single-path) -} - -fn katex_image_data(label: &str) -> Option -``` - -Key entries (from `katex.js:6875-6921`): -- Single-path: `xrightarrow` → `["rightarrow"], 1.469, 522, "xMaxYMin"` -- Two-path: `xleftrightarrow` → `["leftarrow", "rightarrow"], 1.75, 522` -- Three-path: `overbrace` → `["leftbrace", "midbrace", "rightbrace"], 1.6, 548` - -## Step 3: Generalize the Rendering Function - -Replace `katex_stretchy_arrow_path()` with a general function: - -```rust -pub fn katex_stretchy_path(label: &str, width_em: f64) -> Option> -``` - -Logic: -1. Look up label in `katex_image_data()` -2. Compute `height_em = vb_height / 1000.0` and `s = 1.0 / 1000.0` (since KaTeX scale is 1000:1) -3. Based on number of paths: - - **1 path**: Current logic — scale with `s`, apply x_shift based on alignment, clip to `[0, width_em]` - - **2 paths**: Generalize from current `xmapsto` logic: - - Left path: x_shift = 0, scale, clip to `[0, width_em]` - - Right path: x_shift = `width_em - 400000*s`, scale, clip to `[0, width_em]` - - Combine both - - **3 paths** (braces): Same pattern: - - Left path: x_shift = 0 - - Center path: x_shift = `width_em/2 - 200000*s` - - Right path: x_shift = `width_em - 400000*s` - - Combine all three - -The y-transform remains: `y_new = (y_vb - vb_cy) * s` where `vb_cy = vb_height / 2.0` - -## Step 4: Update Callers in engine.rs - -1. **`stretchy_accent_path()`** (~line 2420): Replace the large match block with a call to `katex_stretchy_path()`. Keep the simple fallback arrow only as a last resort. - -2. **`layout_xarrow()`** (~line 2258): Use `katex_stretchy_path()` instead of `katex_stretchy_arrow_path()`. - -3. **`layout_horiz_brace()`** / `horiz_brace_path()`: Use `katex_stretchy_path("overbrace"/"underbrace", width)` instead of custom quadratic curves. - -4. **`layout_accent()` for `\vec`**: Use KaTeX's actual vec path from the path table. - -5. **Accent paths** (`\overrightarrow`, `\underleftarrow`, etc.): These are also in `katexImagesData`, so the generalized function handles them too. - -## Step 5: Fix ViewBox Height Values - -Update existing entries to match KaTeX's `katexImagesData`: -- rightarrow/leftarrow: 534 → **522** -- LEFTMAPSTO (in xmapsto): 534 → **522** (same viewBox as rightarrow) - -## Affected Low-Score Cases - -This change should fix or significantly improve these categories: - -| Category | Cases | Count | -|----------|-------|-------| -| Stretchy arrows (hook, harpoon, double, etc.) | #4-#12, #25-#31, #36 | ~15 | -| Overbrace/underbrace | #13, #39, #43 | 3 | -| Over/under arrows (accent form) | #18, #19, #22 | 3 | -| Bar accent | #38 | 1 | - -## Verification - -```bash -# Build -cargo build --release -p ratex-render - -# Test individual cases -sed -n '357p' tests/golden/test_cases.txt | \ - cargo run --release -p ratex-render --bin render -- \ - --font-dir tools/lexer_compare/node_modules/katex/dist/fonts \ - --output-dir /tmp/ratex_test/ - -# Run full comparison -./scripts/update_golden_output.sh -python3 tools/golden_compare/compare_golden.py --threshold 0.30 -``` - -## Implementation Order - -1. Add all path constants (mechanical, low risk) -2. Add lookup table (mechanical) -3. Generalize rendering function (core logic, test incrementally) -4. Update engine.rs callers one at a time, verifying each -5. Fix viewBox heights diff --git a/docs/LOW_SCORE_CASES.md b/docs/LOW_SCORE_CASES.md index 9cc03b9d..560c67e3 100644 --- a/docs/LOW_SCORE_CASES.md +++ b/docs/LOW_SCORE_CASES.md @@ -25,14 +25,15 @@ Default **pass threshold** for the script summary is **0.30** (see `--threshold` |-------|---------------------|--------| | `\url` / `\href` | 0831, 0359 | Web/hyperlink semantics; raster baseline may not be comparable. | | `\imageof`, `\origof` | 0375, 0599 | Symbol/glyph or metric mismatch vs KaTeX. | -| `\overbrace` / `\underbrace` + `\text` | 0603, 0810 | Brace + label layout; see also `docs/KATEX_SVG_PATH_PLAN.md`. | +| `\overbrace` / `\underbrace` + `\text` | 0603, 0810 | Brace + label layout. | | `\cancel`, `\bcancel`, `\xcancel` | 0074, 0146, 0890 | Strikeout drawing vs KaTeX. | | `\copyright`, `\textregistered`, `\char"263a` | 0198, 0770, 0157 | Text/compound symbol rendering. | | Arrays / environments | 0061, 0286, 0315, 0037, 0880, 0878, 0879, 0730 | `arraystretch`, `equation`, `gather`, `alignat`, starred matrices, `smallmatrix`. | | Delimiters / `\genfrac` | 0454, 0695, 0662, 0092, 0321 | `\llbracket`, `\rrbracket`, `\rBrace`, `\Biggm\vert`, `\genfrac`. | +| Brace notation | 0138 | `{n\brace k}` glyph/metric mismatch. | | Other | 0699 (`\rule`), 0156 (`\cfrac`), 0263 (`\dotsi`) | Spacing, nested fractions, dots. | -Stretchy-arrow SVG work is still documented in [`KATEX_SVG_PATH_PLAN.md`](./KATEX_SVG_PATH_PLAN.md); **this** list is driven by whatever scores below 0.5 **today**, not only arrows. +This list is driven by whatever scores below 0.5 **today**. --- @@ -85,7 +86,7 @@ Then mark the row below with `[x]`. ## SOP: Fixing low-scoring **stretchy arrow** cases -Applies to `\xrightarrow`, `\xleftarrow`, `\xtwoheadrightarrow`, `\xtwoheadleftarrow`, `\xmapsto`, and similar commands. For **braces**, prefer the brace path plan in [`KATEX_SVG_PATH_PLAN.md`](./KATEX_SVG_PATH_PLAN.md). +Applies to `\xrightarrow`, `\xleftarrow`, `\xtwoheadrightarrow`, `\xtwoheadleftarrow`, `\xmapsto`, and similar commands. For **braces**, the same `katex_stretchy_path()` in `crates/ratex-layout/src/katex_svg.rs` handles `overbrace`/`underbrace`. ### 1. Find KaTeX SVG path data diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index 44846d26..a3b923cd 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -60,6 +60,7 @@ RaTeX/ │ ├── test_case_ce.txt # mhchem \\ce / \\pu examples (fixtures_ce/ refs); parser uses Rust mhchem │ ├── scripts/ +│ ├── set-version.sh # Sync version to all platform manifests │ └── update_golden_output.sh # Renders all test_cases.txt → output/ │ └── demo/ # Web demo + sample apps (web, ios, android, flutter, RN) @@ -85,7 +86,7 @@ members = [ ] [workspace.package] -version = "0.0.10" # bump with VERSION + scripts/set-version.sh; see RELEASING.md +version = "0.0.12" # bump with VERSION + scripts/set-version.sh; see RELEASING.md edition = "2021" authors = ["RaTeX Contributors"] license = "MIT" diff --git a/docs/TECH_SPEC.md b/docs/TECH_SPEC.md deleted file mode 100644 index 027c68c6..00000000 --- a/docs/TECH_SPEC.md +++ /dev/null @@ -1,412 +0,0 @@ -# Cross-Platform LaTeX Math Typesetting Engine — Technical Specification Summary - -> Rust core engine + iOS / Android / Web rendering - ---- - -## Document scope - -This file is a **design and rationale** summary (historical estimates, KaTeX mapping, architecture). For **what exists today**—crate layout, `ratex-ffi` C ABI, npm package **`ratex-wasm`**, golden tests—see [`PROJECT_STRUCTURE.md`](./PROJECT_STRUCTURE.md) and the per-platform READMEs under `platforms/`. - ---- - -## 1. Background and Goals - -### 1.1 Starting Point: SwiftMath - -SwiftMath is a pure Swift implementation of iosMath for rendering LaTeX math on iOS/macOS, using the same typesetting rules as LaTeX. - -**SwiftMath code size estimate:** - -| Module | Estimated LOC | -|--------|----------------| -| LaTeX parser (MTMathAtom, MTMathList, etc.) | ~3,000 | -| Typesetting engine (MTTypesetter, MTMathListDisplay) | ~4,000 | -| Font handling (MTFontManager, MTFont) | ~1,500 | -| UI layer (MTMathUILabel, platform adapters) | ~1,000 | -| Utilities, symbol tables, tests | ~2,000 | -| **Total Swift source** | **~11,000–13,000** | - -### 1.2 Why a Cross-Platform Approach - -- SwiftMath is tightly coupled to UIKit/AppKit and cannot be reused on Android or Web. -- Maintaining separate typesetting logic for iOS, Android, and Web is very costly. -- Rust can target native libraries (iOS/Android) and WebAssembly (Web), making it the only practical shared layer. - ---- - -## 2. Reference Implementation Analysis - -### 2.1 Why KaTeX as Reference - -- KaTeX is the most complete and rigorous open-source LaTeX math parsing implementation. -- Layout strictly follows Donald Knuth's TeX standard—the de facto standard for math typesetting. -- It uses a registry-based function system (`defineFunction`) with clear boundaries per command, which maps well to a modular port. -- It has a full test suite (`ss_data.yaml`) that can validate the Rust implementation directly. - -### 2.2 Core KaTeX Parser Files - -| KaTeX file | Rust module | Estimated LOC | -|------------|-------------|----------------| -| `Lexer.js` | `lexer.rs` | ~400 | -| `Token.js` | `token.rs` | ~100 | -| `MacroExpander.js` | `macro_expander.rs` | ~800 (most complex) | -| `Parser.js` | `parser.rs` | ~1,500 | -| `parseNode.js` | `parse_node.rs` | ~600 | -| `symbols.js` | `symbols.rs` | ~800 | -| `defineFunction.js` + `functions/` | `functions/` (~30 files) | ~3,400 | -| `environments.js` | `environments/` | ~1,200 | -| `macros.js` | `macros.rs` | ~500 | - -### 2.3 Rust Ecosystem (2025) - -> ⚠️ **Conclusion: There is no stable, production-ready pure-Rust LaTeX math parser in the ecosystem. Implementing our own is the right direction.** - -| Project | Status | Notes | -|---------|--------|--------| -| `pulldown-latex` | ⚠️ Largely stalled (no commits since Aug 2024) | ~7 months unmaintained | -| `latex2mathml` | ❌ Abandoned (no updates for 5 years) | Limited functionality | -| `tectonic` | ⚠️ Stalled (~2 years) | Full TeX engine, too large | -| `katex-rs` (xu-cheng) | JS wrapper | Runs KaTeX JS via QuickJS, not a real Rust implementation | - -### 2.4 KaTeX-Specific Extensions (Non-Standard LaTeX) - -These are KaTeX extensions for Web output and **are not standard LaTeX**. RaTeX parses them but does not implement their semantics: - -| Command | Description | RaTeX behavior | -|---------|-------------|-----------------| -| `\htmlClass{class}{content}` | Add class to rendered HTML node | Parse only; expand to second argument "content"; no HTML attributes | -| `\htmlData{key=val,...}{content}` | Add data-* attributes | Same | -| `\htmlId{id}{content}` | Add id | Same | -| `\htmlStyle{css}{content}` | Add inline style | Same | - -Implementation: in `macro_expander`, define them as two-argument macros; expansion is only the second argument (content); the first (attributes/selector) is consumed and discarded. No class/id/style metadata reaches rendering; formula appearance matches "without these commands". - ---- - -## 3. Overall Architecture - -### 3.1 End-to-End Data Flow - -> Principle: Rust does all platform-independent computation (parse + layout); platforms only do the final pixel drawing. - -| Stage | Layer | Input | Output | Notes | -|-------|--------|-------|--------|--------| -| Parse | Rust | LaTeX string | ParseNode AST | Pure math structure, no sizes or coordinates | -| Layout | Rust | ParseNode AST + font metrics | DisplayList | Absolute coordinates and sizes per element | -| Render | Platform-native | DisplayList | Pixels | ~200 lines of platform code | - -``` -┌─────────────────────────────────────────────────────┐ -│ Rust core (cross-platform) │ -│ │ -│ LaTeX string │ -│ ↓ Lexer / MacroExpander │ -│ ParseNode AST │ -│ ↓ Layout engine + static font metrics │ -│ DisplayList (draw commands with absolute coords) │ -└──────────┬──────────────┬─────────────┬─────────────┘ - │ │ │ - Swift FFI Kotlin JNI WASM - │ │ │ - ▼ ▼ ▼ - iOS Android Web - CoreText Canvas Canvas 2D - (~200 LOC) (~200 LOC) (~150 LOC TS) -``` - -### 3.2 Why Layout Lives in Rust - -- Layout depends on font metrics—static tables extracted from OTF fonts and embedded in Rust. -- No platform APIs; naturally cross-platform. -- iOS, Android, and Web share the same layout result for pixel-consistent output. -- Platform renderers stay thin (~200 LOC), translating `DisplayItem` to native calls. - -### 3.3 Rust Output: DisplayList - -DisplayList is the central interface—platform-agnostic draw description: - -```rust -pub enum DisplayItem { - // Draw a glyph at given coordinates - Glyph { - x: f64, y: f64, // baseline (absolute) - font_id: FontId, - font_size: f64, // pt size - glyph_id: u16, // OTF glyph index - color: Color, - }, - // Lines (fraction bar, radical top, underline) - Line { x: f64, y: f64, width: f64, thickness: f64, color: Color }, - // Rectangle (\colorbox background) - Rect { x: f64, y: f64, width: f64, height: f64, color: Color }, - // SVG path (radical corner, braces) - Path { x: f64, y: f64, commands: Vec, color: Color }, -} -``` - -Platform consumption: - -| Platform | Call | Render API | Size | -|----------|------|------------|------| -| iOS / macOS | Swift FFI (C ABI) | CoreText + CoreGraphics | ~200 LOC Swift | -| Android | Kotlin JNI | Canvas + Paint | ~200 LOC Kotlin | -| Web | WebAssembly | Canvas 2D API | ~150 LOC TypeScript | - ---- - -## 4. Parser Layer Design - -### 4.1 Parser Architecture - -KaTeX's three-layer design maps directly to Rust: - -``` -Parser (syntax) - └── MacroExpander / gullet (macro expansion) - ├── stack: Vec // replay stack, supports push_back - └── Lexer / mouth // read raw input when stack is empty -``` - -### 4.2 ParseNode AST (Parser Output) - -ParseNode trees describe **math semantics** only; no sizes, coordinates, or fonts. - -Example: `\frac{a^2 + b}{c}`: - -``` -Genfrac { - numer: OrdGroup [ - Supsub { base: Atom('a', Ord), sup: Atom('2', Ord), sub: None }, - Atom('+', BinaryOp), - Atom('b', Ord), - ], - denom: OrdGroup [ Atom('c', Ord) ], - hasBarLine: true, - style: Auto, -} -``` - -**ParseNode characteristics:** -- ✅ Describes math semantics (fraction, numerator, denominator) -- ✅ Records TeX style (display / text / script / scriptscript) -- ❌ No width/height -- ❌ No x/y coordinates - -### 4.3 Key Challenges - -| Challenge | Description | Approach | -|-----------|-------------|----------| -| MacroExpander borrows | Rust is strict about iterators holding mutable refs | Explicit replay stack with `Vec` | -| `\left` / `\right` pairing | Need stack for nesting | Delimiter stack during parse | -| Matrix/align environments | Columns by `&`, rows by `\\` | Collect rows first, then build Array node | -| Font variant propagation | `\mathbf` must propagate to children | Pass font context down during parse | -| Macro expansion recursion | Macros can define macros | Expansion limit (e.g. 1000) | - ---- - -## 5. Layout Layer Design - -### 5.1 TeX Box Model - -Each math element is a box with three dimensions: - -- **width**: horizontal extent -- **height**: above baseline (ascent) -- **depth**: below baseline (descent) - -Rules: horizontal (HBox) — sum x, max height/depth; vertical (VBox) — sum y, max width. Offsets in `em`, multiplied by actual pt size. - -### 5.2 Font Metrics Strategy - -> 💡 **Recommended start:** Convert KaTeX's `fontMetricsData.js` to Rust static `phf_map`; correctness is already validated by KaTeX. - -| Strategy | Pros | Cons | When to use | -|----------|------|------|-------------| -| A: Fixed Latin Modern Math | Simple, precise, full control | Inflexible font | Initial phase | -| B: Runtime metrics (FFI callback) | Any font | FFI overhead, complexity | Later extension | - -### 5.3 Fraction Layout Example (TeX Rule 15d) - -```rust -fn layout_fraction(node: &GenfracNode, style: Style) -> LayoutBox { - let numer = layout_node(&node.numer, style.numerator_style()); - let denom = layout_node(&node.denom, style.denominator_style()); - - let metrics = font_metrics(style); - - // TeX Rule 15d: different offsets for display vs text - let (num_shift, den_shift) = if style.is_display() { - (metrics.num1, metrics.denom1) - } else { - (metrics.num2, metrics.denom2) - }; - - // Clearance between numerator/denominator and bar - let num_clearance = (num_shift - numer.depth) - - (metrics.axis_height + metrics.rule_thickness / 2.0); - let num_shift = if num_clearance < MIN_CLEARANCE { - num_shift + (MIN_CLEARANCE - num_clearance) - } else { num_shift }; - - // ... similar for denominator - - LayoutBox { - width: numer.width.max(denom.width) + 2.0 * FRAC_PADDING, - height: numer.height + num_shift, - depth: denom.depth + den_shift, - content: BoxContent::Fraction { numer, denom, num_shift, den_shift, .. }, - } -} -``` - ---- - -## 6. Implementation Order - -### Phase 1: Lexer + Token (Week 1) - -- Implement Lexer: LaTeX string → token stream. -- Token kinds: `Char`, `Command`, `LBrace`/`RBrace`, `Caret`, `Underscore`, `Ampersand`, `EOF`. -- Validate against KaTeX `lexer_test.js`. -- Handle edges: `\ ` space, `\\` newline, `%` comment. - -### Phase 2: ParseNode Types + Symbol Table (Week 2) - -- Define ~30 `ParseNode` variants (e.g. `Genfrac`, `Radical`, `Supsub`, `Op`, `Array`). -- Build symbol table with `phf_map` (500+ entries from KaTeX `symbols.js`). -- Static font metrics (from KaTeX `fontMetricsData.js`). - -> 💡 Data definitions only; no parse logic. AI can batch-convert for ~10x speedup. - -### Phase 3: MacroExpander + Parser Core (Weeks 3–5) - -- MacroExpander: token replay stack + macro expansion. -- `parseAtom` core loop: main parsing + superscript/subscript stacking. -- Core commands first: `\frac`, `\sqrt`, `^`, `_`, `{ }`, basic atoms. -- Validate with KaTeX `ss_data.yaml` (e.g. 80% of common cases). -- Then: `\left`/`\right`, environments (matrix/align/cases), color, font commands. - -### Phase 4: Layout Engine (Weeks 6–8) - -- `LayoutBox` (width / height / depth + content). -- Core layout: HBox, VBox, Fraction, Radical, Supsub. -- DisplayList generation (LayoutBox tree → DisplayItem sequence with absolute coords). -- Matrix / braces / stretchy symbols. -- Validate: pixel comparison with KaTeX (see §7). - -### Phase 5: FFI + Platform Renderers (Weeks 9–10) - -- C ABI: `parse_latex()`, `layout()`, `free_display_list()`. -- iOS: Swift wrapper, consume DisplayItem, CoreText / CoreGraphics. -- Android: Kotlin JNI, Canvas / Paint. -- Web: WASM build, TypeScript wrapper, Canvas 2D. - ---- - -## 7. Validation - -### 7.1 Core Idea - -> ⚠️ **Correctness of math typesetting cannot be auto-generated—the only reliable check is pixel-level comparison with KaTeX.** - -### 7.2 Golden Test Framework - -1. Render ~500 test formulas with KaTeX → reference PNGs. -2. Render same formulas with our engine → PNGs. -3. Pixel diff with tolerance (e.g. 1px for antialiasing). -4. Mark regressions and produce visual diff reports. - -**Test sources:** -- KaTeX: `katex/test/screenshotter/ss_data.yaml` (~900+ formulas). -- SwiftMath tests (common iOS cases). -- Custom edge cases: long formulas, nested fractions, multi-line matrices. - -### 7.3 Layered Validation - -| Layer | What | Tool | When | -|-------|------|------|------| -| Lexer | Token sequence matches KaTeX | Unit tests vs KaTeX lexer | After Phase 1 | -| Parser | ParseNode structure matches KaTeX | JSON structure compare | After Phase 3 | -| Layout | LayoutBox dimensions | Compare with KaTeX HTML em values | During Phase 4 | -| Render | Pixel PNG comparison | Golden test | After Phase 4 | -| Integration | Device rendering | Manual screenshot check | After Phase 5 | - -### 7.4 Value of Golden Tests - -About 1 week to set up; ongoing value: - -- Every PR runs it to catch regressions. -- AI-generated code is validated the same way. -- Quantified quality before release (e.g. "98.5% pass, 900 formulas"). - ---- - -## 8. Effort and AI Acceleration - -### 8.1 Effort Estimate - -| Module | LOC | Original estimate | With AI | AI speedup | -|--------|-----|-------------------|---------|------------| -| Symbol table / font metrics | ~1,300 | 3 d | 0.5 d | 10x | -| AST type definitions | ~600 | 2 d | 0.5 d | 5x | -| Lexer | ~400 | 2 d | 0.5 d | 4x | -| MacroExpander | ~800 | 8 d | 4 d | 2x | -| Parser core | ~2,000 | 15 d | 6 d | 2.5x | -| Layout core | ~2,500 | 18 d | 9 d | 2x | -| Matrix / Stretchy | ~1,000 | 8 d | 4 d | 2x | -| DisplayList + FFI | ~600 | 4 d | 1.5 d | 3x | -| Platform renderers (×3) | ~600 | 6 d | 2 d | 3x | -| Tests + golden framework | ~1,500 | 8 d | 4 d | 2x | -| **Total** | **~11,300** | **74 d** | **~32 d** | **~2.3x** | - -### 8.2 Where AI Helps - -| Scenario | AI effect | Reason | -|----------|-----------|--------| -| Symbol/metrics (JS→Rust) | ✅ Very high (10x) | Pure data, fixed patterns | -| ParseNode type definitions | ✅ High (5x) | Flow → Rust enum, mechanical | -| Command handlers | ✅ Good (3x) | Clear logic, reference available | -| MacroExpander borrows | ⚠️ Limited (2x) | AI often produces uncompilable Rust | -| TeX rule correctness | ❌ Not replaceable | Must compare pixels with KaTeX | - -### 8.3 Recommended Workflow - -Use AI for **ongoing pair programming**, not "generate everything at once": - -``` -1. Human: Understand KaTeX module (30 min) -2. AI: Generate Rust (5 min) -3. Human: Fix borrows, compile (30 min) -4. Auto: Run pixel comparison (5 min) -5. Repeat for next module -``` - -> With two people (one on parser, one on layout), total time can be ~**20 days**. - ---- - -## 9. Key Decisions - -| Decision | Choice | Rationale | -|----------|--------|-----------| -| Reference | KaTeX (not MathJax/iosMath) | Clear architecture, modular functions, full tests | -| Rust crates | Implement ourselves | pulldown-latex stalled, others unusable | -| Parser boundary | Rust: parse + layout; Swift: render only | Maximize reuse; thin render layer | -| Where layout runs | Rust (not Swift/Kotlin) | Font metrics are static; no platform API | -| FFI format | C ABI + DisplayList as data | Portable, serializable, debuggable | -| Font metrics | Start with embedded Latin Modern Math | Reuse KaTeX-validated data | -| Validation | Pixel golden test vs KaTeX | Only quantifiable correctness check | - ---- - -## 10. Summary - -**Value proposition:** - -- **One implementation, three platforms:** Rust does parse + layout; iOS / Android / Web need ~200 LOC each for rendering. -- **Evidence-based:** Full reference in KaTeX and validation at each layer. -- **Controlled risk:** Golden tests give quantified quality; AI speeds up mechanical work. -- **Reasonable effort:** ~32 person-days solo, ~20 with two people in parallel. - -> Goal: `\frac{-b \pm \sqrt{b^2-4ac}}{2a}` looks pixel-identical on iOS, Android, and Web. diff --git a/docs/binding-architecture.md b/docs/binding-architecture.md index 5d7cff0c..aad14949 100644 --- a/docs/binding-architecture.md +++ b/docs/binding-architecture.md @@ -8,6 +8,8 @@ Each platform wraps this library with a thin native layer: - **iOS** — Swift Package + CoreGraphics renderer - **Android** — JNI + Kotlin + Android Canvas renderer - **Flutter** — Dart FFI + CustomPainter renderer +- **Web** — WebAssembly + Canvas 2D renderer (TypeScript) +- **React Native** — Native module wrapping iOS/Android views RaTeX 通过 `ratex-ffi` crate 对外暴露一个 C ABI 静态/动态库。 每个平台只需实现一层薄薄的 native wrapper: @@ -15,6 +17,8 @@ RaTeX 通过 `ratex-ffi` crate 对外暴露一个 C ABI 静态/动态库。 - **iOS** — Swift Package + CoreGraphics 渲染 - **Android** — JNI + Kotlin + Android Canvas 渲染 - **Flutter** — Dart FFI + CustomPainter 渲染 +- **Web** — WebAssembly + Canvas 2D 渲染(TypeScript) +- **React Native** — 封装 iOS/Android 原生视图的 Native Module --- @@ -69,31 +73,32 @@ LaTeX string (UTF-8) "height": 1.84, // ascent above baseline (基线以上高度) "depth": 0.21, // descent below baseline (基线以下深度) "items": [ - // ---- GlyphPath: a glyph rendered as outline path commands ---- + // ---- GlyphPath: a glyph rendered via bundled KaTeX font ---- + // Internally-tagged: "type" key is at the same level as other fields. + // "font" is a short ID like "Main-Regular", "Math-Italic", "Size1-Regular"; + // platform renderers prepend "KaTeX_" to map to the font family name. { - "GlyphPath": { - "x": 0.0, "y": 0.0, // position (top-left of glyph, in em) - "scale": 1.0, // uniform scale applied to commands - "font": "KaTeX_Main-Regular", - "char_code": 120, // Unicode code point - "commands": [ - { "MoveTo": { "x": 0.1, "y": 0.7 } }, - { "CubicTo": { "x1": 0.2, "y1": 0.5, "x2": 0.4, "y2": 0.3, "x": 0.6, "y": 0.1 } }, - { "Close": null } - ], - "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } - } + "type": "GlyphPath", + "x": 0.0, "y": 0.0, // position (top-left of glyph bounding box, in em) + "scale": 1.0, // uniform scale applied to path commands + "font": "Main-Regular", // short font ID (NOT "KaTeX_Main-Regular") + "char_code": 120, // Unicode code point + "commands": [ + { "type": "MoveTo", "x": 0.1, "y": 0.7 }, + { "type": "CubicTo", "x1": 0.2, "y1": 0.5, "x2": 0.4, "y2": 0.3, "x": 0.6, "y": 0.1 }, + { "type": "Close" } + ], + "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } }, // ---- Line: horizontal rule (fraction bar, etc.) ---- - { "Line": { "x": 0.1, "y": 0.9, "width": 4.8, "thickness": 0.04, - "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } } }, + { "type": "Line", "x": 0.1, "y": 0.9, "width": 4.8, "thickness": 0.04, + "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } }, // ---- Rect: filled rectangle ---- - { "Rect": { "x": 0.5, "y": 1.0, "width": 2.0, "height": 0.5, - "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } } }, - // ---- Path: arbitrary outline (radical, delimiter) ---- - { "Path": { "x": 0.0, "y": 0.0, "commands": [ /* ... */ ], - "fill": true, - "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } } } + { "type": "Rect", "x": 0.5, "y": 1.0, "width": 2.0, "height": 0.5, + "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } }, + // ---- Path: arbitrary outline (radical corner, stretchy delimiter) ---- + { "type": "Path", "x": 0.0, "y": 0.0, "commands": [ /* ... */ ], + "fill": true, "color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 } } ] } ``` @@ -108,7 +113,9 @@ LaTeX string (UTF-8) ### PathCommand variants / 路径指令 -| Variant | Fields | Meaning | +All commands use internally-tagged JSON: `"type"` is a field alongside the coordinates. + +| `type` value | Additional fields | Meaning | |---------|--------|---------| | `MoveTo` | `x, y` | Move pen to (x, y) | | `LineTo` | `x, y` | Draw line to (x, y) | diff --git a/platforms/android/README.md b/platforms/android/README.md index 2fcce91e..0760ecf0 100644 --- a/platforms/android/README.md +++ b/platforms/android/README.md @@ -1,36 +1,40 @@ # RaTeX — Android -Native LaTeX math on Android (Kotlin + Canvas). AAR includes KaTeX fonts. +Native LaTeX math on Android (Kotlin + Canvas). AAR includes KaTeX fonts. minSdk 21, targetSdk 34. ## Out of the box -1. **Add dependency** — In your app's `build.gradle`: `implementation("io.github.erweixin:ratex-android:0.0.3")` (or from Maven Central / local publish). -2. **Use** — Add `RaTeXView` in your layout and set LaTeX in code; fonts load automatically from `assets/fonts/` on first render. +1. **Add dependency** — In your app's `build.gradle`: + ```kotlin + implementation("io.github.erweixin:ratex-android:0.0.12") + ``` +2. **Use** — Add `RaTeXView` in your layout and set LaTeX in code; fonts load automatically on first render. ```kotlin binding.mathView.latex = """\frac{-b \pm \sqrt{b^2-4ac}}{2a}""" binding.mathView.fontSize = 24f // dp — no manual density conversion needed ``` - **Optional:** To preload fonts at startup, call `RaTeXFontLoader.loadFromAssets(context, "fonts")` in your Application or first screen. - -## Prerequisites - -NDK 26+, Rust + `cargo install cargo-ndk`, and targets: -`rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android` + **Optional:** To preload fonts at startup, call `RaTeXFontLoader.loadFromAssets(context, "fonts")` in your `Application` or first screen. -## Build native lib +## Local development (building from source) -From repo root: `bash platforms/android/build-android.sh` -→ outputs `src/main/jniLibs/{arm64-v8a,armeabi-v7a,x86_64}/libratex_ffi.so` - -## Add to project - -- **Maven:** `implementation("io.github.erweixin:ratex-android:0.0.3")` (after publishing; use `mavenLocal()` / `mavenCentral()` as needed). -- **Module:** include this folder as `:ratex-android` in settings.gradle and `implementation(project(":ratex-android"))` in app. +**Prerequisites:** NDK 26+, Rust + `cargo install cargo-ndk`, and targets: +```bash +rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android +``` -## Fonts +Build from repo root: +```bash +bash platforms/android/build-android.sh +# → src/main/jniLibs/{arm64-v8a,armeabi-v7a,x86_64}/libratex_ffi.so +``` -AAR has KaTeX in `assets/fonts/`. **RaTeXView** loads them automatically on first use. Optional: call `RaTeXFontLoader.loadFromAssets(context, "fonts")` at startup to load earlier. +Include as a local module in `settings.gradle`: +```kotlin +include(":ratex-android") +project(":ratex-android").projectDir = file("path/to/RaTeX/platforms/android") +``` +Then in your app: `implementation(project(":ratex-android"))`. ## Usage @@ -48,9 +52,9 @@ binding.mathView.fontSize = 24f // dp — no manual density conversion needed ### Inline formula — `RaTeXSpan` -`RaTeXSpan` is a `ReplacementSpan` that renders a LaTeX formula inline with surrounding text. The formula baseline is aligned to the text baseline, and the line height expands automatically to accommodate the formula. +`RaTeXSpan` is a `ReplacementSpan` that renders a LaTeX formula inline with surrounding text, baseline-aligned, with line height expanding automatically. -Rendering is async (uses `Dispatchers.IO` internally). Call `RaTeXSpan.create` from a coroutine: +Rendering is async. Call `RaTeXSpan.create` from a coroutine: ```kotlin private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) @@ -58,13 +62,13 @@ private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) fun showInlineFormula(textView: TextView) { scope.launch { val span = RaTeXSpan.create( - context = this@MainActivity, - latex = """\frac{1+\sqrt{5}}{2}""", - fontSize = 18f // dp — converted to px internally + context = this@MainActivity, + latex = """\frac{1+\sqrt{5}}{2}""", + fontSize = 18f // dp ) val ssb = SpannableStringBuilder("黄金比例 φ = ") val start = ssb.length - ssb.append("\u200B") // zero-width placeholder for the span + ssb.append("\u200B") ssb.setSpan(span, start, ssb.length, 0) ssb.append(" ≈ 1.618") textView.text = ssb @@ -72,13 +76,11 @@ fun showInlineFormula(textView: TextView) { } ``` -**Parameters:** - | Parameter | Type | Description | |-----------|------|-------------| | `context` | `Context` | Used for asset access during font loading. | | `latex` | `String` | LaTeX math-mode string (no surrounding `$` or `\[…\]`). | -| `fontSize` | `Float` | Font size in **dp** (density-independent pixels). Converted to px internally. | +| `fontSize` | `Float` | Font size in dp. Converted to px internally. | **Throws** `RaTeXException` if the formula cannot be parsed. @@ -87,19 +89,17 @@ fun showInlineFormula(textView: TextView) { ```kotlin val dl = RaTeXEngine.parse(latex) val renderer = RaTeXRenderer(dl, fontSize) { RaTeXFontLoader.getTypeface(it) } -// draw into any Canvas: renderer.draw(canvas) ``` -## Publish +## Demo -- **Local:** `./gradlew :ratex-android:publishReleasePublicationToMavenLocal` (from a build that includes this module, e.g. `demo/android`). -- **Remote (e.g. GitHub Packages):** set `MAVEN_REPO_URL`, `MAVEN_USER`, `MAVEN_PASSWORD` in gradle.properties, then `./gradlew :ratex-android:publishReleasePublicationToRemote`. -- **Maven Central:** configure [central.sonatype.com](https://central.sonatype.com) + GPG + `SONATYPE_NEXUS_USERNAME` / `SONATYPE_NEXUS_PASSWORD` in gradle.properties; from root use `./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository`. -- **CI:** push tag `v0.0.4` → `.github/workflows/release-android.yml` publishes to Central. Set repo secrets: `SONATYPE_NEXUS_USERNAME`, `SONATYPE_NEXUS_PASSWORD`, `GPG_PRIVATE_KEY`, `GPG_PASSPHRASE`. +From repo root: `bash platforms/android/build-android.sh`, then open `demo/android` in Android Studio and run. -## Demo +**Troubleshooting:** `UnsatisfiedLinkError` → run `build-android.sh`. NDK not found → install NDK 26+ or set `ANDROID_NDK_HOME`. -From root: `bash platforms/android/build-android.sh`, then open `demo/android` in Android Studio and run. +## Publishing (maintainers) -**Troubleshooting:** UnsatisfiedLinkError → run `build-android.sh`. NDK not found → install NDK 26+ or set `ANDROID_NDK_HOME`. +- **Local:** `./gradlew :ratex-android:publishReleasePublicationToMavenLocal` +- **Maven Central:** configure [central.sonatype.com](https://central.sonatype.com) + GPG, set `SONATYPE_NEXUS_USERNAME` / `SONATYPE_NEXUS_PASSWORD` / `GPG_PRIVATE_KEY` / `GPG_PASSPHRASE` in gradle.properties. +- **CI:** push tag `v{VERSION}` → `.github/workflows/release-android.yml` publishes to Central automatically. diff --git a/platforms/android/README.zh-CN.md b/platforms/android/README.zh-CN.md index c75acf85..2bbddb5a 100644 --- a/platforms/android/README.zh-CN.md +++ b/platforms/android/README.zh-CN.md @@ -1,35 +1,45 @@ # RaTeX — Android -Android 上原生渲染 LaTeX 数学公式(Kotlin + Canvas),AAR 内含 KaTeX 字体。 +Android 上原生渲染 LaTeX 数学公式(Kotlin + Canvas),AAR 内含 KaTeX 字体。 minSdk 21,targetSdk 34。 ## 开箱即用 -1. **添加依赖** — 在 app 的 `build.gradle` 中:`implementation("io.github.erweixin:ratex-android:0.0.3")`(或从 Maven Central / 本地发布获取)。 -2. **使用** — 布局里放 `RaTeXView`,代码中设置 LaTeX 与字号(见下方「使用」);字体会在首次渲染时从 `assets/fonts/` 自动加载,无需手动加载。 +1. **添加依赖** — 在 app 的 `build.gradle` 中: + ```kotlin + implementation("io.github.erweixin:ratex-android:0.0.12") + ``` +2. **使用** — 布局里放 `RaTeXView`,代码中设置 LaTeX 与字号;字体会在首次渲染时自动加载,无需手动调用。 + ```kotlin + binding.mathView.latex = """\frac{-b \pm \sqrt{b^2-4ac}}{2a}""" + binding.mathView.fontSize = 24f // dp — 无需手动换算密度 + ``` **可选**:若希望启动时提前加载,可在 Application 或首屏调用 `RaTeXFontLoader.loadFromAssets(context, "fonts")`。 -## 环境 +## 本地开发(从源码构建) -NDK 26+、Rust,执行 `cargo install cargo-ndk` 并安装目标: -`rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android` - -## 构建 so - -在仓库根目录执行:`bash platforms/android/build-android.sh` -会在 `src/main/jniLibs/{arm64-v8a,armeabi-v7a,x86_64}/` 下生成 `libratex_ffi.so`。 - -## 集成方式 - -- **Maven:** `implementation("io.github.erweixin:ratex-android:0.0.3")`(需先发布;配好 `mavenLocal()` / `mavenCentral()` 等)。 -- **模块:** 在 settings.gradle 里 include 本目录为 `:ratex-android`,app 中 `implementation(project(":ratex-android"))`。 +**环境要求**:NDK 26+、Rust,执行 `cargo install cargo-ndk` 并安装目标: +```bash +rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android +``` -## 字体 +在仓库根目录执行: +```bash +bash platforms/android/build-android.sh +# → src/main/jniLibs/{arm64-v8a,armeabi-v7a,x86_64}/libratex_ffi.so +``` -AAR 自带 KaTeX 字体(`assets/fonts/`)。**RaTeXView** 会在首次使用时自动加载,无需手动调用。可选:在启动时调用 `RaTeXFontLoader.loadFromAssets(context, "fonts")` 提前加载。 +在 `settings.gradle` 中将本目录作为本地模块引入: +```kotlin +include(":ratex-android") +project(":ratex-android").projectDir = file("path/to/RaTeX/platforms/android") +``` +然后在 app 中:`implementation(project(":ratex-android"))`。 ## 使用 +### 块级公式 — `RaTeXView` + ```xml @@ -37,23 +47,59 @@ AAR 自带 KaTeX 字体(`assets/fonts/`)。**RaTeXView** 会在首次使用 ```kotlin binding.mathView.latex = """\frac{-b \pm \sqrt{b^2-4ac}}{2a}""" -binding.mathView.fontSize = 24f * resources.displayMetrics.scaledDensity +binding.mathView.fontSize = 24f // dp — 无需手动换算密度 +``` + +### 行内公式 — `RaTeXSpan` + +`RaTeXSpan` 是一个 `ReplacementSpan`,可将 LaTeX 公式内嵌到普通文字中,基线自动对齐,行高自动扩展。 + +渲染为异步,需在协程中调用 `RaTeXSpan.create`: + +```kotlin +private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) + +fun showInlineFormula(textView: TextView) { + scope.launch { + val span = RaTeXSpan.create( + context = this@MainActivity, + latex = """\frac{1+\sqrt{5}}{2}""", + fontSize = 18f // dp + ) + val ssb = SpannableStringBuilder("黄金比例 φ = ") + val start = ssb.length + ssb.append("\u200B") + ssb.setSpan(span, start, ssb.length, 0) + ssb.append(" ≈ 1.618") + textView.text = ssb + } +} ``` -Compose:用 `RaTeXRenderer(dl, fontSize) { RaTeXFontLoader.getTypeface(it) }` 在 `Canvas` 中绘制。 +| 参数 | 类型 | 说明 | +|------|------|------| +| `context` | `Context` | 用于字体加载时访问 Assets。 | +| `latex` | `String` | LaTeX 数学字符串(不含外层 `$` 或 `\[…\]`)。 | +| `fontSize` | `Float` | 字体大小(dp),内部自动转换为 px。 | -## 发布 +若公式无法解析,抛出 `RaTeXException`。 -- **本地:** 在包含本模块的工程(如 `demo/android`)下执行 - `./gradlew :ratex-android:publishReleasePublicationToMavenLocal`。 -- **远程(如 GitHub Packages):** 在 gradle.properties 中配置 `MAVEN_REPO_URL`、`MAVEN_USER`、`MAVEN_PASSWORD`,再执行 - `./gradlew :ratex-android:publishReleasePublicationToRemote`。 -- **Maven Central:** 在 [central.sonatype.com](https://central.sonatype.com) 注册并配置命名空间与 GPG,在 gradle.properties 中配置 `SONATYPE_NEXUS_USERNAME`、`SONATYPE_NEXUS_PASSWORD`;在根工程执行 - `./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository`。 -- **CI:** 推送 tag(如 `v0.0.4`)会触发 `.github/workflows/release-android.yml` 发布到 Central。需在仓库 Secrets 中配置:`SONATYPE_NEXUS_USERNAME`、`SONATYPE_NEXUS_PASSWORD`、`GPG_PRIVATE_KEY`、`GPG_PASSPHRASE`。 +### 底层(Compose / 自定义绘制) + +```kotlin +val dl = RaTeXEngine.parse(latex) +val renderer = RaTeXRenderer(dl, fontSize) { RaTeXFontLoader.getTypeface(it) } +renderer.draw(canvas) +``` ## Demo -根目录执行 `bash platforms/android/build-android.sh`,用 Android Studio 打开 `demo/android` 运行即可。 +在仓库根目录执行 `bash platforms/android/build-android.sh`,用 Android Studio 打开 `demo/android` 运行即可。 + +**常见问题:** `UnsatisfiedLinkError` → 先执行 `build-android.sh`。NDK 未找到 → 安装 NDK 26+ 或设置 `ANDROID_NDK_HOME`。 + +## 发布(维护者) -**常见问题:** UnsatisfiedLinkError → 先执行 `build-android.sh`。NDK 未找到 → 安装 NDK 26+ 或设置 `ANDROID_NDK_HOME`。 +- **本地:** `./gradlew :ratex-android:publishReleasePublicationToMavenLocal` +- **Maven Central:** 配置 [central.sonatype.com](https://central.sonatype.com) 与 GPG,在 gradle.properties 中设置 `SONATYPE_NEXUS_USERNAME` / `SONATYPE_NEXUS_PASSWORD` / `GPG_PRIVATE_KEY` / `GPG_PASSPHRASE`。 +- **CI:** 推送 tag(如 `v{VERSION}`)会触发 `.github/workflows/release-android.yml` 自动发布到 Central。 diff --git a/platforms/flutter/README.md b/platforms/flutter/README.md index 23e667f7..83bc05a4 100644 --- a/platforms/flutter/README.md +++ b/platforms/flutter/README.md @@ -21,8 +21,9 @@ CustomPaint Widget ## Out of the box -1. **Add dependency** — In `pubspec.yaml`: `ratex_flutter: ^0.0.3`, then run `flutter pub get`. No native build required — the published package includes prebuilt Android `.so` and iOS XCFramework. -2. **Use** — Use `RaTeXWidget`: +1. **Add dependency** — add `ratex_flutter: ^0.0.12` to `pubspec.yaml`, then run `flutter pub get`. No native build required — the published package includes prebuilt Android `.so` and iOS XCFramework. +2. **Register fonts** — Flutter does not auto-register plugin fonts for the host app. Copy the [KaTeX font declarations](#font-setup) into your `pubspec.yaml` (see Installation below). +3. **Use** — Use `RaTeXWidget`: ```dart RaTeXWidget( latex: r'\frac{-b \pm \sqrt{b^2-4ac}}{2a}', @@ -41,11 +42,77 @@ Add to your `pubspec.yaml`: ```yaml dependencies: - ratex_flutter: ^0.0.3 + ratex_flutter: ^0.0.12 ``` Then run `flutter pub get`. No native build required — the published package includes prebuilt Android `.so` and iOS `RaTeX.xcframework`. +#### Font setup + +Flutter requires host apps to explicitly declare fonts from plugin packages ([Flutter docs](https://docs.flutter.dev/cookbook/design/package-fonts#from-a-package)). Add the following to the `flutter:` section of your `pubspec.yaml`: + +```yaml +flutter: + fonts: + - family: KaTeX_AMS + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_AMS-Regular.ttf + - family: KaTeX_Caligraphic + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Caligraphic-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_Caligraphic-Bold.ttf + weight: 700 + - family: KaTeX_Fraktur + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Fraktur-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_Fraktur-Bold.ttf + weight: 700 + - family: KaTeX_Main + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Main-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_Main-Bold.ttf + weight: 700 + - asset: packages/ratex_flutter/fonts/KaTeX_Main-Italic.ttf + style: italic + - asset: packages/ratex_flutter/fonts/KaTeX_Main-BoldItalic.ttf + weight: 700 + style: italic + - family: KaTeX_Math + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Math-Italic.ttf + style: italic + - asset: packages/ratex_flutter/fonts/KaTeX_Math-BoldItalic.ttf + weight: 700 + style: italic + - family: KaTeX_SansSerif + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_SansSerif-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_SansSerif-Bold.ttf + weight: 700 + - asset: packages/ratex_flutter/fonts/KaTeX_SansSerif-Italic.ttf + style: italic + - family: KaTeX_Script + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Script-Regular.ttf + - family: KaTeX_Typewriter + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Typewriter-Regular.ttf + - family: KaTeX_Size1 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size1-Regular.ttf + - family: KaTeX_Size2 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size2-Regular.ttf + - family: KaTeX_Size3 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size3-Regular.ttf + - family: KaTeX_Size4 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size4-Regular.ttf +``` + +Without this step, `RaTeXPainter` silently falls back to the system font and formulas render incorrectly. + ### From local path (development) If you use the package from the RaTeX repo: @@ -146,8 +213,6 @@ buildInlineMath( ) ``` -Use `$...$` as delimiters inside the string. The `$` character is split on, so odd-indexed parts are LaTeX, even-indexed are plain text. - ### Async (large formulas) ```dart @@ -210,4 +275,4 @@ To publish an **out-of-the-box** package that works without building native code dart pub publish ``` - **CI**: Pushing a version tag (e.g. `v0.0.4`) runs [release-flutter.yml](https://github.com/erweixin/RaTeX/blob/main/.github/workflows/release-flutter.yml): it builds Android and iOS native libs, injects them into this package, and runs `dart pub publish`. Ensure the tag matches the `version` in `pubspec.yaml`. Repository secret required: `PUB_DEV_TOKEN` (create at https://pub.dev/settings/tokens). + **CI**: Pushing a version tag (e.g. `v{VERSION}`) runs [release-flutter.yml](https://github.com/erweixin/RaTeX/blob/main/.github/workflows/release-flutter.yml): it builds Android and iOS native libs, injects them into this package, and runs `dart pub publish`. Ensure the tag matches the `version` in `pubspec.yaml`. Repository secret required: `PUB_DEV_TOKEN` (create at https://pub.dev/settings/tokens). diff --git a/platforms/flutter/README.zh-CN.md b/platforms/flutter/README.zh-CN.md index 756b7009..043c4c8d 100644 --- a/platforms/flutter/README.zh-CN.md +++ b/platforms/flutter/README.zh-CN.md @@ -1,6 +1,6 @@ # RaTeX — Flutter 集成说明 -通过 Dart FFI 与 CustomPainter 在 Flutter 中原生渲染 LaTeX 数学公式。 +通过 Dart FFI 与 CustomPainter 在 Flutter 中原生渲染 LaTeX 数学公式。 无 WebView、无 JavaScript。 --- @@ -21,8 +21,9 @@ CustomPaint Widget ## 开箱即用 -1. **添加依赖** — 在 `pubspec.yaml` 中:`ratex_flutter: ^0.0.3`,然后执行 `flutter pub get`。无需自行编译原生库,发布包内已含 Android `.so` 与 iOS XCFramework。 -2. **使用** — 直接使用 `RaTeXWidget`: +1. **添加依赖** — 在 `pubspec.yaml` 中:`ratex_flutter: ^0.0.12`,然后执行 `flutter pub get`。无需自行编译原生库,发布包内已含 Android `.so` 与 iOS XCFramework。 +2. **注册字体** — Flutter 不会自动为宿主应用注册插件字体。请将 [KaTeX 字体声明](#字体配置) 复制到你的 `pubspec.yaml`(见下方安装说明)。 +3. **使用** — 直接使用 `RaTeXWidget`: ```dart RaTeXWidget( latex: r'\frac{-b \pm \sqrt{b^2-4ac}}{2a}', @@ -41,11 +42,77 @@ CustomPaint Widget ```yaml dependencies: - ratex_flutter: ^0.0.3 + ratex_flutter: ^0.0.12 ``` 然后执行 `flutter pub get`。无需本地构建 — 已发布包内含预编译的 Android `.so` 与 iOS `RaTeX.xcframework`。 +#### 字体配置 + +Flutter 要求宿主应用显式声明来自插件包的字体([Flutter 文档](https://docs.flutter.dev/cookbook/design/package-fonts#from-a-package))。请将以下内容添加到 `pubspec.yaml` 的 `flutter:` 节: + +```yaml +flutter: + fonts: + - family: KaTeX_AMS + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_AMS-Regular.ttf + - family: KaTeX_Caligraphic + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Caligraphic-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_Caligraphic-Bold.ttf + weight: 700 + - family: KaTeX_Fraktur + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Fraktur-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_Fraktur-Bold.ttf + weight: 700 + - family: KaTeX_Main + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Main-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_Main-Bold.ttf + weight: 700 + - asset: packages/ratex_flutter/fonts/KaTeX_Main-Italic.ttf + style: italic + - asset: packages/ratex_flutter/fonts/KaTeX_Main-BoldItalic.ttf + weight: 700 + style: italic + - family: KaTeX_Math + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Math-Italic.ttf + style: italic + - asset: packages/ratex_flutter/fonts/KaTeX_Math-BoldItalic.ttf + weight: 700 + style: italic + - family: KaTeX_SansSerif + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_SansSerif-Regular.ttf + - asset: packages/ratex_flutter/fonts/KaTeX_SansSerif-Bold.ttf + weight: 700 + - asset: packages/ratex_flutter/fonts/KaTeX_SansSerif-Italic.ttf + style: italic + - family: KaTeX_Script + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Script-Regular.ttf + - family: KaTeX_Typewriter + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Typewriter-Regular.ttf + - family: KaTeX_Size1 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size1-Regular.ttf + - family: KaTeX_Size2 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size2-Regular.ttf + - family: KaTeX_Size3 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size3-Regular.ttf + - family: KaTeX_Size4 + fonts: + - asset: packages/ratex_flutter/fonts/KaTeX_Size4-Regular.ttf +``` + +若跳过此步骤,`RaTeXPainter` 会静默回退到系统字体,公式渲染将出现异常。 + ### 从本地路径(开发) 若从 RaTeX 仓库使用该包: @@ -100,6 +167,51 @@ final painter = RaTeXPainter(displayList: dl, fontSize: 24); CustomPaint(painter: painter, size: Size(painter.widthPx, painter.totalHeightPx)) ``` +### 行内公式(文字 + LaTeX 混排) + +Flutter 推荐使用 `RichText` + `WidgetSpan` 实现行内公式混排。使用 `PlaceholderAlignment.middle` 进行垂直居中对齐: + +```dart +import 'package:flutter/material.dart'; +import 'package:ratex_flutter/ratex_flutter.dart'; + +/// 解析含 `$...$` 行内数学标记的 [text],返回交织了普通 [TextSpan] 与 [WidgetSpan] 的 [RichText]。 +Widget buildInlineMath(String text, {double mathFontSize = 18, TextStyle? textStyle}) { + final style = textStyle ?? + const TextStyle(fontSize: 16, height: 1.8, color: Colors.black87); + + final parts = text.split(r'$'); + final spans = []; + + for (int i = 0; i < parts.length; i++) { + if (parts[i].isEmpty) continue; + if (i.isEven) { + // 普通文本 + spans.add(TextSpan(text: parts[i], style: style)); + } else { + // 行内公式 + spans.add(WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: RaTeXWidget( + latex: parts[i], + fontSize: mathFontSize, + onError: (e) => debugPrint('RaTeX inline error: $e'), + loading: const SizedBox.shrink(), + ), + )); + } + } + + return RichText(text: TextSpan(children: spans)); +} + +// 用法: +buildInlineMath( + r'质能等价关系 $E = mc^2$,其中光速 $c \approx 3\times10^8\ \text{m/s}$。', +) +``` + ### 异步(大公式) ```dart @@ -160,4 +272,4 @@ final dl = await compute( dart pub publish ``` - **CI**:推送版本 tag(如 `v0.0.4`)会触发 [release-flutter.yml](https://github.com/erweixin/RaTeX/blob/main/.github/workflows/release-flutter.yml):构建 Android 与 iOS 原生库、注入本包并执行 `dart pub publish`。请确保 tag 与 `pubspec.yaml` 中的 `version` 一致。仓库需配置 Secret:`PUB_DEV_TOKEN`(在 https://pub.dev/settings/tokens 创建)。 + **CI**:推送版本 tag(如 `v{VERSION}`)会触发 [release-flutter.yml](https://github.com/erweixin/RaTeX/blob/main/.github/workflows/release-flutter.yml):构建 Android 与 iOS 原生库、注入本包并执行 `dart pub publish`。请确保 tag 与 `pubspec.yaml` 中的 `version` 一致。仓库需配置 Secret:`PUB_DEV_TOKEN`(在 https://pub.dev/settings/tokens 创建)。 diff --git a/platforms/ios/README.md b/platforms/ios/README.md index 1672c053..ec14966a 100644 --- a/platforms/ios/README.md +++ b/platforms/ios/README.md @@ -21,15 +21,13 @@ UIView / SwiftUI View ## Out of the box -1. **Add dependency** — In Xcode: **File → Add Package Dependencies**, enter the repo URL `https://github.com/erweixin/RaTeX`, select the `RaTeX` library. +1. **Add dependency** — via Swift Package Manager (see [Installation](#add-to-your-xcode-project) below). 2. **Use** — Use `RaTeXView` or `RaTeXFormula`; fonts load automatically on first render. ```swift // SwiftUI RaTeXFormula(latex: #"\frac{-b \pm \sqrt{b^2-4ac}}{2a}"#, fontSize: 24) ``` - **Optional:** To preload fonts at startup (e.g. to avoid slight delay on first formula), call `RaTeXFontLoader.loadFromPackageBundle()` when the app launches. - -**Local development** (when editing RaTeX source): From the repo root run `bash platforms/ios/build-ios.sh`, then in Xcode **File → Add Package Dependencies → Add Local…** and select the RaTeX repo root. + **Optional:** To preload fonts at startup, call `RaTeXFontLoader.loadFromPackageBundle()` when the app launches. --- @@ -65,11 +63,9 @@ This produces `platforms/ios/RaTeX.xcframework`. ### Option A — Swift Package Manager (recommended) -**Published release** — In Xcode: **File → Add Package Dependencies**, enter -the GitHub repo URL and select the `RaTeX` product. Fonts load automatically on first render; optionally call `RaTeXFontLoader.loadFromPackageBundle()` at startup to load earlier. +In Xcode: **File → Add Package Dependencies**, enter `https://github.com/erweixin/RaTeX` and select the `RaTeX` product. -**Local development** — After building the XCFramework, point Xcode to the -repo root (`File → Add Package Dependencies → Add Local…`). +**Local development** — Run `bash platforms/ios/build-ios.sh` first, then point Xcode to the repo root via **File → Add Package Dependencies → Add Local…**. ### Option B — Manual diff --git a/platforms/ios/README.zh-CN.md b/platforms/ios/README.zh-CN.md index 89881a06..bcc7338e 100644 --- a/platforms/ios/README.zh-CN.md +++ b/platforms/ios/README.zh-CN.md @@ -65,9 +65,9 @@ bash platforms/ios/build-ios.sh ### 方式 A — Swift Package Manager(推荐) -**已发布版本** — 在 Xcode 中:**File → Add Package Dependencies**,输入 GitHub 仓库 URL,选择 `RaTeX` 产品。字体会在首次渲染时自动加载;可选在启动时调用 `RaTeXFontLoader.loadFromPackageBundle()` 提前加载。 +在 Xcode 中:**File → Add Package Dependencies**,输入 `https://github.com/erweixin/RaTeX`,选择 `RaTeX` 产品。 -**本地开发** — 构建好 XCFramework 后,在 Xcode 中指向仓库根目录(**File → Add Package Dependencies → Add Local…**)。 +**本地开发** — 先执行 `bash platforms/ios/build-ios.sh`,然后在 Xcode 中通过 **File → Add Package Dependencies → Add Local…** 指向仓库根目录。 ### 方式 B — 手动集成 @@ -99,7 +99,7 @@ NSLayoutConstraint.activate([ ]) ``` -### SwiftUI +### SwiftUI — 块级公式 ```swift import RaTeX @@ -115,6 +115,37 @@ struct ContentView: View { } ``` +### SwiftUI — 行内公式(文字 + LaTeX 混排) + +使用自定义 `FlowLayout`(SwiftUI `Layout`)将 `Text` 与 `RaTeXFormula` 并排排列,自动换行。基线对齐通过库内置的 `RaTeXFormulaAscentKey` 布局值实现,无需两次测量。 + +```swift +import RaTeX + +struct InlineExample: View { + private let fs: CGFloat = 17 + + var body: some View { + FlowLayout(horizontalSpacing: 3, lineSpacing: 6) { + Text("由勾股定理") + RaTeXFormula(latex: #"a^2 + b^2 = c^2"#, fontSize: fs, onError: { _ in }) + Text("可直接求得斜边长度。") + } + } +} + +// FlowLayout:水平排列子视图,自动换行,基线对齐。 +// 读取 RaTeXFormulaAscentKey 获取公式基线;Text 视图回退到 firstTextBaseline。 +struct FlowLayout: Layout { + var horizontalSpacing: CGFloat = 4 + var lineSpacing: CGFloat = 6 + + // ... 完整实现见 demo/ios +} +``` + +`RaTeXFormulaAscentKey` 是库内置的 `LayoutValueKey`,携带公式的 ascent(基线到顶部的距离),供 `FlowLayout` 在混排时对齐,无需手动计算偏移量。 + ### 底层自定义绘制 ```swift diff --git a/platforms/react-native/README.md b/platforms/react-native/README.md index a9333b35..c8818488 100644 --- a/platforms/react-native/README.md +++ b/platforms/react-native/README.md @@ -113,13 +113,7 @@ Renders a mixed string of plain text and `$...$` LaTeX formulas as a single inli ## Architecture Support -### New Architecture (Fabric) - -The component is defined via **Codegen** (`RaTeXViewNativeComponent.ts`) and uses Fabric's synchronous rendering pipeline. No additional configuration is needed — React Native ≥ 0.73 with `newArchEnabled=true` picks it up automatically. - -### Old Architecture (Bridge) - -A `RaTeXViewManager` (iOS: `RaTeXViewManager.mm`, Android: `RaTeXViewManager.kt`) is provided for projects still on the classic bridge. The same JS component works for both architectures. +Supports both **New Architecture** (Fabric / Codegen) and **Old Architecture** (Bridge) — no configuration needed. React Native ≥ 0.73 with `newArchEnabled=true` uses Fabric automatically; older projects fall back to the Bridge manager. ## Font size note diff --git a/platforms/react-native/README.zh.md b/platforms/react-native/README.zh.md index f23a490b..43717fb3 100644 --- a/platforms/react-native/README.zh.md +++ b/platforms/react-native/README.zh.md @@ -113,13 +113,7 @@ function Paragraph() { ## 架构支持 -### 新架构(Fabric) - -组件通过 **Codegen**(`RaTeXViewNativeComponent.ts`)定义,使用 Fabric 同步渲染管线。React Native ≥ 0.73 开启 `newArchEnabled=true` 后无需任何额外配置。 - -### 旧架构(Bridge) - -为仍在使用旧架构的项目提供了 `RaTeXViewManager`(iOS:`RaTeXViewManager.mm`,Android:`RaTeXViewManager.kt`)。同一个 JS 组件在两种架构下均可使用。 +同时支持**新架构**(Fabric / Codegen)和**旧架构**(Bridge),无需任何配置。React Native ≥ 0.73 开启 `newArchEnabled=true` 后自动使用 Fabric;旧项目则回退到 Bridge 管理器。 ## fontSize 说明 diff --git a/platforms/web/README.md b/platforms/web/README.md index 24cdc6e7..0c94c83a 100644 --- a/platforms/web/README.md +++ b/platforms/web/README.md @@ -7,7 +7,7 @@ Use RaTeX in the browser: Rust compiled to WASM handles parsing and layout; Type ## Architecture - **ratex-wasm** (`crates/ratex-wasm`): Rust → WASM, exports `renderLatex(latex: string) => string` returning DisplayList JSON. -- **web-render** (`src/renderer.ts`): Renders the DisplayList to Canvas 2D (Line / Rect / Path / GlyphPath). **GlyphPath** is currently a placeholder rectangle in layout; the browser draws characters via Canvas `fillText` and a math font using `char_code`, so the page must load a math font (e.g. KaTeX CSS or Latin Modern Math). +- **web-render** (`src/renderer.ts`): Renders the DisplayList to Canvas 2D. `GlyphPath` items are drawn via Canvas `fillText` using `char_code` and the loaded KaTeX font; the page must load a math font (bundled `fonts.css` covers this). - **Entry** (`src/index.ts`): Initializes WASM and provides `renderLatexToCanvas(latex, canvas, options)` for one-step rendering. ## Out of the box @@ -15,13 +15,7 @@ Use RaTeX in the browser: Rust compiled to WASM handles parsing and layout; Type No build required — use the published npm package: 1. **Install** — `npm install ratex-wasm` (or `yarn add ratex-wasm`). -2. **In your page** — Load fonts and register the web component, then use the custom element: - ```html - - - - ``` -3. Supported attributes: `latex`, `font-size`, `padding`, `background-color`; you can also set `element.latex = '...'` via JS. +2. **Use** — see [Drop-in Web Component](#drop-in-web-component-ratex-formula) below. ## Build @@ -84,21 +78,3 @@ const json = renderLatex('x^2 + y^2 = z^2'); const displayList = JSON.parse(json); ``` -### Option 3: Local demo page - -Demo page lives in the repo root under `demo/`. After building the web platform, serve the repo root and open the demo: - -```bash -# From repo root (RaTeX/) -cd platforms/web && npm run build && cd ../.. -npx serve . -# open http://localhost:8080/demo/ -``` - -## Relation to other platforms - -- **ratex-ffi**: C ABI for iOS, Android (JNI), Flutter, React Native. -- **ratex-render**: Native tiny-skia rendering to PNG. -- **ratex-wasm + platforms/web**: Same DisplayList in the browser, drawn by **web-render** on Canvas 2D. - -So **web-render** is the layer that “draws the DisplayList to Canvas 2D in the browser”. diff --git a/platforms/web/README.zh-CN.md b/platforms/web/README.zh-CN.md index 1d036035..ba54ccff 100644 --- a/platforms/web/README.zh-CN.md +++ b/platforms/web/README.zh-CN.md @@ -7,7 +7,7 @@ ## 架构 - **ratex-wasm**(`crates/ratex-wasm`):Rust → WASM,导出 `renderLatex(latex: string) => string`,返回 DisplayList JSON。 -- **web-render**(`src/renderer.ts`):将 DisplayList 绘制到 Canvas 2D(Line / Rect / Path / GlyphPath)。**GlyphPath** 当前在排版中为占位矩形;浏览器通过 Canvas `fillText` 与数学字体的 `char_code` 绘制字符,因此页面需加载数学字体(如 KaTeX CSS 或 Latin Modern Math)。 +- **web-render**(`src/renderer.ts`):将 DisplayList 绘制到 Canvas 2D。`GlyphPath` 条目通过 Canvas `fillText` 与 `char_code` 加载的 KaTeX 字体绘制;页面需加载数学字体(随包附带的 `fonts.css` 已覆盖此需求)。 - **入口**(`src/index.ts`):初始化 WASM,提供 `renderLatexToCanvas(latex, canvas, options)` 一步渲染。 ## 开箱即用 @@ -84,21 +84,3 @@ const json = renderLatex('x^2 + y^2 = z^2'); const displayList = JSON.parse(json); ``` -### 方式三:本地 Demo 页 - -Demo 位于仓库根目录的 `demo/`。构建 web 平台后,在仓库根目录起服务并打开 demo: - -```bash -# 在仓库根目录(RaTeX/) -cd platforms/web && npm run build && cd ../.. -npx serve . -# 打开 http://localhost:8080/demo/ -``` - -## 与其他平台的关系 - -- **ratex-ffi**:iOS、Android(JNI)、Flutter、React Native 的 C ABI。 -- **ratex-render**:原生 tiny-skia 渲染为 PNG。 -- **ratex-wasm + platforms/web**:同一 DisplayList 在浏览器中由 **web-render** 在 Canvas 2D 上绘制。 - -**web-render** 即“在浏览器中将 DisplayList 绘制到 Canvas 2D”的这一层。 diff --git a/scripts/set-version.sh b/scripts/set-version.sh index 68ee775d..ece2356e 100644 --- a/scripts/set-version.sh +++ b/scripts/set-version.sh @@ -1,6 +1,12 @@ #!/usr/bin/env bash -# 将根目录 VERSION 文件中的版本号同步到 Cargo.toml、pubspec.yaml、 -# platforms/web 与 platforms/react-native 的 package.json。 +# 将根目录 VERSION 文件中的版本号同步到所有平台的版本声明文件: +# Cargo.toml, platforms/flutter/pubspec.yaml, +# platforms/flutter/ios/ratex_flutter.podspec, +# platforms/flutter/android/build.gradle, +# platforms/flutter/README.md, +# platforms/android/README.md, +# demo/flutter/pubspec.yaml, +# platforms/web/package.json, platforms/react-native/package.json # platforms/android(Maven Central)在未传 -PlibraryVersion 时从本文件读取版本,见 platforms/android/build.gradle.kts。 # 用法: ./scripts/set-version.sh [版本号] # 若省略版本号,则使用 VERSION 文件内容。 @@ -31,6 +37,21 @@ sed -e '/^[[:space:]]*version = "/s/version = "[^"]*"/version = "'"$VER"'"/' \ # Flutter pubspec sed "s/^version: .*/version: $VER/" platforms/flutter/pubspec.yaml > platforms/flutter/pubspec.yaml.tmp && mv platforms/flutter/pubspec.yaml.tmp platforms/flutter/pubspec.yaml +# Flutter podspec(s.version = 'X.X.X') +sed "s/s\.version *= *'[^']*'/s.version = '$VER'/" platforms/flutter/ios/ratex_flutter.podspec > platforms/flutter/ios/ratex_flutter.podspec.tmp && mv platforms/flutter/ios/ratex_flutter.podspec.tmp platforms/flutter/ios/ratex_flutter.podspec + +# Flutter android/build.gradle(version 'X.X.X') +sed "s/^version '[^']*'/version '$VER'/" platforms/flutter/android/build.gradle > platforms/flutter/android/build.gradle.tmp && mv platforms/flutter/android/build.gradle.tmp platforms/flutter/android/build.gradle + +# Flutter README.md(ratex_flutter: ^X.X.X in code blocks) +sed "s/ratex_flutter: \^[0-9][0-9.]*/ratex_flutter: ^$VER/g" platforms/flutter/README.md > platforms/flutter/README.md.tmp && mv platforms/flutter/README.md.tmp platforms/flutter/README.md + +# Demo app pubspec(ratex_flutter: ^X.X.X) +sed "s/ratex_flutter: \^[0-9][0-9.]*/ratex_flutter: ^$VER/" demo/flutter/pubspec.yaml > demo/flutter/pubspec.yaml.tmp && mv demo/flutter/pubspec.yaml.tmp demo/flutter/pubspec.yaml + +# Android README(Maven artifact version) +sed "s/ratex-android:[0-9][0-9.]*/ratex-android:$VER/g" platforms/android/README.md > platforms/android/README.md.tmp && mv platforms/android/README.md.tmp platforms/android/README.md + # npm:Web(ratex-wasm)与 React Native node -e " const fs = require('fs'); @@ -41,4 +62,4 @@ for (const p of ['platforms/web/package.json', 'platforms/react-native/package.j } " -echo "Done. Updated: Cargo.toml, platforms/flutter/pubspec.yaml, platforms/web/package.json, platforms/react-native/package.json; Android Maven 使用根目录 VERSION" +echo "Done. Updated: Cargo.toml, platforms/flutter/pubspec.yaml, platforms/flutter/ios/ratex_flutter.podspec, platforms/flutter/android/build.gradle, platforms/flutter/README.md, platforms/android/README.md, demo/flutter/pubspec.yaml, platforms/web/package.json, platforms/react-native/package.json; Android Maven 使用根目录 VERSION" diff --git a/tests/golden/output/0192.png b/tests/golden/output/0192.png index 4dbd129c..1ad548b0 100644 Binary files a/tests/golden/output/0192.png and b/tests/golden/output/0192.png differ diff --git a/website/public/demo-support-table.js b/website/public/demo-support-table.js index 1c501301..282ddc38 100644 --- a/website/public/demo-support-table.js +++ b/website/public/demo-support-table.js @@ -31,8 +31,1067 @@ function ratexWasmModuleUrl() { return new URL(rel, pageDir).href; } -const FORMULAS = ["n!", "a\\!b", "\\def\\sqr#1{#1^2} \\sqr{y}", "\\#", "\\%", "\\begin{matrix} a & b \\\\ c & d \\end{matrix}", "\\text{\\'{a}}", "\\text{\\(\\frac a b\\)}", "a\\ b", "\\text{\\\"{a}}", "a\\,\\,{b}", "\\text{\\.{a}}", "a\\:\\:{b}", "\\;\\;{b}", "x_i", "\\text{\\={a}}", "a\\>\\>{b}", "{a}", "\\text{no~no~no~breaks}", "\\text{\\~{a}}", "x^i", "\\text{\\^{a}}", "\\text{\\textdollar}", "\\text{\\`{a}}", "\\text{\\AA}", "\\text{\\aa}", "{a \\above{2pt} b+1}", "\\acute e", "\\text{\\AE}", "\\text{\\ae}", "\\alef", "\\alefsym", "\\aleph", "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", "\\begin{aligned} a&=b+c \\\\ d+e&=f \\end{aligned}", "\\begin{alignat*}{2} 10&x+ &3&y = 2 \\\\ 3&x+&13&y = 4 \\end{alignat*}", "\\begin{alignat*}{2} 10&x+ &3&y = 2 \\\\ 3&x+&13&y = 4 \\end{alignat*}", "\\begin{alignedat}{2} 10&x+ &3&y = 2 \\\\ 3&x+&13&y = 4 \\end{alignedat}", "\\Alpha", "\\alpha", "\\amalg", "\\And", "a_{\\angl n}", "\\angln", "\\allowbreak", "\\angle", "\\approx", "\\approxeq", "\\approxcolon", "\\approxcoloncolon", "\\arccos", "\\arcctg", "\\arcsin", "\\arctan", "\\arctg", "\\arg", "\\argmax", "\\argmin", "\\begin{array}{cc} a & b \\\\ c & d \\end{array}", "\\def\\arraystretch{1.5} \\begin{array}{cc} a & b \\\\ c & d \\end{array}", "\\ast", "\\asymp", "{a \\atop b}", "\\backepsilon", "\\backprime", "\\backsim", "\\backsimeq", "\\backslash", "\\bar{y}", "\\barwedge", "\\Bbb{ABC}", "\\Bbbk", "\\bcancel{5}", "\\because", "\\Beta", "\\beta", "\\beth", "\\between", "\\begingroup a \\endgroup", "\\bf AaBb12", "\\big(\\big)", "\\Big(\\Big)", "\\bigcap", "\\bigcirc", "\\bigcup", "\\bigg(\\bigg)", "\\Bigg(\\Bigg)", "\\biggl(", "\\Biggl(", "\\biggm\\vert", "\\Biggm\\vert", "\\biggr)", "\\Biggr)", "\\bigl(", "\\Bigl(", "\\bigm\\vert", "\\Bigm\\vert", "\\bigodot", "\\bigoplus", "\\bigotimes", "\\bigr)", "\\Bigr)", "\\bigsqcup", "\\bigstar", "\\bigtriangledown", "\\bigtriangleup", "\\biguplus", "\\bigvee", "\\bigwedge", "\\binom n k", "\\blacklozenge", "\\blacksquare", "\\blacktriangle", "\\blacktriangledown", "\\blacktriangleleft", "\\blacktriangleright", "\\bm{AaBb}", "\\begin{Bmatrix} a & b \\\\ c & d \\end{Bmatrix}", "\\begin{Bmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{Bmatrix*}", "\\begin{bmatrix} a & b \\\\ c & d \\end{bmatrix}", "\\begin{bmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{bmatrix*}", "a \\bmod b", "\\bold{AaBb123}", "\\boldsymbol{AaBb}", "\\bot", "\\bowtie", "\\Box", "\\boxdot", "\\boxed{ab}", "\\boxminus", "\\boxplus", "\\boxtimes", "\\Bra{\\psi}", "\\bra{\\psi}", "\\braket{\\phi|\\psi}", "\\Braket{a|\\frac{\\partial^2}{\\partial t^2}|b}", "{n\\brace k}", "{n\\brack k}", "\\breve{eu}", "\\bull", "\\bullet", "\\Bumpeq", "\\bumpeq", "\\cal AaBb123", "\\cancel{5}", "\\Cap", "\\cap", "\\begin{cases} a &\\text{if } b \\\\ c &\\text{if } d \\end{cases}", "\\begin{CD} A @>a>> B \\\\ @VbVV @AAcA \\\\ C @= D \\end{CD}", "\\cdot", "\\cdotp", "\\cdots", "\\ce{C6H5-CHO}", "a\\centerdot b", "\\cfrac{2}{1+\\cfrac{2}{1+\\cfrac{2}{1}}}", "\\char\"263a", "\\check{oe}", "\\ch", "\\checkmark", "\\Chi", "\\chi", "{n+1 \\choose k+2}", "\\circ", "\\circeq", "\\circlearrowleft", "\\circlearrowright", "\\circledast", "\\circledcirc", "\\circleddash", "\\circledR", "\\circledS", "\\clubs", "\\clubsuit", "\\cnums", "\\colon", "\\Colonapprox", "\\colonapprox", "\\coloncolon", "\\coloncolonapprox", "\\coloncolonequals", "\\coloncolonminus", "\\coloncolonsim", "\\Coloneq", "\\coloneq", "\\colonequals", "\\Coloneqq", "\\coloneqq", "\\colonminus", "\\Colonsim", "\\colonsim", "\\color{#0000FF} AaBb123", "\\colorbox{red}{Black on red}", "\\complement", "\\Complex", "\\cong", "\\coprod", "\\copyright", "\\cos", "\\cosec", "\\cosh", "\\cot", "\\cotg", "\\coth", "\\begin{matrix} a & b \\cr c & d \\end{matrix}", "\\csc", "\\ctg", "\\cth", "\\Cup", "\\cup", "\\curlyeqprec", "\\curlyeqsucc", "\\curlyvee", "\\curlywedge", "\\curvearrowleft", "\\curvearrowright", "\\dag", "\\Dagger", "\\dagger", "\\daleth", "\\Darr", "\\dArr", "\\darr", "\\begin{darray}{cc} a & b \\\\ c & d \\end{darray}", "\\dashleftarrow", "\\dashrightarrow", "\\dashv", "\\dbinom n k", "\\dblcolon", "\\begin{dcases} a &\\text{if } b \\\\ c &\\text{if } d \\end{dcases}", "\\ddag", "\\ddagger", "\\ddddot x", "\\dddot x", "\\ddot x", "\\ddots", "\\def\\foo{x^2} \\foo + \\foo", "\\deg", "\\degree", "\\delta", "\\Delta", "\\det", "\\digamma", "\\dfrac{a-1}{b-1}", "\\diagdown", "\\diagup", "\\Diamond", "\\diamond", "\\diamonds", "\\diamondsuit", "\\dim", "\\displaystyle\\sum_0^n", "\\div", "\\divideontimes", "\\dot x", "\\Doteq", "\\doteq", "\\doteqdot", "\\dotplus", "x_1 + \\dots + x_n", "x_1 +\\dotsb + x_n", "x,\\dotsc,y", "\\int_{A_1}\\int_{A_2}\\dotsi", "x_1 x_2 \\dotsm x_n", "\\dotso", "\\doublebarwedge", "\\doublecap", "\\doublecup", "\\Downarrow", "\\downarrow", "\\downdownarrows", "\\downharpoonleft", "\\downharpoonright", "\\begin{drcases} a &\\text{if } b \\\\ c &\\text{if } d \\end{drcases}", "\\def\\foo{a}\\edef\\fcopy{\\foo}\\def\\foo{}\\fcopy", "\\ell", "\\emph{nested \\emph{emphasis}}", "\\empty", "\\emptyset", "a\\enspace b", "\\Epsilon", "\\epsilon", "\\eqcirc", "\\Eqcolon", "\\eqcolon", "\\begin{equation*} a = b + c \\end{equation*}", "\\begin{equation*} a = b + c \\end{equation*}", "\\Eqqcolon", "\\eqqcolon", "\\eqsim", "\\eqslantgtr", "\\eqslantless", "\\equalscolon", "\\equalscoloncolon", "\\equiv", "\\Eta", "\\eta", "\\eth", "\\exist", "\\exists", "\\exp", "\\fallingdotseq", "\\fbox{Hi there!}", "\\fcolorbox{red}{aqua}{A}", "\\Finv", "\\flat", "\\footnotesize footnotesize", "\\forall", "\\frac a b", "\\frak{AaBb}", "\\frown", "\\Game", "\\Gamma", "\\gamma", "\\begin{gather*} a=b \\\\ e=b+c \\end{gather*}", "\\begin{gathered} a=b \\\\ e=b+c \\end{gathered}", "\\gcd", "\\gdef\\sqr#1{#1^2} \\sqr{y} + \\sqr{y}", "\\gdef\\VERT{|} \\braket{\\phi\\VERT\\psi}", "\\ge", "\\genfrac ( ] {2pt}{0}a{a+1}", "\\geq", "\\geqq", "\\geqslant", "\\gets", "\\gg", "\\ggg", "\\gggtr", "\\gimel", "\\global\\def\\add#1#2{#1+#2} \\add 2 3", "\\gnapprox", "\\gneq", "\\gneqq", "\\gnsim", "\\grave{eu}", "a \\gt b", "\\gtrdot", "\\gtrapprox", "\\gtreqless", "\\gtreqqless", "\\gtrless", "\\gtrsim", "\\gvertneqq", "\\text{\\H{a}}", "\\Harr", "\\hArr", "\\harr", "\\hat{\\theta}", "\\hbar", "\\hbox{$x^2$}", "\\begin{matrix} a & b \\\\ \\hdashline c & d \\end{matrix}", "\\hearts", "\\heartsuit", "\\begin{matrix} a & b \\\\ \\hline c & d \\end{matrix}", "\\hom", "\\hookleftarrow", "\\hookrightarrow", "a\\hphantom{bc}d", "\\href{https://katex.org/}{\\KaTeX}", "w\\hskip1em i\\hskip2em d", "\\hslash", "s\\hspace{7ex} k", "\\htmlClass{foo}{x}", "\\htmlData{foo=a, bar=b}{x}", "\\htmlId{bar}{x}", "\\htmlStyle{color: red;}{x}", "\\huge huge", "\\Huge Huge", "\\text{\\i}", "A\\iff B", "\\iiint", "\\iint", "\\Im", "\\image", "\\imageof", "\\imath", "P\\impliedby Q", "P\\implies Q", "\\in", "\\includegraphics[height=0.8em, totalheight=0.9em, width=0.9em, alt=KA logo]{https://cdn.kastatic.org/images/apple-touch-icon-57x57-precomposed.new.png}", "\\inf", "\\infin", "\\infty", "\\injlim", "\\int", "\\intercal", "\\intop", "\\Iota", "\\iota", "\\isin", "{\\it AaBb}", "\\text{\\j}", "\\jmath", "\\Join", "\\Kappa", "\\kappa", "\\KaTeX", "\\ker", "I\\kern-2.5pt R", "\\Ket{\\psi}", "\\ket{\\psi}", "\\Lambda", "\\lambda", "\\land", "\\lang A\\rangle", "\\langle A\\rangle", "\\Larr", "\\lArr", "\\larr", "\\large large", "\\Large Large", "\\LARGE LARGE", "\\LaTeX", "\\lBrace", "\\lbrace", "\\lbrack", "\\lceil", "\\ldotp", "\\ldots", "\\le", "\\leadsto", "\\left\\lbrace \\dfrac ab \\right.", "\\leftarrow", "\\Leftarrow", "\\leftarrowtail", "\\leftharpoondown", "\\leftharpoonup", "\\leftleftarrows", "\\Leftrightarrow", "\\leftrightarrow", "\\leftrightarrows", "\\leftrightharpoons", "\\leftrightsquigarrow", "\\leftthreetimes", "\\leq", "\\leqq", "\\leqslant", "\\lessapprox", "\\lessdot", "\\lesseqgtr", "\\lesseqqgtr", "\\lessgtr", "\\lesssim", "\\lfloor", "\\lg", "\\lgroup", "\\lhd", "\\lim", "\\liminf", "\\lim\\limits_x", "\\limsup", "\\ll", "{=}\\llap{/\\,}", "\\llbracket", "\\llcorner", "\\Lleftarrow", "\\lll", "\\llless", "\\lmoustache", "\\ln", "\\lnapprox", "\\lneq", "\\lneqq", "\\lnot", "\\lnsim", "\\log", "\\Longleftarrow", "\\longleftarrow", "\\Longleftrightarrow", "\\longleftrightarrow", "\\longmapsto", "\\Longrightarrow", "\\longrightarrow", "\\looparrowleft", "\\looparrowright", "\\lor", "\\lozenge", "\\lparen", "\\Lrarr", "\\lrArr", "\\lrarr", "\\lrcorner", "\\lq", "\\Lsh", "\\lt", "\\ltimes", "\\lVert", "\\lvert", "\\lvertneqq", "\\maltese", "\\mapsto", "\\mathbb{AB}", "\\mathbf{AaBb123}", "a\\mathbin{!}b", "\\mathcal{AaBb123}", "a\\mathchoice{\\,}{\\,\\,}{\\,\\,\\,}{\\,\\,\\,\\,}b", "\\sum_{\\mathclap{1\\le i\\le n}} x_{i}", "a + (b\\mathclose\\gt + c", "\\mathellipsis", "\\mathfrak{AaBb}", "ab\\mathinner{\\text{inside}}cd", "\\mathit{AaBb}", "{=}\\mathllap{/\\,}", "\\mathnormal{AaBb}", "\\mathop{\\star}_a^b", "\\{x∈ℝ\\mid x>0\\}", "P\\left(A\\middle\\vert B\\right)", "\\min", "\\minuscolon", "\\minuscoloncolon", "\\minuso", "a\\mkern18mu b", "3\\equiv 5 \\mod 2", "\\models", "\\mp", "a\\mskip{10mu}b", "\\Mu", "\\mu", "\\multimap", "\\N", "\\nabla", "\\natnums", "\\natural", "a\\negmedspace b", "\\ncong", "\\ne", "\\nearrow", "\\neg", "a\\negthickspace b", "a\\negthinspace b", "\\neq", "\\newcommand\\chk{\\checkmark} \\chk", "a\\newline b", "\\nexists", "\\ngeq", "\\ngeqq", "\\ngeqslant", "\\ngtr", "\\ni", "\\nleftarrow", "\\nLeftarrow", "\\nLeftrightarrow", "\\nleftrightarrow", "\\nleq", "\\nleqq", "\\nleqslant", "\\nless", "\\nmid", "a\\nobreakspace b", "\\lim\\nolimits_x", "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", "\\normalsize normalsize", "\\not =", "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", "\\notin", "\\notni", "\\nparallel", "\\nprec", "\\npreceq", "\\nRightarrow", "\\nrightarrow", "\\nshortmid", "\\nshortparallel", "\\nsim", "\\nsubseteq", "\\nsubseteqq", "\\nsucc", "\\nsucceq", "\\nsupseteq", "\\nsupseteqq", "\\ntriangleleft", "\\ntrianglelefteq", "\\ntriangleright", "\\ntrianglerighteq", "\\Nu", "\\nu", "\\nVDash", "\\nVdash", "\\nvDash", "\\nvdash", "\\nwarrow", "\\text{\\O}", "\\text{\\o}", "\\odot", "\\text{\\OE}", "\\text{\\oe}", "\\oiiint", "\\oiint", "\\oint", "\\omega", "\\Omega", "\\Omicron", "\\omicron", "\\ominus", "\\operatorname{asin} x", "\\operatorname*{asin}\\limits_y x", "\\operatornamewithlimits{asin}\\limits_y x", "\\oplus", "\\origof", "\\oslash", "\\otimes", "{a+1 \\over b+2}+c", "\\overbrace{x+⋯+x}^{n\\text{ times}}", "\\overgroup{AB}", "\\overleftarrow{AB}", "\\overleftharpoon{AB}", "\\overleftrightarrow{AB}", "\\overline{\\text{a long argument}}", "\\overlinesegment{AB}", "\\Overrightarrow{AB}", "\\overrightarrow{AB}", "\\overrightharpoon{ac}", "\\overset{!}{=}", "\\owns", "\\text{\\P}", "\\parallel", "\\partial", "\\perp", "\\Gamma^{\\phantom{i}j}_{i\\phantom{j}k}", "\\phase{-78^\\circ}", "\\Phi", "\\phi", "\\Pi", "\\pi", "\\pitchfork", "\\plim", "\\plusmn", "\\pm", "\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}", "\\begin{pmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{pmatrix*}", "\\pmb{\\mu}", "x\\pmod a", "x \\pod a", "\\pounds", "\\Pr", "\\prec", "\\precapprox", "\\preccurlyeq", "\\preceq", "\\precnapprox", "\\precneqq", "\\precnsim", "\\precsim", "\\prime", "\\prod", "\\projlim", "\\propto", "\\providecommand\\greet{\\text{Hello}} \\greet", "\\psi", "\\Psi", "\\pu{123 kJ//mol}", "a\\qquad\\qquad{b}", "a\\quad\\quad{b}", "\\R", "\\text{\\r{a}}", "h\\raisebox{2pt}{ighe}r", "\\langle A\\rang", "\\Rarr", "\\rArr", "\\rarr", "\\ratio", "\\rBrace", "\\rbrace", "\\rbrack", "\\begin{rcases} a &\\text{if } b \\\\ c &\\text{if } d \\end{rcases}", "\\rceil", "\\Re", "\\real", "\\Reals", "\\reals", "\\def\\hail{Hi!} \\renewcommand\\hail{\\text{Ahoy!}} \\hail", "\\restriction", "\\rfloor", "\\rgroup", "\\rhd", "\\Rho", "\\rho", "\\left.\\dfrac a b\\right)", "\\Rightarrow", "\\rightarrow", "\\rightarrowtail", "\\rightharpoondown", "\\rightharpoonup", "\\rightleftarrows", "\\rightleftharpoons", "\\rightrightarrows", "\\rightsquigarrow", "\\rightthreetimes", "\\risingdotseq", "\\rlap{\\,/}{=}", "\\rm AaBb12", "\\rmoustache", "\\rparen", "\\rq", "\\rrbracket", "\\Rrightarrow", "\\Rsh", "\\rtimes", "x\\rule[6pt]{2ex}{1ex}x", "\\rVert", "\\rvert", "\\text{\\S}", "\\scriptscriptstyle \\frac cd", "\\scriptsize scriptsize", "\\frac ab + {\\scriptstyle \\frac cd}", "\\sdot", "\\searrow", "\\sec", "\\text{\\sect}", "\\set{x|x<5}", "\\Set{ x | x<\\frac 1 2 }", "\\setminus", "\\sf AaBb123", "\\sharp", "\\shortmid", "\\shortparallel", "\\Sigma", "\\sigma", "\\sim", "\\simcolon", "\\simcoloncolon", "\\simeq", "\\sin", "\\sinh", "\\sixptsize sixptsize", "\\sh", "\\small small", "\\smallfrown", "\\smallint", "\\begin{smallmatrix} a & b \\\\ c & d \\end{smallmatrix}", "\\smallsetminus", "\\smallsmile", "\\left(x^{\\smash{2}}\\right)", "\\smile", "\\sout{abc}", "a\\space b", "\\spades", "\\spadesuit", "\\sphericalangle", "\\begin{equation} \\begin{split} a &=b+c\\\\ &=e+f \\end{split} \\end{equation}", "\\sqcap", "\\sqcup", "\\square", "\\sqrt[3]{x}", "\\sqsubset", "\\sqsubseteq", "\\sqsupset", "\\sqsupseteq", "\\text{\\ss}", "\\stackrel{!}{=}", "\\star", "\\sub", "\\sube", "\\Subset", "\\subset", "\\subseteq", "\\subseteqq", "\\subsetneq", "\\subsetneqq", "\\sum_{\\substack{0\\le i\\le n \\\\ i\\text{ even}}} x_i", "\\text{\\textgreater}", "\\textit{AaBb}", "\\text{\\textless}", "\\textmd{AaBb123}", "\\textnormal{AB}", "\\text{\\textquotedblleft}", "\\text{\\textquotedblright}", "\\text{\\textquoteleft}", "\\text{\\textquoteright}", "\\text{\\textregistered}", "\\textrm{AaBb123}", "\\textsf{AaBb123}", "\\text{\\textsterling}", "\\textstyle\\sum_0^n", "\\texttt{AaBb123}", "\\text{\\textunderscore}", "\\textup{AaBb123}", "\\tfrac ab", "\\tg", "\\th", "\\therefore", "\\Theta", "\\theta", "\\thetasym", "\\thickapprox", "\\thicksim", "a\\thickspace b", "a\\thinspace b", "\\tilde M", "\\times", "\\tiny tiny", "\\to", "\\top", "\\triangle", "\\triangledown", "\\triangleleft", "\\trianglelefteq", "\\triangleq", "\\triangleright", "\\trianglerighteq", "{\\tt AaBb123}", "\\twoheadleftarrow", "\\twoheadrightarrow", "\\text{\\u{a}}", "\\Uarr", "\\uArr", "\\uarr", "\\ulcorner", "\\underbar{X}", "\\underbrace{x+⋯+x}_{n\\text{ times}}", "\\undergroup{AB}", "\\underleftarrow{AB}", "\\underleftrightarrow{AB}", "\\underrightarrow{AB}", "\\underline{\\text{a long argument}}", "\\underlinesegment{AB}", "\\underset{!}{=}", "\\unlhd", "\\unrhd", "\\Uparrow", "\\uparrow", "\\Updownarrow", "\\updownarrow", "\\upharpoonleft", "\\upharpoonright", "\\uplus", "\\Upsilon", "\\upsilon", "\\upuparrows", "\\urcorner", "\\url{https://katex.org/}", "\\utilde{AB}", "\\text{\\v{a}}", "\\varDelta", "\\varepsilon", "\\varGamma", "\\varinjlim", "\\varkappa", "\\varLambda", "\\varliminf", "\\varlimsup", "\\varnothing", "\\varOmega", "\\varPhi", "\\varphi", "\\varPi", "\\varpi", "\\varprojlim", "\\varpropto", "\\varPsi", "\\varrho", "\\varSigma", "\\varsigma", "\\varsubsetneq", "\\varsubsetneqq", "\\varsupsetneq", "\\varsupsetneqq", "\\varTheta", "\\vartheta", "\\vartriangle", "\\vartriangleleft", "\\vartriangleright", "\\varUpsilon", "\\varXi", "\\mathrel{\\vcentcolon =}", "a+\\left(\\vcenter{\\frac{\\frac a b}c}\\right)", "\\Vdash", "\\vDash", "\\vdash", "\\vdots", "\\vec{F}", "\\vee", "\\veebar", "\\verb!\\frac a b!", "\\Vert", "\\vert", "\\begin{Vmatrix} a & b \\\\ c & d \\end{Vmatrix}", "\\begin{Vmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{Vmatrix*}", "\\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix}", "\\begin{vmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{vmatrix*}", "\\overline{\\vphantom{M}a}", "\\Vvdash", "\\wedge", "\\weierp", "\\widecheck{AB}", "\\widehat{AB}", "\\widetilde{AB}", "\\wp", "\\wr", "\\xcancel{ABC}", "\\def\\foo{a}\\xdef\\fcopy{\\foo}\\def\\foo{}\\fcopy", "\\Xi", "\\xi", "\\xhookleftarrow{abc}", "\\xhookrightarrow{abc}", "\\xLeftarrow{abc}", "\\xleftarrow{abc}", "\\xleftharpoondown{abc}", "\\xleftharpoonup{abc}", "\\xLeftrightarrow{abc}", "\\xleftrightarrow{abc}", "\\xleftrightharpoons{abc}", "\\xlongequal{abc}", "\\xmapsto{abc}", "\\xRightarrow{abc}", "\\xrightarrow{abc}", "\\xrightharpoondown{abc}", "\\xrightharpoonup{abc}", "\\xrightleftharpoons{abc}", "\\xtofrom{abc}", "\\xtwoheadleftarrow{abc}", "\\xtwoheadrightarrow{abc}", "\\yen", "\\Z", "\\Zeta", "\\zeta", "\\hat{M}", "\\hat{b}", "\\hat{\\aa}", "\\hat{o}", "\\tilde{M}", "\\left\\{\\begin{array}{l} \\nabla\\cdot\\vec{D} = \\rho \\\\ \\nabla\\cdot\\vec{B} = 0 \\\\ \\nabla\\times\\vec{E} = -\\frac{\\partial\\vec{B}}{\\partial t} \\\\ \\nabla\\times\\vec{H} = \\vec{J}_f + \\frac{\\partial\\vec{D}}{\\partial t} \\end{array}\\right.", "酸 + 碱 \\rightarrow 盐 + 水"]; -const GOLDEN_SCORES = {"1":0.889,"2":0.914,"3":0.756,"4":0.898,"5":0.875,"6":0.915,"7":0.675,"8":0.853,"9":0.909,"10":0.643,"11":0.905,"12":0.643,"13":0.911,"14":0.906,"15":0.88,"16":0.911,"17":0.911,"18":0.898,"19":0.866,"20":0.65,"21":0.915,"22":0.69,"23":0.899,"24":0.69,"25":0.756,"26":0.732,"27":0.879,"28":0.691,"29":0.884,"30":0.922,"31":0.926,"32":0.926,"33":0.926,"34":0.683,"35":0.605,"36":0.605,"37":0.476,"38":0.558,"39":0.558,"40":0.887,"41":0.876,"42":0.907,"43":0.904,"44":0.825,"45":0.855,"46":0.8,"47":0.894,"48":0.884,"49":0.921,"50":0.611,"51":0.578,"52":0.903,"53":0.905,"54":0.825,"55":0.781,"56":0.906,"57":0.884,"58":0.811,"59":0.908,"60":0.904,"61":0.387,"62":0.911,"63":0.886,"64":0.902,"65":0.818,"66":0.9,"67":0.926,"68":0.878,"69":0.919,"70":0.77,"71":0.885,"72":0.792,"73":0.915,"74":0.782,"75":0.771,"76":0.9,"77":0.827,"78":0.892,"79":0.884,"80":0.898,"81":0.922,"82":0.935,"83":0.918,"84":0.928,"85":0.889,"86":0.933,"87":0.939,"88":0.839,"89":0.923,"90":0.847,"91":0.689,"92":0.473,"93":0.935,"94":0.87,"95":0.93,"96":0.943,"97":0.532,"98":0.534,"99":0.951,"100":0.954,"101":0.95,"102":0.911,"103":0.875,"104":0.929,"105":0.932,"106":0.796,"107":0.919,"108":0.935,"109":0.937,"110":0.935,"111":0.53,"112":0.956,"113":0.954,"114":0.974,"115":0.953,"116":0.939,"117":0.931,"118":0.853,"119":0.902,"120":0.525,"121":0.901,"122":0.509,"123":0.909,"124":0.743,"125":0.853,"126":0.909,"127":0.901,"128":0.875,"129":0.876,"130":0.783,"131":0.88,"132":0.883,"133":0.882,"134":0.832,"135":0.832,"136":0.852,"137":0.521,"138":0.434,"139":0.525,"140":0.667,"141":0.964,"142":0.964,"143":0.932,"144":0.935,"145":0.771,"146":0.792,"147":0.919,"148":0.936,"149":0.835,"151":0.78,"152":0.78,"153":0.924,"155":0.914,"156":0.476,"157":0.39,"158":0.549,"159":0.892,"160":0.927,"161":0.825,"162":0.846,"163":0.893,"164":0.913,"165":0.939,"166":0.821,"167":0.822,"168":0.889,"169":0.88,"170":0.873,"171":0.916,"172":0.906,"173":0.956,"174":0.956,"175":0.869,"176":0.794,"177":0.601,"178":0.627,"179":0.822,"180":0.601,"181":0.592,"182":0.684,"183":0.68,"184":0.684,"185":0.634,"186":0.547,"187":0.592,"188":0.547,"189":0.634,"190":0.68,"191":0.648,"192":0.788,"193":0.98,"194":0.946,"195":0.869,"196":0.876,"197":0.975,"198":0.741,"199":0.914,"200":0.906,"201":0.895,"202":0.851,"203":0.911,"204":0.914,"205":0.915,"206":0.91,"207":0.902,"208":0.912,"209":0.92,"210":0.938,"211":0.83,"212":0.861,"213":0.908,"214":0.909,"215":0.701,"216":0.758,"217":0.923,"218":0.924,"219":0.923,"220":0.903,"221":0.88,"222":0.88,"223":0.829,"224":0.904,"225":0.93,"226":0.92,"227":0.88,"228":0.53,"229":0.822,"230":0.835,"231":0.924,"232":0.924,"233":0.579,"234":0.619,"235":0.644,"236":0.832,"237":0.672,"238":0.884,"239":0.881,"240":0.911,"241":0.683,"242":0.899,"243":0.903,"244":0.862,"245":0.902,"246":0.808,"247":0.862,"248":0.85,"249":0.79,"250":0.79,"251":0.909,"252":0.667,"253":0.856,"254":0.847,"255":0.643,"256":0.944,"257":0.911,"258":0.944,"259":0.961,"260":0.787,"261":0.787,"262":0.608,"263":0.479,"264":0.674,"265":0.916,"266":0.856,"267":0.919,"268":0.92,"269":0.88,"270":0.829,"271":0.925,"272":0.912,"273":0.891,"274":0.835,"275":0.898,"276":0.886,"277":0.643,"278":0.917,"279":0.917,"280":0.91,"281":0.81,"282":0.905,"283":0.882,"284":0.646,"285":0.61,"286":0.397,"287":0.821,"288":0.577,"289":0.538,"290":0.888,"291":0.906,"292":0.915,"293":0.538,"294":0.577,"295":0.874,"296":0.822,"297":0.895,"298":0.899,"299":0.884,"300":0.884,"301":0.857,"302":0.844,"303":0.886,"304":0.535,"305":0.816,"306":0.943,"307":0.579,"308":0.848,"309":0.849,"310":0.854,"311":0.879,"312":0.861,"313":0.623,"314":0.889,"315":0.467,"316":0.84,"317":0.918,"318":0.599,"319":0.852,"320":0.926,"321":0.497,"322":0.926,"323":0.865,"324":0.91,"325":0.917,"326":0.902,"327":0.881,"328":0.881,"329":0.897,"330":0.918,"331":0.891,"332":0.927,"333":0.889,"334":0.859,"335":0.612,"336":0.876,"337":0.818,"338":0.898,"339":0.92,"340":0.901,"341":0.835,"342":0.838,"343":0.894,"344":0.671,"345":0.848,"346":0.848,"347":0.873,"348":0.715,"349":0.934,"350":0.89,"351":0.782,"352":0.879,"353":0.879,"354":0.759,"355":0.847,"356":0.878,"357":0.854,"358":0.905,"359":0.138,"360":0.771,"361":0.93,"362":0.903,"363":0.885,"364":0.885,"365":0.885,"366":0.885,"367":0.91,"368":0.628,"369":0.883,"370":0.892,"371":0.951,"372":0.958,"373":0.854,"374":0.854,"375":0.163,"376":0.788,"377":0.739,"378":0.743,"379":0.825,"381":0.898,"382":0.85,"383":0.85,"384":0.861,"385":0.944,"386":0.956,"387":0.944,"388":0.892,"389":0.901,"390":0.825,"391":0.911,"392":0.892,"393":0.885,"394":0.901,"395":0.827,"396":0.884,"397":0.668,"398":0.879,"399":0.668,"400":0.826,"401":0.826,"402":0.646,"403":0.917,"404":0.908,"405":0.883,"406":0.883,"407":0.837,"408":0.837,"409":0.917,"410":0.675,"411":0.843,"412":0.834,"413":0.67,"414":0.655,"415":0.94,"416":0.79,"417":0.932,"418":0.775,"419":0.916,"420":0.928,"421":0.808,"422":0.625,"423":0.917,"424":0.837,"425":0.837,"426":0.815,"427":0.88,"428":0.863,"429":0.848,"430":0.873,"431":0.918,"432":0.918,"433":0.788,"434":0.855,"435":0.928,"436":0.874,"437":0.913,"438":0.903,"439":0.821,"440":0.922,"441":0.906,"442":0.825,"443":0.847,"444":0.905,"445":0.894,"446":0.941,"447":0.852,"448":0.915,"449":0.812,"450":0.787,"451":0.834,"452":0.819,"453":0.924,"454":0.813,"455":0.928,"456":0.912,"457":0.887,"458":0.887,"459":0.919,"460":0.902,"461":0.894,"462":0.93,"463":0.894,"464":0.92,"465":0.868,"466":0.888,"467":0.907,"468":0.904,"469":0.888,"470":0.841,"471":0.838,"472":0.886,"473":0.852,"474":0.891,"475":0.892,"476":0.914,"477":0.862,"478":0.814,"479":0.848,"480":0.848,"481":0.873,"482":0.928,"483":0.92,"484":0.841,"485":0.823,"486":0.829,"487":0.834,"488":0.737,"489":0.899,"490":0.926,"491":0.856,"492":0.847,"493":0.743,"494":0.92,"495":0.771,"496":0.91,"497":0.562,"498":0.752,"499":0.916,"500":0.854,"501":0.901,"502":0.911,"503":0.924,"504":0.868,"505":0.861,"506":0.699,"507":0.572,"508":0.829,"509":0.61,"510":0.646,"511":0.542,"512":0.91,"513":0.884,"514":0.895,"515":0.912,"516":0.908,"517":0.899,"518":0.829,"519":0.811,"520":0.731,"521":0.945,"522":0.731,"523":0.897,"524":0.908,"525":0.924,"526":0.912,"527":0.903,"528":0.92,"529":0.915,"530":0.914,"531":0.912,"532":0.927,"533":0.748,"534":0.864,"535":0.925,"536":0.917,"537":0.914,"538":0.855,"539":0.807,"540":0.82,"541":0.911,"542":0.891,"543":0.781,"544":0.927,"545":0.919,"546":0.914,"547":0.859,"548":0.819,"549":0.909,"550":0.756,"551":0.621,"552":0.791,"553":0.912,"554":0.621,"555":0.92,"556":0.93,"557":0.861,"558":0.853,"559":0.922,"560":0.916,"561":0.769,"562":0.891,"563":0.901,"564":0.902,"565":0.921,"566":0.885,"567":0.86,"568":0.92,"569":0.924,"570":0.881,"571":0.856,"572":0.927,"573":0.871,"574":0.927,"575":0.801,"576":0.854,"577":0.857,"578":0.846,"579":0.857,"580":0.84,"581":0.844,"582":0.897,"583":0.877,"584":0.886,"585":0.88,"586":0.925,"587":0.913,"588":0.852,"589":0.939,"590":0.897,"591":0.657,"592":0.855,"593":0.874,"594":0.894,"595":0.765,"596":0.6,"597":0.6,"598":0.895,"599":0.163,"600":0.885,"601":0.887,"602":0.773,"603":0.654,"604":0.658,"605":0.622,"606":0.615,"607":0.629,"608":0.654,"609":0.617,"610":0.782,"611":0.618,"612":0.572,"613":0.717,"614":0.807,"615":0.905,"616":0.834,"617":0.91,"618":0.909,"619":0.714,"620":0.705,"621":0.815,"622":0.89,"623":0.667,"624":0.879,"625":0.919,"626":0.856,"627":0.813,"628":0.813,"629":0.926,"630":0.511,"631":0.842,"632":0.717,"633":0.762,"634":0.886,"635":0.893,"636":0.814,"637":0.902,"638":0.921,"639":0.935,"640":0.855,"641":0.892,"642":0.86,"643":0.843,"644":0.896,"645":0.976,"646":0.796,"647":0.867,"648":0.874,"649":0.874,"650":0.708,"652":0.91,"653":0.909,"654":0.903,"655":0.732,"656":0.887,"657":0.883,"658":0.823,"659":0.823,"660":0.862,"661":0.835,"662":0.418,"663":0.936,"664":0.823,"665":0.835,"666":0.873,"667":0.918,"668":0.918,"669":0.903,"670":0.903,"671":0.884,"672":0.877,"673":0.851,"674":0.917,"675":0.859,"676":0.903,"677":0.834,"678":0.625,"679":0.823,"680":0.862,"681":0.819,"682":0.819,"683":0.881,"684":0.92,"685":0.9,"686":0.802,"687":0.808,"688":0.85,"689":0.845,"690":0.896,"691":0.925,"692":0.918,"693":0.912,"694":0.872,"695":0.85,"696":0.884,"697":0.855,"698":0.831,"699":0.378,"700":0.834,"701":0.737,"702":0.838,"703":0.836,"704":0.898,"705":0.652,"706":0.78,"707":0.871,"708":0.907,"709":0.838,"710":0.763,"711":0.658,"712":0.919,"713":0.916,"714":0.883,"715":0.71,"716":0.903,"717":0.613,"718":0.919,"719":0.888,"720":0.645,"721":0.67,"722":0.878,"723":0.913,"724":0.885,"725":0.835,"726":0.884,"727":0.87,"728":0.748,"729":0.929,"730":0.414,"731":0.78,"732":0.83,"733":0.754,"734":0.92,"735":0.596,"736":0.909,"737":0.955,"738":0.955,"739":0.906,"740":0.606,"741":0.832,"742":0.808,"743":0.875,"744":0.703,"745":0.895,"746":0.943,"747":0.883,"748":0.942,"749":0.902,"750":0.717,"751":0.886,"752":0.824,"753":0.937,"754":0.891,"755":0.824,"756":0.937,"757":0.92,"758":0.932,"759":0.899,"760":0.792,"761":0.808,"762":0.705,"763":0.823,"764":0.92,"765":0.854,"766":0.879,"767":0.873,"768":0.92,"769":0.909,"770":0.375,"771":0.92,"772":0.63,"773":0.886,"774":0.807,"775":0.606,"776":0.678,"777":0.92,"778":0.853,"779":0.894,"780":0.888,"781":0.779,"782":0.768,"783":0.896,"784":0.894,"785":0.901,"786":0.901,"787":0.908,"788":0.91,"789":0.639,"790":0.83,"791":0.827,"792":0.862,"793":0.908,"794":0.919,"795":0.902,"796":0.873,"797":0.925,"798":0.853,"799":0.873,"800":0.934,"801":0.918,"802":0.873,"803":0.807,"804":0.721,"805":0.877,"806":0.877,"807":0.82,"808":0.874,"809":0.794,"810":0.392,"811":0.863,"812":0.927,"813":0.887,"814":0.895,"815":0.647,"816":0.858,"817":0.768,"818":0.925,"819":0.934,"820":0.877,"821":0.82,"822":0.897,"823":0.876,"824":0.92,"825":0.877,"826":0.928,"827":0.722,"828":0.768,"829":0.906,"830":0.809,"831":0.044,"832":0.594,"833":0.569,"834":0.909,"835":0.922,"836":0.904,"837":0.579,"838":0.936,"839":0.839,"840":0.571,"841":0.578,"842":0.827,"843":0.839,"844":0.887,"845":0.898,"846":0.892,"847":0.9,"848":0.609,"849":0.856,"850":0.851,"851":0.815,"852":0.838,"853":0.876,"854":0.836,"855":0.87,"856":0.835,"857":0.865,"858":0.85,"859":0.894,"860":0.922,"861":0.852,"862":0.859,"863":0.901,"864":0.874,"865":0.541,"866":0.582,"867":0.902,"868":0.874,"869":0.88,"870":0.877,"871":0.667,"872":0.914,"873":0.898,"874":0.832,"875":0.834,"876":0.737,"877":0.511,"878":0.402,"879":0.487,"880":0.379,"881":0.881,"882":0.905,"883":0.908,"884":0.816,"885":0.567,"886":0.56,"887":0.565,"888":0.816,"889":0.908,"890":0.636,"891":0.898,"892":0.716,"893":0.909,"894":0.619,"895":0.592,"896":0.639,"897":0.632,"898":0.625,"899":0.61,"900":0.609,"901":0.611,"902":0.642,"903":0.64,"904":0.596,"905":0.594,"906":0.586,"907":0.6,"908":0.588,"909":0.64,"910":0.63,"911":0.626,"912":0.585,"913":0.908,"914":0.891,"915":0.876,"916":0.916,"917":0.605,"918":0.772,"919":0.723,"920":0.736,"921":0.639}; +const FORMULAS = [ + "n!", + "a\\!b", + "\\def\\sqr#1{#1^2} \\sqr{y}", + "\\#", + "\\%", + "\\begin{matrix} a & b \\\\ c & d \\end{matrix}", + "\\text{\\'{a}}", + "\\text{\\(\\frac a b\\)}", + "a\\ b", + "\\text{\\\"{a}}", + "a\\,\\,{b}", + "\\text{\\.{a}}", + "a\\:\\:{b}", + "\\;\\;{b}", + "x_i", + "\\text{\\={a}}", + "a\\>\\>{b}", + "{a}", + "\\text{no~no~no~breaks}", + "\\text{\\~{a}}", + "x^i", + "\\text{\\^{a}}", + "\\text{\\textdollar}", + "\\text{\\`{a}}", + "\\text{\\AA}", + "\\text{\\aa}", + "{a \\above{2pt} b+1}", + "\\acute e", + "\\text{\\AE}", + "\\text{\\ae}", + "\\alef", + "\\alefsym", + "\\aleph", + "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", + "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", + "\\begin{aligned} a&=b+c \\\\ d+e&=f \\end{aligned}", + "\\begin{alignat*}{2} 10&x+ &3&y = 2 \\\\ 3&x+&13&y = 4 \\end{alignat*}", + "\\begin{alignat*}{2} 10&x+ &3&y = 2 \\\\ 3&x+&13&y = 4 \\end{alignat*}", + "\\begin{alignedat}{2} 10&x+ &3&y = 2 \\\\ 3&x+&13&y = 4 \\end{alignedat}", + "\\Alpha", + "\\alpha", + "\\amalg", + "\\And", + "a_{\\angl n}", + "\\angln", + "\\allowbreak", + "\\angle", + "\\approx", + "\\approxeq", + "\\approxcolon", + "\\approxcoloncolon", + "\\arccos", + "\\arcctg", + "\\arcsin", + "\\arctan", + "\\arctg", + "\\arg", + "\\argmax", + "\\argmin", + "\\begin{array}{cc} a & b \\\\ c & d \\end{array}", + "\\def\\arraystretch{1.5} \\begin{array}{cc} a & b \\\\ c & d \\end{array}", + "\\ast", + "\\asymp", + "{a \\atop b}", + "\\backepsilon", + "\\backprime", + "\\backsim", + "\\backsimeq", + "\\backslash", + "\\bar{y}", + "\\barwedge", + "\\Bbb{ABC}", + "\\Bbbk", + "\\bcancel{5}", + "\\because", + "\\Beta", + "\\beta", + "\\beth", + "\\between", + "\\begingroup a \\endgroup", + "\\bf AaBb12", + "\\big(\\big)", + "\\Big(\\Big)", + "\\bigcap", + "\\bigcirc", + "\\bigcup", + "\\bigg(\\bigg)", + "\\Bigg(\\Bigg)", + "\\biggl(", + "\\Biggl(", + "\\biggm\\vert", + "\\Biggm\\vert", + "\\biggr)", + "\\Biggr)", + "\\bigl(", + "\\Bigl(", + "\\bigm\\vert", + "\\Bigm\\vert", + "\\bigodot", + "\\bigoplus", + "\\bigotimes", + "\\bigr)", + "\\Bigr)", + "\\bigsqcup", + "\\bigstar", + "\\bigtriangledown", + "\\bigtriangleup", + "\\biguplus", + "\\bigvee", + "\\bigwedge", + "\\binom n k", + "\\blacklozenge", + "\\blacksquare", + "\\blacktriangle", + "\\blacktriangledown", + "\\blacktriangleleft", + "\\blacktriangleright", + "\\bm{AaBb}", + "\\begin{Bmatrix} a & b \\\\ c & d \\end{Bmatrix}", + "\\begin{Bmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{Bmatrix*}", + "\\begin{bmatrix} a & b \\\\ c & d \\end{bmatrix}", + "\\begin{bmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{bmatrix*}", + "a \\bmod b", + "\\bold{AaBb123}", + "\\boldsymbol{AaBb}", + "\\bot", + "\\bowtie", + "\\Box", + "\\boxdot", + "\\boxed{ab}", + "\\boxminus", + "\\boxplus", + "\\boxtimes", + "\\Bra{\\psi}", + "\\bra{\\psi}", + "\\braket{\\phi|\\psi}", + "\\Braket{a|\\frac{\\partial^2}{\\partial t^2}|b}", + "{n\\brace k}", + "{n\\brack k}", + "\\breve{eu}", + "\\bull", + "\\bullet", + "\\Bumpeq", + "\\bumpeq", + "\\cal AaBb123", + "\\cancel{5}", + "\\Cap", + "\\cap", + "\\begin{cases} a &\\text{if } b \\\\ c &\\text{if } d \\end{cases}", + "\\begin{CD} A @>a>> B \\\\ @VbVV @AAcA \\\\ C @= D \\end{CD}", + "\\cdot", + "\\cdotp", + "\\cdots", + "\\ce{C6H5-CHO}", + "a\\centerdot b", + "\\cfrac{2}{1+\\cfrac{2}{1+\\cfrac{2}{1}}}", + "\\char\"263a", + "\\check{oe}", + "\\ch", + "\\checkmark", + "\\Chi", + "\\chi", + "{n+1 \\choose k+2}", + "\\circ", + "\\circeq", + "\\circlearrowleft", + "\\circlearrowright", + "\\circledast", + "\\circledcirc", + "\\circleddash", + "\\circledR", + "\\circledS", + "\\clubs", + "\\clubsuit", + "\\cnums", + "\\colon", + "\\Colonapprox", + "\\colonapprox", + "\\coloncolon", + "\\coloncolonapprox", + "\\coloncolonequals", + "\\coloncolonminus", + "\\coloncolonsim", + "\\Coloneq", + "\\coloneq", + "\\colonequals", + "\\Coloneqq", + "\\coloneqq", + "\\colonminus", + "\\Colonsim", + "\\colonsim", + "\\color{#0000FF} AaBb123", + "\\colorbox{red}{Black on red}", + "\\complement", + "\\Complex", + "\\cong", + "\\coprod", + "\\copyright", + "\\cos", + "\\cosec", + "\\cosh", + "\\cot", + "\\cotg", + "\\coth", + "\\begin{matrix} a & b \\cr c & d \\end{matrix}", + "\\csc", + "\\ctg", + "\\cth", + "\\Cup", + "\\cup", + "\\curlyeqprec", + "\\curlyeqsucc", + "\\curlyvee", + "\\curlywedge", + "\\curvearrowleft", + "\\curvearrowright", + "\\dag", + "\\Dagger", + "\\dagger", + "\\daleth", + "\\Darr", + "\\dArr", + "\\darr", + "\\begin{darray}{cc} a & b \\\\ c & d \\end{darray}", + "\\dashleftarrow", + "\\dashrightarrow", + "\\dashv", + "\\dbinom n k", + "\\dblcolon", + "\\begin{dcases} a &\\text{if } b \\\\ c &\\text{if } d \\end{dcases}", + "\\ddag", + "\\ddagger", + "\\ddddot x", + "\\dddot x", + "\\ddot x", + "\\ddots", + "\\def\\foo{x^2} \\foo + \\foo", + "\\deg", + "\\degree", + "\\delta", + "\\Delta", + "\\det", + "\\digamma", + "\\dfrac{a-1}{b-1}", + "\\diagdown", + "\\diagup", + "\\Diamond", + "\\diamond", + "\\diamonds", + "\\diamondsuit", + "\\dim", + "\\displaystyle\\sum_0^n", + "\\div", + "\\divideontimes", + "\\dot x", + "\\Doteq", + "\\doteq", + "\\doteqdot", + "\\dotplus", + "x_1 + \\dots + x_n", + "x_1 +\\dotsb + x_n", + "x,\\dotsc,y", + "\\int_{A_1}\\int_{A_2}\\dotsi", + "x_1 x_2 \\dotsm x_n", + "\\dotso", + "\\doublebarwedge", + "\\doublecap", + "\\doublecup", + "\\Downarrow", + "\\downarrow", + "\\downdownarrows", + "\\downharpoonleft", + "\\downharpoonright", + "\\begin{drcases} a &\\text{if } b \\\\ c &\\text{if } d \\end{drcases}", + "\\def\\foo{a}\\edef\\fcopy{\\foo}\\def\\foo{}\\fcopy", + "\\ell", + "\\emph{nested \\emph{emphasis}}", + "\\empty", + "\\emptyset", + "a\\enspace b", + "\\Epsilon", + "\\epsilon", + "\\eqcirc", + "\\Eqcolon", + "\\eqcolon", + "\\begin{equation*} a = b + c \\end{equation*}", + "\\begin{equation*} a = b + c \\end{equation*}", + "\\Eqqcolon", + "\\eqqcolon", + "\\eqsim", + "\\eqslantgtr", + "\\eqslantless", + "\\equalscolon", + "\\equalscoloncolon", + "\\equiv", + "\\Eta", + "\\eta", + "\\eth", + "\\exist", + "\\exists", + "\\exp", + "\\fallingdotseq", + "\\fbox{Hi there!}", + "\\fcolorbox{red}{aqua}{A}", + "\\Finv", + "\\flat", + "\\footnotesize footnotesize", + "\\forall", + "\\frac a b", + "\\frak{AaBb}", + "\\frown", + "\\Game", + "\\Gamma", + "\\gamma", + "\\begin{gather*} a=b \\\\ e=b+c \\end{gather*}", + "\\begin{gathered} a=b \\\\ e=b+c \\end{gathered}", + "\\gcd", + "\\gdef\\sqr#1{#1^2} \\sqr{y} + \\sqr{y}", + "\\gdef\\VERT{|} \\braket{\\phi\\VERT\\psi}", + "\\ge", + "\\genfrac ( ] {2pt}{0}a{a+1}", + "\\geq", + "\\geqq", + "\\geqslant", + "\\gets", + "\\gg", + "\\ggg", + "\\gggtr", + "\\gimel", + "\\global\\def\\add#1#2{#1+#2} \\add 2 3", + "\\gnapprox", + "\\gneq", + "\\gneqq", + "\\gnsim", + "\\grave{eu}", + "a \\gt b", + "\\gtrdot", + "\\gtrapprox", + "\\gtreqless", + "\\gtreqqless", + "\\gtrless", + "\\gtrsim", + "\\gvertneqq", + "\\text{\\H{a}}", + "\\Harr", + "\\hArr", + "\\harr", + "\\hat{\\theta}", + "\\hbar", + "\\hbox{$x^2$}", + "\\begin{matrix} a & b \\\\ \\hdashline c & d \\end{matrix}", + "\\hearts", + "\\heartsuit", + "\\begin{matrix} a & b \\\\ \\hline c & d \\end{matrix}", + "\\hom", + "\\hookleftarrow", + "\\hookrightarrow", + "a\\hphantom{bc}d", + "\\href{https://katex.org/}{\\KaTeX}", + "w\\hskip1em i\\hskip2em d", + "\\hslash", + "s\\hspace{7ex} k", + "\\htmlClass{foo}{x}", + "\\htmlData{foo=a, bar=b}{x}", + "\\htmlId{bar}{x}", + "\\htmlStyle{color: red;}{x}", + "\\huge huge", + "\\Huge Huge", + "\\text{\\i}", + "A\\iff B", + "\\iiint", + "\\iint", + "\\Im", + "\\image", + "\\imageof", + "\\imath", + "P\\impliedby Q", + "P\\implies Q", + "\\in", + "\\includegraphics[height=0.8em, totalheight=0.9em, width=0.9em, alt=KA logo]{https://cdn.kastatic.org/images/apple-touch-icon-57x57-precomposed.new.png}", + "\\inf", + "\\infin", + "\\infty", + "\\injlim", + "\\int", + "\\intercal", + "\\intop", + "\\Iota", + "\\iota", + "\\isin", + "{\\it AaBb}", + "\\text{\\j}", + "\\jmath", + "\\Join", + "\\Kappa", + "\\kappa", + "\\KaTeX", + "\\ker", + "I\\kern-2.5pt R", + "\\Ket{\\psi}", + "\\ket{\\psi}", + "\\Lambda", + "\\lambda", + "\\land", + "\\lang A\\rangle", + "\\langle A\\rangle", + "\\Larr", + "\\lArr", + "\\larr", + "\\large large", + "\\Large Large", + "\\LARGE LARGE", + "\\LaTeX", + "\\lBrace", + "\\lbrace", + "\\lbrack", + "\\lceil", + "\\ldotp", + "\\ldots", + "\\le", + "\\leadsto", + "\\left\\lbrace \\dfrac ab \\right.", + "\\leftarrow", + "\\Leftarrow", + "\\leftarrowtail", + "\\leftharpoondown", + "\\leftharpoonup", + "\\leftleftarrows", + "\\Leftrightarrow", + "\\leftrightarrow", + "\\leftrightarrows", + "\\leftrightharpoons", + "\\leftrightsquigarrow", + "\\leftthreetimes", + "\\leq", + "\\leqq", + "\\leqslant", + "\\lessapprox", + "\\lessdot", + "\\lesseqgtr", + "\\lesseqqgtr", + "\\lessgtr", + "\\lesssim", + "\\lfloor", + "\\lg", + "\\lgroup", + "\\lhd", + "\\lim", + "\\liminf", + "\\lim\\limits_x", + "\\limsup", + "\\ll", + "{=}\\llap{/\\,}", + "\\llbracket", + "\\llcorner", + "\\Lleftarrow", + "\\lll", + "\\llless", + "\\lmoustache", + "\\ln", + "\\lnapprox", + "\\lneq", + "\\lneqq", + "\\lnot", + "\\lnsim", + "\\log", + "\\Longleftarrow", + "\\longleftarrow", + "\\Longleftrightarrow", + "\\longleftrightarrow", + "\\longmapsto", + "\\Longrightarrow", + "\\longrightarrow", + "\\looparrowleft", + "\\looparrowright", + "\\lor", + "\\lozenge", + "\\lparen", + "\\Lrarr", + "\\lrArr", + "\\lrarr", + "\\lrcorner", + "\\lq", + "\\Lsh", + "\\lt", + "\\ltimes", + "\\lVert", + "\\lvert", + "\\lvertneqq", + "\\maltese", + "\\mapsto", + "\\mathbb{AB}", + "\\mathbf{AaBb123}", + "a\\mathbin{!}b", + "\\mathcal{AaBb123}", + "a\\mathchoice{\\,}{\\,\\,}{\\,\\,\\,}{\\,\\,\\,\\,}b", + "\\sum_{\\mathclap{1\\le i\\le n}} x_{i}", + "a + (b\\mathclose\\gt + c", + "\\mathellipsis", + "\\mathfrak{AaBb}", + "ab\\mathinner{\\text{inside}}cd", + "\\mathit{AaBb}", + "{=}\\mathllap{/\\,}", + "\\mathnormal{AaBb}", + "\\mathop{\\star}_a^b", + "\\{x\u2208\u211d\\mid x>0\\}", + "P\\left(A\\middle\\vert B\\right)", + "\\min", + "\\minuscolon", + "\\minuscoloncolon", + "\\minuso", + "a\\mkern18mu b", + "3\\equiv 5 \\mod 2", + "\\models", + "\\mp", + "a\\mskip{10mu}b", + "\\Mu", + "\\mu", + "\\multimap", + "\\N", + "\\nabla", + "\\natnums", + "\\natural", + "a\\negmedspace b", + "\\ncong", + "\\ne", + "\\nearrow", + "\\neg", + "a\\negthickspace b", + "a\\negthinspace b", + "\\neq", + "\\newcommand\\chk{\\checkmark} \\chk", + "a\\newline b", + "\\nexists", + "\\ngeq", + "\\ngeqq", + "\\ngeqslant", + "\\ngtr", + "\\ni", + "\\nleftarrow", + "\\nLeftarrow", + "\\nLeftrightarrow", + "\\nleftrightarrow", + "\\nleq", + "\\nleqq", + "\\nleqslant", + "\\nless", + "\\nmid", + "a\\nobreakspace b", + "\\lim\\nolimits_x", + "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", + "\\normalsize normalsize", + "\\not =", + "\\begin{align*} a&=b+c \\\\ d+e&=f \\end{align*}", + "\\notin", + "\\notni", + "\\nparallel", + "\\nprec", + "\\npreceq", + "\\nRightarrow", + "\\nrightarrow", + "\\nshortmid", + "\\nshortparallel", + "\\nsim", + "\\nsubseteq", + "\\nsubseteqq", + "\\nsucc", + "\\nsucceq", + "\\nsupseteq", + "\\nsupseteqq", + "\\ntriangleleft", + "\\ntrianglelefteq", + "\\ntriangleright", + "\\ntrianglerighteq", + "\\Nu", + "\\nu", + "\\nVDash", + "\\nVdash", + "\\nvDash", + "\\nvdash", + "\\nwarrow", + "\\text{\\O}", + "\\text{\\o}", + "\\odot", + "\\text{\\OE}", + "\\text{\\oe}", + "\\oiiint", + "\\oiint", + "\\oint", + "\\omega", + "\\Omega", + "\\Omicron", + "\\omicron", + "\\ominus", + "\\operatorname{asin} x", + "\\operatorname*{asin}\\limits_y x", + "\\operatornamewithlimits{asin}\\limits_y x", + "\\oplus", + "\\origof", + "\\oslash", + "\\otimes", + "{a+1 \\over b+2}+c", + "\\overbrace{x+\u22ef+x}^{n\\text{ times}}", + "\\overgroup{AB}", + "\\overleftarrow{AB}", + "\\overleftharpoon{AB}", + "\\overleftrightarrow{AB}", + "\\overline{\\text{a long argument}}", + "\\overlinesegment{AB}", + "\\Overrightarrow{AB}", + "\\overrightarrow{AB}", + "\\overrightharpoon{ac}", + "\\overset{!}{=}", + "\\owns", + "\\text{\\P}", + "\\parallel", + "\\partial", + "\\perp", + "\\Gamma^{\\phantom{i}j}_{i\\phantom{j}k}", + "\\phase{-78^\\circ}", + "\\Phi", + "\\phi", + "\\Pi", + "\\pi", + "\\pitchfork", + "\\plim", + "\\plusmn", + "\\pm", + "\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}", + "\\begin{pmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{pmatrix*}", + "\\pmb{\\mu}", + "x\\pmod a", + "x \\pod a", + "\\pounds", + "\\Pr", + "\\prec", + "\\precapprox", + "\\preccurlyeq", + "\\preceq", + "\\precnapprox", + "\\precneqq", + "\\precnsim", + "\\precsim", + "\\prime", + "\\prod", + "\\projlim", + "\\propto", + "\\providecommand\\greet{\\text{Hello}} \\greet", + "\\psi", + "\\Psi", + "\\pu{123 kJ//mol}", + "a\\qquad\\qquad{b}", + "a\\quad\\quad{b}", + "\\R", + "\\text{\\r{a}}", + "h\\raisebox{2pt}{ighe}r", + "\\langle A\\rang", + "\\Rarr", + "\\rArr", + "\\rarr", + "\\ratio", + "\\rBrace", + "\\rbrace", + "\\rbrack", + "\\begin{rcases} a &\\text{if } b \\\\ c &\\text{if } d \\end{rcases}", + "\\rceil", + "\\Re", + "\\real", + "\\Reals", + "\\reals", + "\\def\\hail{Hi!} \\renewcommand\\hail{\\text{Ahoy!}} \\hail", + "\\restriction", + "\\rfloor", + "\\rgroup", + "\\rhd", + "\\Rho", + "\\rho", + "\\left.\\dfrac a b\\right)", + "\\Rightarrow", + "\\rightarrow", + "\\rightarrowtail", + "\\rightharpoondown", + "\\rightharpoonup", + "\\rightleftarrows", + "\\rightleftharpoons", + "\\rightrightarrows", + "\\rightsquigarrow", + "\\rightthreetimes", + "\\risingdotseq", + "\\rlap{\\,/}{=}", + "\\rm AaBb12", + "\\rmoustache", + "\\rparen", + "\\rq", + "\\rrbracket", + "\\Rrightarrow", + "\\Rsh", + "\\rtimes", + "x\\rule[6pt]{2ex}{1ex}x", + "\\rVert", + "\\rvert", + "\\text{\\S}", + "\\scriptscriptstyle \\frac cd", + "\\scriptsize scriptsize", + "\\frac ab + {\\scriptstyle \\frac cd}", + "\\sdot", + "\\searrow", + "\\sec", + "\\text{\\sect}", + "\\set{x|x<5}", + "\\Set{ x | x<\\frac 1 2 }", + "\\setminus", + "\\sf AaBb123", + "\\sharp", + "\\shortmid", + "\\shortparallel", + "\\Sigma", + "\\sigma", + "\\sim", + "\\simcolon", + "\\simcoloncolon", + "\\simeq", + "\\sin", + "\\sinh", + "\\sixptsize sixptsize", + "\\sh", + "\\small small", + "\\smallfrown", + "\\smallint", + "\\begin{smallmatrix} a & b \\\\ c & d \\end{smallmatrix}", + "\\smallsetminus", + "\\smallsmile", + "\\left(x^{\\smash{2}}\\right)", + "\\smile", + "\\sout{abc}", + "a\\space b", + "\\spades", + "\\spadesuit", + "\\sphericalangle", + "\\begin{equation*} \\begin{split} a &=b+c\\\\ &=e+f \\end{split} \\end{equation*}", + "\\sqcap", + "\\sqcup", + "\\square", + "\\sqrt[3]{x}", + "\\sqsubset", + "\\sqsubseteq", + "\\sqsupset", + "\\sqsupseteq", + "\\text{\\ss}", + "\\stackrel{!}{=}", + "\\star", + "\\sub", + "\\sube", + "\\Subset", + "\\subset", + "\\subseteq", + "\\subseteqq", + "\\subsetneq", + "\\subsetneqq", + "\\sum_{\\substack{0\\le i\\le n \\\\ i\\text{ even}}} x_i", + "\\text{\\textgreater}", + "\\textit{AaBb}", + "\\text{\\textless}", + "\\textmd{AaBb123}", + "\\textnormal{AB}", + "\\text{\\textquotedblleft}", + "\\text{\\textquotedblright}", + "\\text{\\textquoteleft}", + "\\text{\\textquoteright}", + "\\text{\\textregistered}", + "\\textrm{AaBb123}", + "\\textsf{AaBb123}", + "\\text{\\textsterling}", + "\\textstyle\\sum_0^n", + "\\texttt{AaBb123}", + "\\text{\\textunderscore}", + "\\textup{AaBb123}", + "\\tfrac ab", + "\\tg", + "\\th", + "\\therefore", + "\\Theta", + "\\theta", + "\\thetasym", + "\\thickapprox", + "\\thicksim", + "a\\thickspace b", + "a\\thinspace b", + "\\tilde M", + "\\times", + "\\tiny tiny", + "\\to", + "\\top", + "\\triangle", + "\\triangledown", + "\\triangleleft", + "\\trianglelefteq", + "\\triangleq", + "\\triangleright", + "\\trianglerighteq", + "{\\tt AaBb123}", + "\\twoheadleftarrow", + "\\twoheadrightarrow", + "\\text{\\u{a}}", + "\\Uarr", + "\\uArr", + "\\uarr", + "\\ulcorner", + "\\underbar{X}", + "\\underbrace{x+\u22ef+x}_{n\\text{ times}}", + "\\undergroup{AB}", + "\\underleftarrow{AB}", + "\\underleftrightarrow{AB}", + "\\underrightarrow{AB}", + "\\underline{\\text{a long argument}}", + "\\underlinesegment{AB}", + "\\underset{!}{=}", + "\\unlhd", + "\\unrhd", + "\\Uparrow", + "\\uparrow", + "\\Updownarrow", + "\\updownarrow", + "\\upharpoonleft", + "\\upharpoonright", + "\\uplus", + "\\Upsilon", + "\\upsilon", + "\\upuparrows", + "\\urcorner", + "\\url{https://katex.org/}", + "\\utilde{AB}", + "\\text{\\v{a}}", + "\\varDelta", + "\\varepsilon", + "\\varGamma", + "\\varinjlim", + "\\varkappa", + "\\varLambda", + "\\varliminf", + "\\varlimsup", + "\\varnothing", + "\\varOmega", + "\\varPhi", + "\\varphi", + "\\varPi", + "\\varpi", + "\\varprojlim", + "\\varpropto", + "\\varPsi", + "\\varrho", + "\\varSigma", + "\\varsigma", + "\\varsubsetneq", + "\\varsubsetneqq", + "\\varsupsetneq", + "\\varsupsetneqq", + "\\varTheta", + "\\vartheta", + "\\vartriangle", + "\\vartriangleleft", + "\\vartriangleright", + "\\varUpsilon", + "\\varXi", + "\\mathrel{\\vcentcolon =}", + "a+\\left(\\vcenter{\\frac{\\frac a b}c}\\right)", + "\\Vdash", + "\\vDash", + "\\vdash", + "\\vdots", + "\\vec{F}", + "\\vee", + "\\veebar", + "\\verb!\\frac a b!", + "\\Vert", + "\\vert", + "\\begin{Vmatrix} a & b \\\\ c & d \\end{Vmatrix}", + "\\begin{Vmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{Vmatrix*}", + "\\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix}", + "\\begin{vmatrix*}[r] 0 & -1 \\\\ -1 & 0 \\end{vmatrix*}", + "\\overline{\\vphantom{M}a}", + "\\Vvdash", + "\\wedge", + "\\weierp", + "\\widecheck{AB}", + "\\widehat{AB}", + "\\widetilde{AB}", + "\\wp", + "\\wr", + "\\xcancel{ABC}", + "\\def\\foo{a}\\xdef\\fcopy{\\foo}\\def\\foo{}\\fcopy", + "\\Xi", + "\\xi", + "\\xhookleftarrow{abc}", + "\\xhookrightarrow{abc}", + "\\xLeftarrow{abc}", + "\\xleftarrow{abc}", + "\\xleftharpoondown{abc}", + "\\xleftharpoonup{abc}", + "\\xLeftrightarrow{abc}", + "\\xleftrightarrow{abc}", + "\\xleftrightharpoons{abc}", + "\\xlongequal{abc}", + "\\xmapsto{abc}", + "\\xRightarrow{abc}", + "\\xrightarrow{abc}", + "\\xrightharpoondown{abc}", + "\\xrightharpoonup{abc}", + "\\xrightleftharpoons{abc}", + "\\xtofrom{abc}", + "\\xtwoheadleftarrow{abc}", + "\\xtwoheadrightarrow{abc}", + "\\yen", + "\\Z", + "\\Zeta", + "\\zeta", + "\\hat{M}", + "\\hat{b}", + "\\hat{\\aa}", + "\\hat{o}", + "\\tilde{M}", + "\\left\\{\\begin{array}{l} \\nabla\\cdot\\vec{D} = \\rho \\\\ \\nabla\\cdot\\vec{B} = 0 \\\\ \\nabla\\times\\vec{E} = -\\frac{\\partial\\vec{B}}{\\partial t} \\\\ \\nabla\\times\\vec{H} = \\vec{J}_f + \\frac{\\partial\\vec{D}}{\\partial t} \\end{array}\\right.", + "\u9178 + \u78b1 \\rightarrow \u76d0 + \u6c34", + "\\sqrt{}", + "\\begin{pmatrix} a_{11} & a_{12} & a_{13} \\\\ a_{21} & a_{22} & a_{23} \\\\ a_{31} & a_{32} & a_{33} \\end{pmatrix}", + "\\begin{vmatrix} 1 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 7 & 8 & 9 \\end{vmatrix}", + "\\begin{bmatrix} 1 & 0 & 0 \\\\ 0 & 1 & 0 \\\\ 0 & 0 & 1 \\end{bmatrix}", + "\\begin{pmatrix} \\cos\\theta & -\\sin\\theta & 0 \\\\ \\sin\\theta & \\cos\\theta & 0 \\\\ 0 & 0 & 1 \\end{pmatrix}", + "\\begin{pmatrix} 1 & 0 & \\cdots & 0 \\\\ 0 & 1 & \\cdots & 0 \\\\ \\vdots & \\vdots & \\ddots & \\vdots \\\\ 0 & 0 & \\cdots & 1 \\end{pmatrix}", + "\\begin{bmatrix} A & B \\\\ C & D \\end{bmatrix}\\begin{bmatrix} x \\\\ y \\end{bmatrix} = \\begin{bmatrix} 0 \\\\ 0 \\end{bmatrix}", + "\\begin{Bmatrix} a & b & c \\\\ d & e & f \\\\ g & h & i \\end{Bmatrix}", + "\\mathring{A}", + "\\hat{\\hat{x}}", + "\\dot{\\vec{p}} = -\\nabla V", + "\\widehat{XY + Z}", + "\\widetilde{f \\circ g}", + "\\ddot{\\vec{r}}", + "R^{\\mu}{}_{{\\nu\\rho\\sigma}}", + "T^{\\mu\\nu}{}_{\\rho} = g^{\\mu\\alpha}g^{\\nu\\beta}T_{\\alpha\\beta\\rho}", + "\\Gamma^{\\mu}_{\\nu\\rho} = \\tfrac{1}{2}g^{\\mu\\sigma}\\!\\left(\\partial_\\nu g_{\\rho\\sigma}+\\partial_\\rho g_{\\nu\\sigma}-\\partial_\\sigma g_{\\nu\\rho}\\right)", + "\\nabla_\\mu T^{\\mu\\nu} = 0", + "\\exp\\Bigl(\\tfrac{x+y}{1+x^2}\\Bigr)", + "f^{(n)}(x) = \\dfrac{\\mathrm{d}^n f}{\\mathrm{d}x^n}", + "\\frac{\\partial^2 u}{\\partial x \\partial y}", + "\\begin{matrix} \\hline a & b \\\\ c & d \\end{matrix}", + "\\displaystyle\\binom{k}{p}\\binom{n-k}{r-p}", + "\\bigl\\{\\begin{smallmatrix} 1&2\\\\3&4 \\end{smallmatrix}\\bigr\\}", + "\\operatorname{supp} f \\cap \\operatorname{supp} g", + "\\mathring{U} \\subseteq \\overline{V}", + "\\acute{\\mathbb{A}}", + "\\check{\\mathscr{D}}", + "\\tilde{\\mathrm{e}}^{\\mathrm{i}\\pi}+1=0", + "\\utilde{EF} + \\widehat{GH}", + "\\begin{array}{|c|c|} \\hline 1 & 2 \\\\ \\hline 3 & 4 \\\\ \\hline \\end{array}", + "\\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}", + "\\ce{CO2 + C -> 2 CO}", + "\\ce{Hg^2+ ->[I-] HgI2 ->[I-] [Hg^{II}I4]^2-}", + "$C_p[\\ce{H2O(l)}] = \\pu{75.3 J // mol K}$", + "\\ce{H2O}", + "\\ce{Sb2O3}", + "\\ce{H+}", + "\\ce{CrO4^2-}", + "\\ce{[AgCl2]-}", + "\\ce{Y^99+}", + "\\ce{Y^{99+}}", + "\\ce{2 H2O}", + "\\ce{2H2O}", + "\\ce{0.5 H2O}", + "\\ce{1/2 H2O}", + "\\ce{(1/2) H2O}", + "\\ce{$n$ H2O}", + "\\ce{^{227}_{90}Th+}", + "\\ce{^227_90Th+}", + "\\ce{^{0}_{-1}n^{-}}", + "\\ce{^0_-1n-}", + "\\ce{H{}^3HO}", + "\\ce{H^3HO}", + "\\ce{A -> B}", + "\\ce{A <- B}", + "\\ce{A <-> B}", + "\\ce{A <--> B}", + "\\ce{A <=> B}", + "\\ce{A <=>> B}", + "\\ce{A <<=> B}", + "\\ce{A ->[H2O] B}", + "\\ce{A ->[{text above}][{text below}] B}", + "\\ce{A ->[$x$][$x_i$] B}", + "\\ce{(NH4)2S}", + "\\ce{[\\{(X2)3\\}2]^3+}", + "\\ce{CH4 + 2 $\\left( \\ce{O2 + 79/21 N2} \\right)$}", + "\\ce{H2(aq)}", + "\\ce{CO3^2-_{(aq)}}", + "\\ce{NaOH(aq,$\\infty$)}", + "\\ce{ZnS($c$)}", + "\\ce{ZnS(\\ca$c$)}", + "\\ce{NO_x}", + "\\ce{Fe^n+}", + "\\ce{x Na(NH4)HPO4 ->[\\Delta] (NaPO3)_x + x NH3 ^ + x H2O}", + "\\ce{\\mu-Cl}", + "\\ce{[Pt(\\eta^2-C2H4)Cl3]-}", + "\\ce{\\beta +}", + "\\ce{^40_18Ar + \\gamma{} + \\nu_e}", + "\\ce{Fe(CN)_{$\\frac{6}{2}$}}", + "\\ce{X_{$i$}^{$x$}}", + "\\ce{X_$i$^$x$}", + "\\ce{$cis${-}[PtCl2(NH3)2]}", + "\\ce{CuS($hP12$)}", + "\\ce{{Gluconic Acid} + H2O2}", + "\\ce{X_{{red}}}", + "\\ce{{(+)}_589{-}[Co(en)3]Cl3}", + "\\ce{C6H5-CHO}", + "\\ce{A-B=C#D}", + "\\ce{A\\bond{-}B\\bond{=}C\\bond{#}D}", + "\\ce{A\\bond{1}B\\bond{2}C\\bond{3}D}", + "\\ce{A\\bond{~}B\\bond{~-}C}", + "\\ce{A\\bond{~--}B\\bond{~=}C\\bond{-~-}D}", + "\\ce{A\\bond{...}B\\bond{....}C}", + "\\ce{A\\bond{->}B\\bond{<-}C}", + "\\ce{KCr(SO4)2*12H2O}", + "\\ce{KCr(SO4)2.12H2O}", + "\\ce{KCr(SO4)2 * 12 H2O}", + "\\ce{Fe^{II}Fe^{III}2O4}", + "\\ce{OCO^{.-}}", + "\\ce{NO^{(2.)-}}", + "\\ce{Li^x_{Li,1-2x}Mg^._{Li,x}$V$'_{Li,x}Cl^x_{Cl}}", + "\\ce{O''_{i,x}}", + "\\ce{M^{..}_i}", + "\\ce{$V$^{4'}_{Ti}}", + "\\ce{V_{V,1}C_{C,0.8}$V$_{C,0.2}}", + "\\ce{A + B}", + "\\ce{A - B}", + "\\ce{A = B}", + "\\ce{A \\pm B}", + "\\ce{SO4^2- + Ba^2+ -> BaSO4 v}", + "\\ce{A v B (v) -> B ^ B (^)}", + "\\ce{NO^*}", + "\\ce{1s^2-N}", + "\\ce{n-Pr}", + "\\ce{iPr}", + "\\ce{\\ca Fe}", + "\\ce{A, B, C; F}", + "\\ce{{and others}}", + "\\ce{Zn^2+ <=>[+ 2OH-][+ 2H+] $\\underset{\\text{amphoteres Hydroxid}}{\\ce{Zn(OH)2 v}}$ <=>[+ 2OH-][+ 2H+] $\\underset{\\text{Hydroxozikat}}{\\ce{[Zn(OH)4]^2-}}$}", + "\\ce{$K = \\frac{[\\ce{Hg^2+}][\\ce{Hg}]}{[\\ce{Hg2^2+}]}$}", + "\\ce{$K = \\ce{\\frac{[Hg^2+][Hg]}{[Hg2^2+]}}$}", + "\\ce{Hg^2+ ->[I-] $\\underset{\\mathrm{red}}{\\ce{HgI2}}$ ->[I-] $\\underset{\\mathrm{red}}{\\ce{[Hg^{II}I4]^2-}}$}", + "\\pu{123 kJ}", + "\\pu{123 mm2}", + "\\pu{123 J s}", + "\\pu{123 J*s}", + "\\pu{123 kJ/mol}", + "\\pu{123 kJ//mol}", + "\\pu{123 kJ mol-1}", + "\\pu{123 kJ*mol-1}", + "\\pu{1.2e3 kJ}", + "\\pu{1,2e3 kJ}", + "\\pu{1.2E3 kJ}", + "\\pu{1,2E3 kJ}" +]; +const GOLDEN_SCORES = {"1":0.889, "2":0.914, "3":0.818, "4":0.898, "5":0.875, "6":0.915, "7":0.675, "8":0.853, "9":0.909, "10":0.643, "11":0.905, "12":0.643, "13":0.911, "14":0.906, "15":0.88, "16":0.911, "17":0.911, "18":0.898, "19":0.866, "20":0.65, "21":0.915, "22":0.69, "23":0.899, "24":0.69, "25":0.756, "26":0.732, "27":0.879, "28":0.691, "29":0.884, "30":0.922, "31":0.926, "32":0.926, "33":0.926, "34":0.605, "35":0.605, "36":0.605, "37":0.572, "38":0.572, "39":0.572, "40":0.887, "41":0.876, "42":0.907, "43":0.904, "44":0.825, "45":0.855, "46":0.8, "47":0.894, "48":0.884, "49":0.921, "50":0.611, "51":0.578, "52":0.903, "53":0.905, "54":0.825, "55":0.781, "56":0.906, "57":0.884, "58":0.811, "59":0.908, "60":0.904, "61":0.888, "62":0.911, "63":0.886, "64":0.902, "65":0.818, "66":0.9, "67":0.926, "68":0.878, "69":0.919, "70":0.77, "71":0.885, "72":0.792, "73":0.915, "74":0.782, "75":0.771, "76":0.9, "77":0.827, "78":0.892, "79":0.884, "80":0.898, "81":0.922, "82":0.935, "83":0.918, "84":0.928, "85":0.889, "86":0.933, "87":0.939, "88":0.839, "89":0.923, "90":0.847, "91":0.689, "92":0.473, "93":0.935, "94":0.87, "95":0.93, "96":0.943, "97":0.532, "98":0.534, "99":0.951, "100":0.954, "101":0.95, "102":0.911, "103":0.875, "104":0.929, "105":0.932, "106":0.796, "107":0.919, "108":0.935, "109":0.937, "110":0.935, "111":0.914, "112":0.956, "113":0.954, "114":0.974, "115":0.953, "116":0.939, "117":0.931, "118":0.853, "119":0.902, "120":0.943, "121":0.901, "122":0.843, "123":0.909, "124":0.743, "125":0.853, "126":0.909, "127":0.901, "128":0.875, "129":0.876, "130":0.821, "131":0.88, "132":0.883, "133":0.882, "134":0.832, "135":0.832, "136":0.852, "137":0.519, "138":0.933, "139":0.896, "140":0.667, "141":0.964, "142":0.964, "143":0.932, "144":0.935, "145":0.771, "146":0.792, "147":0.919, "148":0.936, "149":0.835, "151":0.78, "152":0.78, "153":0.924, "154":0.853, "155":0.914, "156":0.476, "157":0.39, "158":0.549, "159":0.892, "160":0.927, "161":0.825, "162":0.846, "163":0.893, "164":0.913, "165":0.939, "166":0.821, "167":0.822, "168":0.889, "169":0.88, "170":0.873, "171":0.916, "172":0.906, "173":0.956, "174":0.956, "175":0.869, "176":0.794, "177":0.601, "178":0.627, "179":0.822, "180":0.601, "181":0.592, "182":0.684, "183":0.68, "184":0.684, "185":0.634, "186":0.547, "187":0.592, "188":0.547, "189":0.634, "190":0.68, "191":0.648, "192":0.788, "193":0.98, "194":0.946, "195":0.869, "196":0.876, "197":0.975, "198":0.741, "199":0.914, "200":0.906, "201":0.895, "202":0.851, "203":0.911, "204":0.914, "205":0.915, "206":0.91, "207":0.902, "208":0.912, "209":0.92, "210":0.938, "211":0.83, "212":0.861, "213":0.908, "214":0.909, "215":0.701, "216":0.758, "217":0.923, "218":0.924, "219":0.923, "220":0.903, "221":0.88, "222":0.88, "223":0.829, "224":0.904, "225":0.93, "226":0.92, "227":0.88, "228":0.914, "229":0.822, "230":0.835, "231":0.924, "232":0.924, "233":0.579, "234":0.619, "235":0.644, "236":0.832, "237":0.672, "238":0.884, "239":0.881, "240":0.911, "241":0.683, "242":0.899, "243":0.903, "244":0.862, "245":0.902, "246":0.808, "247":0.862, "248":0.85, "249":0.79, "250":0.79, "251":0.909, "252":0.667, "253":0.856, "254":0.847, "255":0.643, "256":0.944, "257":0.911, "258":0.944, "259":0.961, "260":0.787, "261":0.787, "262":0.608, "263":0.479, "264":0.674, "265":0.916, "266":0.856, "267":0.919, "268":0.92, "269":0.88, "270":0.829, "271":0.925, "272":0.912, "273":0.891, "274":0.835, "275":0.898, "276":0.886, "277":0.643, "278":0.917, "279":0.917, "280":0.91, "281":0.81, "282":0.905, "283":0.882, "284":0.646, "285":0.61, "286":0.821, "287":0.821, "288":0.577, "289":0.538, "290":0.888, "291":0.906, "292":0.915, "293":0.538, "294":0.577, "295":0.874, "296":0.822, "297":0.895, "298":0.899, "299":0.884, "300":0.884, "301":0.857, "302":0.844, "303":0.765, "304":0.533, "305":0.816, "306":0.943, "307":0.579, "308":0.848, "309":0.849, "310":0.854, "311":0.879, "312":0.861, "313":0.623, "314":0.889, "315":0.84, "316":0.84, "317":0.918, "318":0.633, "319":0.852, "320":0.926, "321":0.905, "322":0.926, "323":0.865, "324":0.91, "325":0.917, "326":0.902, "327":0.881, "328":0.881, "329":0.897, "330":0.918, "331":0.891, "332":0.927, "333":0.889, "334":0.859, "335":0.612, "336":0.876, "337":0.818, "338":0.898, "339":0.92, "340":0.901, "341":0.835, "342":0.838, "343":0.894, "344":0.671, "345":0.848, "346":0.848, "347":0.873, "348":0.715, "349":0.934, "350":0.89, "351":0.86, "352":0.879, "353":0.879, "354":0.881, "355":0.847, "356":0.878, "357":0.854, "358":0.905, "359":0.526, "360":0.771, "361":0.93, "362":0.903, "363":0.885, "364":0.885, "365":0.885, "366":0.885, "367":0.91, "368":0.628, "369":0.883, "370":0.892, "371":0.951, "372":0.958, "373":0.854, "374":0.854, "375":0.163, "376":0.788, "377":0.739, "378":0.743, "379":0.825, "381":0.898, "382":0.85, "383":0.85, "384":0.861, "385":0.944, "386":0.956, "387":0.944, "388":0.892, "389":0.901, "390":0.825, "391":0.911, "392":0.892, "393":0.885, "394":0.901, "395":0.827, "396":0.884, "397":0.668, "398":0.879, "399":0.668, "400":0.826, "401":0.826, "402":0.646, "403":0.917, "404":0.908, "405":0.883, "406":0.883, "407":0.837, "408":0.837, "409":0.917, "410":0.675, "411":0.843, "412":0.834, "413":0.67, "414":0.939, "415":0.94, "416":0.79, "417":0.932, "418":0.775, "419":0.916, "420":0.928, "421":0.808, "422":0.625, "423":0.917, "424":0.837, "425":0.837, "426":0.815, "427":0.88, "428":0.863, "429":0.848, "430":0.873, "431":0.918, "432":0.918, "433":0.788, "434":0.855, "435":0.928, "436":0.874, "437":0.913, "438":0.903, "439":0.821, "440":0.922, "441":0.906, "442":0.825, "443":0.847, "444":0.905, "445":0.894, "446":0.941, "447":0.852, "448":0.915, "449":0.812, "450":0.787, "451":0.834, "452":0.819, "453":0.924, "454":0.813, "455":0.928, "456":0.912, "457":0.887, "458":0.887, "459":0.919, "460":0.902, "461":0.894, "462":0.93, "463":0.894, "464":0.92, "465":0.868, "466":0.888, "467":0.907, "468":0.904, "469":0.888, "470":0.841, "471":0.838, "472":0.886, "473":0.852, "474":0.891, "475":0.892, "476":0.914, "477":0.862, "478":0.814, "479":0.848, "480":0.848, "481":0.873, "482":0.928, "483":0.92, "484":0.841, "485":0.823, "486":0.829, "487":0.834, "488":0.737, "489":0.899, "490":0.926, "491":0.856, "492":0.847, "493":0.743, "494":0.92, "495":0.771, "496":0.91, "497":0.562, "498":0.752, "499":0.916, "500":0.854, "501":0.901, "502":0.911, "503":0.924, "504":0.868, "505":0.861, "506":0.699, "507":0.572, "508":0.829, "509":0.61, "510":0.646, "511":0.837, "512":0.91, "513":0.884, "514":0.895, "515":0.912, "516":0.908, "517":0.899, "518":0.829, "519":0.811, "520":0.731, "521":0.945, "522":0.731, "523":0.897, "524":0.908, "525":0.924, "526":0.912, "527":0.903, "528":0.92, "529":0.915, "530":0.914, "531":0.912, "532":0.927, "533":0.748, "534":0.864, "535":0.925, "536":0.917, "537":0.914, "538":0.855, "539":0.807, "540":0.82, "541":0.911, "542":0.891, "543":0.781, "544":0.927, "545":0.919, "546":0.914, "547":0.859, "548":0.819, "549":0.909, "550":0.756, "551":0.605, "552":0.791, "553":0.912, "554":0.605, "555":0.92, "556":0.93, "557":0.861, "558":0.853, "559":0.922, "560":0.916, "561":0.769, "562":0.891, "563":0.901, "564":0.902, "565":0.921, "566":0.885, "567":0.86, "568":0.92, "569":0.924, "570":0.881, "571":0.856, "572":0.927, "573":0.871, "574":0.927, "575":0.801, "576":0.854, "577":0.857, "578":0.846, "579":0.857, "580":0.84, "581":0.844, "582":0.897, "583":0.877, "584":0.886, "585":0.88, "586":0.925, "587":0.913, "588":0.852, "589":0.939, "590":0.897, "591":0.657, "592":0.855, "593":0.874, "594":0.894, "595":0.841, "596":0.58, "597":0.58, "598":0.895, "599":0.163, "600":0.885, "601":0.887, "602":0.773, "603":0.525, "604":0.658, "605":0.839, "606":0.837, "607":0.804, "608":0.654, "609":0.782, "610":0.782, "611":0.799, "612":0.893, "613":0.717, "614":0.807, "615":0.905, "616":0.834, "617":0.91, "618":0.909, "619":0.639, "620":0.898, "621":0.815, "622":0.89, "623":0.667, "624":0.879, "625":0.919, "626":0.856, "627":0.813, "628":0.813, "629":0.926, "630":0.887, "631":0.842, "632":0.717, "633":0.762, "634":0.886, "635":0.893, "636":0.814, "637":0.902, "638":0.921, "639":0.935, "640":0.855, "641":0.892, "642":0.86, "643":0.843, "644":0.896, "645":0.976, "646":0.796, "647":0.867, "648":0.874, "649":0.874, "650":0.708, "651":0.694, "652":0.91, "653":0.909, "654":0.903, "655":0.732, "656":0.887, "657":0.883, "658":0.823, "659":0.823, "660":0.862, "661":0.835, "662":0.878, "663":0.936, "664":0.823, "665":0.835, "666":0.873, "667":0.918, "668":0.918, "669":0.903, "670":0.903, "671":0.884, "672":0.877, "673":0.851, "674":0.917, "675":0.859, "676":0.903, "677":0.834, "678":0.625, "679":0.823, "680":0.862, "681":0.819, "682":0.819, "683":0.881, "684":0.92, "685":0.9, "686":0.802, "687":0.808, "688":0.85, "689":0.845, "690":0.896, "691":0.925, "692":0.918, "693":0.912, "694":0.872, "695":0.85, "696":0.884, "697":0.855, "698":0.831, "699":0.846, "700":0.834, "701":0.737, "702":0.838, "703":0.836, "704":0.898, "705":0.652, "706":0.78, "707":0.871, "708":0.907, "709":0.838, "710":0.763, "711":0.659, "712":0.919, "713":0.916, "714":0.883, "715":0.71, "716":0.903, "717":0.613, "718":0.919, "719":0.888, "720":0.645, "721":0.67, "722":0.878, "723":0.913, "724":0.885, "725":0.835, "726":0.884, "727":0.87, "728":0.748, "729":0.929, "730":0.414, "731":0.78, "732":0.83, "733":0.754, "734":0.92, "735":0.596, "736":0.909, "737":0.955, "738":0.955, "739":0.906, "740":0.656, "741":0.832, "742":0.808, "743":0.875, "744":0.759, "745":0.895, "746":0.943, "747":0.883, "748":0.942, "749":0.902, "750":0.717, "751":0.886, "752":0.824, "753":0.937, "754":0.891, "755":0.824, "756":0.937, "757":0.92, "758":0.932, "759":0.899, "760":0.792, "761":0.808, "762":0.911, "763":0.823, "764":0.92, "765":0.854, "766":0.879, "767":0.873, "768":0.92, "769":0.909, "770":0.757, "771":0.92, "772":0.916, "773":0.886, "774":0.807, "775":0.918, "776":0.678, "777":0.92, "778":0.853, "779":0.894, "780":0.888, "781":0.779, "782":0.768, "783":0.896, "784":0.894, "785":0.901, "786":0.901, "787":0.908, "788":0.91, "789":0.639, "790":0.83, "791":0.827, "792":0.862, "793":0.908, "794":0.919, "795":0.902, "796":0.873, "797":0.925, "798":0.853, "799":0.873, "800":0.934, "801":0.918, "802":0.873, "803":0.807, "804":0.721, "805":0.877, "806":0.877, "807":0.82, "808":0.874, "809":0.794, "810":0.583, "811":0.863, "812":0.927, "813":0.887, "814":0.895, "815":0.647, "816":0.858, "817":0.768, "818":0.925, "819":0.934, "820":0.877, "821":0.82, "822":0.897, "823":0.876, "824":0.92, "825":0.877, "826":0.928, "827":0.722, "828":0.768, "829":0.906, "830":0.809, "831":0.486, "832":0.594, "833":0.569, "834":0.909, "835":0.922, "836":0.904, "837":0.579, "838":0.936, "839":0.839, "840":0.571, "841":0.578, "842":0.827, "843":0.839, "844":0.887, "845":0.898, "846":0.892, "847":0.9, "848":0.609, "849":0.856, "850":0.851, "851":0.815, "852":0.838, "853":0.876, "854":0.836, "855":0.87, "856":0.835, "857":0.865, "858":0.85, "859":0.894, "860":0.922, "861":0.852, "862":0.859, "863":0.901, "864":0.874, "865":0.541, "866":0.582, "867":0.902, "868":0.874, "869":0.88, "870":0.877, "871":0.813, "872":0.914, "873":0.898, "874":0.832, "875":0.834, "876":0.737, "877":0.77, "878":0.748, "879":0.801, "880":0.802, "881":0.881, "882":0.905, "883":0.908, "884":0.816, "885":0.567, "886":0.56, "887":0.565, "888":0.816, "889":0.908, "890":0.636, "891":0.898, "892":0.716, "893":0.909, "894":0.903, "895":0.893, "896":0.866, "897":0.906, "898":0.911, "899":0.866, "900":0.861, "901":0.889, "902":0.856, "903":0.781, "904":0.894, "905":0.857, "906":0.889, "907":0.902, "908":0.864, "909":0.856, "910":0.866, "911":0.822, "912":0.763, "913":0.908, "914":0.891, "915":0.876, "916":0.916, "917":0.605, "918":0.772, "919":0.723, "920":0.736, "921":0.639, "922":0.49, "923":0.421, "924":0.916, "925":0.63, "926":0.826, "927":0.926, "928":0.656, "929":0.637, "930":0.66, "931":0.765, "932":0.73, "933":0.696, "934":0.882, "935":0.413, "936":0.568, "937":0.834, "938":0.75, "939":0.602, "940":0.518, "941":0.73, "942":0.632, "943":0.538, "944":0.641, "945":0.9, "946":0.61, "947":0.534, "948":0.658, "949":0.522, "950":0.65, "951":0.632, "952":0.484, "953":0.495, "954":0.68, "955":0.795, "956":0.613, "957":0.613, "958":0.606, "959":0.775, "960":0.805, "961":0.893, "962":0.829, "963":0.882, "964":0.712, "965":0.712, "966":0.747, "967":0.747, "968":0.74, "969":0.527, "970":0.846, "971":0.758, "972":0.829, "973":0.829, "974":0.799, "975":0.799, "976":0.621, "977":0.621, "978":0.803, "979":0.816, "980":0.795, "981":0.922, "982":0.739, "983":0.737, "984":0.738, "985":0.547, "986":0.884, "987":0.86, "988":0.834, "989":0.666, "990":0.635, "991":0.879, "992":0.691, "993":0.747, "994":0.8, "995":0.761, "996":0.849, "997":0.915, "998":0.753, "999":0.936, "1000":0.624, "1001":0.793, "1002":0.722, "1003":0.855, "1004":0.818, "1005":0.818, "1006":0.759, "1007":0.721, "1008":0.625, "1009":0.884, "1010":0.856, "1011":0.635, "1012":0.735, "1013":0.735, "1014":0.735, "1015":0.629, "1016":0.696, "1017":0.75, "1018":0.756, "1019":0.831, "1020":0.831, "1021":0.831, "1022":0.691, "1023":0.892, "1024":0.812, "1025":0.609, "1026":0.932, "1027":0.945, "1028":0.601, "1029":0.55, "1030":0.802, "1031":0.87, "1032":0.874, "1033":0.871, "1034":0.589, "1035":0.92, "1036":0.912, "1037":0.734, "1038":0.937, "1039":0.946, "1040":0.95, "1041":0.776, "1042":0.851, "1043":0.514, "1044":0.584, "1045":0.584, "1046":0.489, "1047":0.961, "1048":0.923, "1049":0.953, "1050":0.944, "1051":0.765, "1052":0.695, "1053":0.928, "1054":0.928, "1055":0.842, "1056":0.723, "1057":0.823, "1058":0.715}; // ── score → tier ── function tier(s) { @@ -359,10 +1418,18 @@ document.getElementById('search').addEventListener('input', e => { }); // ── Boot ── -document.getElementById('katex-script').addEventListener('load', () => { - buildTable(); - loadFontsAndWasm(); -}); +// Both katex.min.js and mhchem.min.js are defer-loaded in DOM order. +// katex fires its load event before mhchem has executed, so \ce / \pu macros +// are not yet registered at that point. Wait for both before building the table. +let _katexReady = false, _mhchemReady = false; +function _tryBoot() { + if (_katexReady && _mhchemReady) { + buildTable(); + loadFontsAndWasm(); + } +} +document.getElementById('katex-script').addEventListener('load', () => { _katexReady = true; _tryBoot(); }); +document.getElementById('mhchem-script').addEventListener('load', () => { _mhchemReady = true; _tryBoot(); }); async function loadFontsAndWasm() { // Pre-load fonts so canvas renders use KaTeX glyphs, not fallback serif diff --git a/website/src/i18n/locales/en.ts b/website/src/i18n/locales/en.ts index f4681e28..f39d18ac 100644 --- a/website/src/i18n/locales/en.ts +++ b/website/src/i18n/locales/en.ts @@ -144,6 +144,7 @@ export const en = { "Dart FFI to `libratex_ffi`; `CustomPainter` renders the display list. Prebuilt iOS XCFramework + Android `.so` on pub.dev.", steps: [ "Add `ratex_flutter` to `pubspec.yaml` and run `flutter pub get`.", + "Register KaTeX fonts in your app's `pubspec.yaml` under `flutter: fonts:` using the `packages/ratex_flutter/` asset prefix — without this step glyphs silently fall back to system fonts. See the full doc for the complete snippet.", "Use `RaTeXWidget(latex: r'…', fontSize: 28)`.", ], }, @@ -157,12 +158,12 @@ export const en = { ], }, { - title: "Server / CLI (PNG)", + title: "Server / CLI", blurb: - "Rasterize the same display list to PNG with tiny-skia—CI snapshots, backends, or headless servers—no browser.", + "Rasterize to PNG with tiny-skia (`ratex-render`) or export to self-contained SVG with `ratex-svg`—CI snapshots, backends, or headless servers—no browser needed.", steps: [ - "From the repo root, use the `ratex-render` crate (see README \u201cRender to PNG\u201d).", - "Pipe LaTeX stdin or pass flags as documented; output is a PNG file or stdout.", + "PNG: pipe LaTeX to stdin — `cargo run --release -p ratex-render`.", + "SVG: add `--features cli` — `cargo run --release -p ratex-svg --features cli`. Outputs ``-based SVG with no web-font dependency.", ], }, ], @@ -178,7 +179,7 @@ export const en = { suggestedOrderDescMid: "for one formula, open the", suggestedOrderTableLink: "support table", suggestedOrderDescSuffix: - "to scan all 916 lines, then use galleries when you want categorized scrolling.", + "to scan all 1058 lines, then use galleries when you want categorized scrolling.", howItLoadsLabel: "How it loads:", howItLoadsDesc: "KaTeX 0.16.9 CSS/JS from jsDelivr. RaTeX uses this site\u2019s platforms/web/ (WASM + fonts). On GitHub Pages that ships with the deployment; locally, build WASM and use the dev server\u2014see", @@ -189,7 +190,7 @@ export const en = { "Edit one LaTeX line and compare RaTeX canvas output with KaTeX side by side\u2014status, errors, and the same WASM bundle as the galleries.", liveComparisonCta: "Open interactive demo", supportTableTitle: "Support table", - supportTableSubtitle: "916 golden formulas", + supportTableSubtitle: "1058 golden formulas", supportTableBody: "Opens the full-page benchmark: every golden-suite line vs KaTeX 0.16.9, with batch IoU scores and a live RaTeX column in your browser\u2014best for coverage and regression triage.", supportTableCta: "Open full support table", @@ -227,7 +228,7 @@ export const en = { supportTable: { eyebrow: "Benchmarks", heading: "Formula support table", - desc: "RaTeX (Rust + WASM) vs KaTeX 0.16.9 across 916 golden-suite lines. Offline cells use pre-computed ink IoU vs KaTeX reference PNGs; the RaTeX column is computed live in your browser from the loaded WASM.", + desc: "RaTeX (Rust + WASM) vs KaTeX 0.16.9 across 1058 golden-suite lines (includes mhchem \\ce / \\pu). Offline cells use pre-computed ink IoU vs KaTeX reference PNGs; the RaTeX column is computed live in your browser from the loaded WASM.", dataSourceLabel: "Data source", dataSourceDescPrefix: "Batch offline scores and aggregate counts are regenerated in CI runs and may lag the latest", diff --git a/website/src/i18n/locales/zh.ts b/website/src/i18n/locales/zh.ts index 91933ddd..91be7dc2 100644 --- a/website/src/i18n/locales/zh.ts +++ b/website/src/i18n/locales/zh.ts @@ -145,6 +145,7 @@ export const zh: TranslationDict = { "Dart FFI 链接到 `libratex_ffi`;`CustomPainter` 渲染显示列表。预构建的 iOS XCFramework + Android `.so` 在 pub.dev 上。", steps: [ "在 `pubspec.yaml` 中添加 `ratex_flutter` 并运行 `flutter pub get`。", + "在应用的 `pubspec.yaml` 的 `flutter: fonts:` 节中用 `packages/ratex_flutter/` 资源前缀注册 KaTeX 字体——缺少此步骤字形将静默回退到系统字体。完整声明片段见文档。", "使用 `RaTeXWidget(latex: r'…', fontSize: 28)`。", ], }, @@ -158,12 +159,12 @@ export const zh: TranslationDict = { ], }, { - title: "Server / CLI (PNG)", + title: "Server / CLI", blurb: - "使用 tiny-skia 将相同的显示列表光栅化为 PNG——CI 快照、后端或无头服务器——无需浏览器。", + "使用 tiny-skia 光栅化为 PNG(`ratex-render`)或用 `ratex-svg` 导出自包含 SVG——CI 快照、后端或无头服务器——无需浏览器。", steps: [ - "从仓库根目录使用 `ratex-render` crate(见 README\u201c渲染为 PNG\u201d)。", - "通过管道传入 LaTeX 标准输入或按文档传递参数;输出为 PNG 文件或标准输出。", + "PNG:将 LaTeX 通过管道传入标准输入 — `cargo run --release -p ratex-render`。", + "SVG:添加 `--features cli` — `cargo run --release -p ratex-svg --features cli`,输出基于 `` 的 SVG,无需网络字体依赖。", ], }, ], @@ -177,7 +178,7 @@ export const zh: TranslationDict = { suggestedOrderLiveLink: "实时对比", suggestedOrderDescMid: "开始测试单个公式,然后打开", suggestedOrderTableLink: "支持表", - suggestedOrderDescSuffix: "扫描全部 916 行,最后在需要分类浏览时使用图库。", + suggestedOrderDescSuffix: "扫描全部 1058 行,最后在需要分类浏览时使用图库。", howItLoadsLabel: "加载方式:", howItLoadsDesc: "KaTeX 0.16.9 CSS/JS 来自 jsDelivr。RaTeX 使用本站的 platforms/web/(WASM + 字体)。在 GitHub Pages 上会随部署一起发布;本地请构建 WASM 并使用开发服务器——见", @@ -188,7 +189,7 @@ export const zh: TranslationDict = { "编辑一行 LaTeX 并并排比较 RaTeX Canvas 输出与 KaTeX——状态、错误以及与图库相同的 WASM 包。", liveComparisonCta: "打开交互演示", supportTableTitle: "支持表", - supportTableSubtitle: "916 个基准公式", + supportTableSubtitle: "1058 个基准公式", supportTableBody: "打开全页基准测试:每个基准测试套件行与 KaTeX 0.16.9 对比,批量 IoU 分数以及浏览器中实时的 RaTeX 列——最适合覆盖率和回归分类。", supportTableCta: "打开完整支持表", @@ -226,7 +227,7 @@ export const zh: TranslationDict = { supportTable: { eyebrow: "基准测试", heading: "公式支持表", - desc: "RaTeX(Rust + WASM)与 KaTeX 0.16.9 对比 916 个基准测试套件行。离线格使用预计算的墨水 IoU 与 KaTeX 参考 PNG 对比;RaTeX 列由您浏览器中加载的 WASM 实时计算。", + desc: "RaTeX(Rust + WASM)与 KaTeX 0.16.9 对比 1058 个基准测试套件行(含 mhchem \\ce / \\pu)。离线格使用预计算的墨水 IoU 与 KaTeX 参考 PNG 对比;RaTeX 列由您浏览器中加载的 WASM 实时计算。", dataSourceLabel: "数据来源", dataSourceDescPrefix: "批量离线分数和聚合计数在 CI 运行中重新生成,可能比最新的", diff --git a/website/src/pages/demo/support-table.astro b/website/src/pages/demo/support-table.astro index 5ce06a2f..72fd71e1 100644 --- a/website/src/pages/demo/support-table.astro +++ b/website/src/pages/demo/support-table.astro @@ -18,7 +18,7 @@ const filterIdle = + crossorigin="anonymous" + id="mhchem-script">