From 6f7853e6bc3dedefd25e2276b962896719e2149a Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Tue, 27 Oct 2026 13:30:12 -0500 Subject: [PATCH 1/2] Fix(tui): math = "latex" says plainly that TeX is yours to install zn draws LaTeX math with pdftex and pdftocairo from the user's own computer and ships neither, but nothing said so where it mattered. With the tools missing, every LaTeX block quietly showed its source: the preview gave no reason, and zn doctor named the tools without saying how to get them. The README covered macOS only. Now the first LaTeX block of a note carries a notice that names the missing tools, says zn does not include them and points at zn doctor; later blocks of the note stay quiet, so a page of formulas is not a page of warnings. Switching to latex in the settings without the tools raises the same message as a toast. zn doctor's failing check says zn does not include the tools and prints the install commands for the system it runs on (macOS, Debian and Ubuntu, Fedora, Arch, Windows, or a generic line), also as a hint list in --json. The README, the in-app help, the starter config.toml and the Homebrew caveats say the same, with copy-paste commands for each platform. The Debian/Ubuntu, Fedora and Arch commands were run in clean Ubuntu 24.04, Fedora 42 and Arch containers, where the resulting pdftex and pdftocairo rendered zn's default preamble. The Windows line (MiKTeX and scoop's poppler) is not tested here. --- README.md | 100 +++++++++++++++++----- internal/cli/commands_diagnostics.go | 67 ++++++++++++++- internal/cli/commands_diagnostics_test.go | 51 +++++++++++ internal/config/prefs.go | 5 +- internal/tui/app.go | 3 + internal/tui/embeds.go | 39 ++++++++- internal/tui/helpdata.go | 2 +- internal/tui/latex_notice_test.go | 77 +++++++++++++++++ internal/tui/preview.go | 1 + internal/tui/settings_live.go | 6 ++ packaging/homebrew/Formula/zn.rb | 12 +-- 11 files changed, 331 insertions(+), 32 deletions(-) create mode 100644 internal/tui/latex_notice_test.go diff --git a/README.md b/README.md index 0861c09..6821531 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,10 @@ different connection and sync options. `zn` is a single static binary with no runtime dependencies. Pick one: +> One optional feature is the exception: drawing LaTeX math as pictures +> (`math = "latex"`) runs TeX installed on your own computer, and zn does not +> include it. See [LaTeX math needs TeX on your computer](#latex-math-needs-tex-on-your-computer). + **Homebrew (macOS and Linux).** Install from the same tap as the desktop app: ```bash @@ -624,31 +628,85 @@ sheet of your own, or `zen` for the built-in renderer). - **`![[Note]]`** renders the note inline. - **Mermaid** blocks draw as text through a pure-Go renderer, or as pictures when `mmdc` is installed. -- **`[terminal] math`** picks the engine for display math. `typst`, the - default, renders `$$` blocks when `typst` is installed. `latex` also renders - `\[ \]`, `tikzpicture` and the amsmath display environments (`equation`, - `align`, `gather`, `multline` and the rest, starred or not), and ` ```tikz ` - fences the way the desktop app draws them (`\usepackage` and - `\usetikzlibrary` lines above the picture are kept). It needs `pdftex` and - `pdftocairo` (poppler) on PATH; `zn doctor` names a missing one. A basic TeX - Live is enough: add the packages zn uses with - `tlmgr install standalone varwidth preview xcolor amsmath amsfonts mhchem pgf` - (or your distribution's TeX packages), and others later as your preamble - needs them. Packages come from `preamble.tex` next to `config.toml`, written - on the first render with `amsmath`, `amssymb`, `mhchem` and `tikz` with the - TikZ libraries the desktop loads (`arrows.meta`, `calc`, `positioning`, - `shapes`, `decorations.pathreplacing`, `intersections`, `patterns`); zn - never rewrites it. The desktop also loads `pgfplots` and `circuitikz` when a - fence uses them; for those, install them and add them to `preamble.tex`. - Pictures are cached in the user cache directory under `zn/tex`; zn deletes - the pictures and formats there that it has not used for 30 days. TeX can - read files by absolute path, so under `latex` a note can show in the preview - the contents of any file you can read. Leave latex off for vaults you do not - trust. +- **`[terminal] math`** picks the engine for display math: `typst`, the + default, renders `$$` blocks when `typst` is installed; `latex` uses TeX on + your computer (next section). - **`render_timeout`** (seconds, default 10) bounds one diagram or math render; **`render_concurrency`** (default 2) caps how many run at once and takes effect after a restart. +### LaTeX math needs TeX on your computer + +**zn does not include TeX.** `math = "latex"` runs two programs you install +yourself: `pdftex`, from a TeX distribution (TeX Live, MacTeX, MiKTeX), and +`pdftocairo`, from poppler. Until both are on your PATH, LaTeX blocks show +their source and the preview says which one is missing. `zn doctor` checks for +them and prints the install commands for your system. + +macOS, with Homebrew: + +```bash +brew install --cask basictex +brew install poppler +eval "$(/usr/libexec/path_helper)" # or open a new terminal +sudo tlmgr update --self +sudo tlmgr install standalone varwidth preview xcolor amsmath amsfonts mhchem pgf +``` + +Debian and Ubuntu: + +```bash +sudo apt install texlive-latex-extra texlive-science texlive-pictures poppler-utils +``` + +Fedora: + +```bash +sudo dnf install texlive-scheme-basic texlive-standalone texlive-varwidth texlive-preview texlive-xcolor texlive-amsmath texlive-amsfonts texlive-mhchem texlive-pgf poppler-utils +``` + +Arch: + +```bash +sudo pacman -S texlive-basic texlive-latex texlive-latexextra texlive-science texlive-pictures poppler +``` + +Windows: install [MiKTeX](https://miktex.org), which fetches missing LaTeX +packages on first use, and poppler for `pdftocairo`, for example with +`scoop install poppler`. + +Then turn it on in `config.toml` (or under `:settings`) and check: + +```toml +[terminal] +math = "latex" +``` + +```bash +zn doctor +``` + +Pictures need a terminal that shows images (Kitty, Ghostty); others show the +source. + +What it draws: `$$` and `\[ \]` blocks, `tikzpicture` blocks, the amsmath +display environments (`equation`, `align`, `gather`, `multline` and the rest, +starred or not), and ` ```tikz ` fences the way the desktop app draws them +(`\usepackage` and `\usetikzlibrary` lines above the picture are kept). +Packages come from `preamble.tex` next to `config.toml`, written on the first +render with `amsmath`, `amssymb`, `mhchem` and `tikz` with the TikZ libraries +the desktop loads (`arrows.meta`, `calc`, `positioning`, `shapes`, +`decorations.pathreplacing`, `intersections`, `patterns`); zn never rewrites +it, so add your own packages there (and install them with your TeX +distribution). The desktop also loads `pgfplots` and `circuitikz` when a fence +uses them; for those, install them and add them to `preamble.tex`. + +Pictures are cached in the user cache directory under `zn/tex`; zn deletes +the pictures and formats there that it has not used for 30 days. TeX can read +files by absolute path, so under `latex` a note can show in the preview the +contents of any file you can read. Leave latex off for vaults you do not +trust. + ### Vaults and servers inside the app `:vault` (or `Space v`) lists every saved vault and server, with entries to diff --git a/internal/cli/commands_diagnostics.go b/internal/cli/commands_diagnostics.go index 433e312..b7b9c89 100644 --- a/internal/cli/commands_diagnostics.go +++ b/internal/cli/commands_diagnostics.go @@ -113,6 +113,65 @@ type diagnosticCheck struct { Name string `json:"name"` OK bool `json:"ok"` Message string `json:"message"` + // Hint is what to run to fix a failed check, one command or line each. + Hint []string `json:"hint,omitempty"` +} + +// latexPackages are the LaTeX packages zn's render document and default +// preamble.tex load; a basic TeX install lacks some of them. +const latexPackages = "standalone varwidth preview xcolor amsmath amsfonts mhchem pgf" + +// latexInstallHint is how to install what math = "latex" runs, for this +// system: zn ships no TeX. distro is /etc/os-release's ID and ID_LIKE. +// Each Linux line was checked in a clean container of that distribution. +func latexInstallHint(goos, distro string) []string { + like := func(names ...string) bool { + for _, f := range strings.Fields(distro) { + for _, n := range names { + if f == n { + return true + } + } + } + return false + } + switch { + case goos == "darwin": + return []string{ + "brew install --cask basictex", + "brew install poppler", + `eval "$(/usr/libexec/path_helper)" # or open a new terminal`, + "sudo tlmgr update --self", + "sudo tlmgr install " + latexPackages, + } + case goos == "windows": + return []string{ + "Install MiKTeX (https://miktex.org); it fetches missing LaTeX packages on first use.", + "Install poppler for pdftocairo, for example: scoop install poppler", + } + case like("debian", "ubuntu"): + return []string{"sudo apt install texlive-latex-extra texlive-science texlive-pictures poppler-utils"} + case like("fedora", "rhel", "centos"): + return []string{"sudo dnf install texlive-scheme-basic texlive-standalone texlive-varwidth texlive-preview texlive-xcolor texlive-amsmath texlive-amsfonts texlive-mhchem texlive-pgf poppler-utils"} + case like("arch"): + return []string{"sudo pacman -S texlive-basic texlive-latex texlive-latexextra texlive-science texlive-pictures poppler"} + } + return []string{"Install TeX Live with the packages " + latexPackages + ", and poppler for pdftocairo."} +} + +// osDistro is /etc/os-release's ID and ID_LIKE, space separated, or "". +func osDistro() string { + b, err := os.ReadFile("/etc/os-release") + if err != nil { + return "" + } + ids := []string{} + for _, line := range strings.Split(string(b), "\n") { + if k, v, ok := strings.Cut(line, "="); ok && (k == "ID" || k == "ID_LIKE") { + ids = append(ids, strings.Trim(v, `"'`)) + } + } + return strings.Join(ids, " ") } func cmdDoctor(ctx context.Context, args Args) error { @@ -134,9 +193,12 @@ func cmdDoctor(ctx context.Context, args Args) error { } var err error if len(missing) > 0 { - err = fmt.Errorf("math = \"latex\" needs %s on PATH", strings.Join(missing, " and ")) + err = fmt.Errorf("math = \"latex\" needs %s on PATH; zn does not include them, so install them on this computer", strings.Join(missing, " and ")) } add("latex tools", "pdftex and pdftocairo are available", err) + if err != nil { + checks[len(checks)-1].Hint = latexInstallHint(runtime.GOOS, osDistro()) + } } ws, wsErr := config.LoadWorkspacesFile() add("saved vaults", fmt.Sprintf("%d vaults and %d servers in %s", len(ws.Vaults), len(ws.Servers), config.WorkspacesPath()), wsErr) @@ -199,6 +261,9 @@ func cmdDoctor(ctx context.Context, args Args) error { state = "FAIL" } emitLine(fmt.Sprintf("%s %s: %s", state, check.Name, check.Message)) + for _, line := range check.Hint { + emitLine(" " + line) + } } } if failed != 0 { diff --git a/internal/cli/commands_diagnostics_test.go b/internal/cli/commands_diagnostics_test.go index 6ba2dd7..91c387a 100644 --- a/internal/cli/commands_diagnostics_test.go +++ b/internal/cli/commands_diagnostics_test.go @@ -91,3 +91,54 @@ func TestDoctorTypstNeverMentionsTexTools(t *testing.T) { } } } + +// A failed latex check carries this system's install steps, and doctor +// prints them under the check. +func TestDoctorLatexPrintsInstallSteps(t *testing.T) { + out, checks := doctorWithTools(t, `math = "latex"`) + c := latexToolsCheck(t, checks, out) + if c.OK || len(c.Hint) == 0 { + t.Fatalf("missing tools gave no install steps: %+v", c) + } + if !strings.Contains(c.Message, "zn does not include them") { + t.Fatalf("message does not say zn ships no TeX: %q", c.Message) + } + isolatedCLI(t) + text := captureOutput(t, func() { + _ = os.MkdirAll(filepath.Dir(config.ConfigTomlPath()), 0o755) + _ = os.WriteFile(config.ConfigTomlPath(), []byte("[terminal]\nmath = \"latex\"\n"), 0o644) + t.Setenv("PATH", t.TempDir()) + _ = Main([]string{"doctor"}) + }) + if !strings.Contains(text, " "+c.Hint[0]) { + t.Fatalf("doctor did not print the install steps under the check:\n%s", text) + } +} + +func TestDoctorLatexWithToolsHasNoInstallSteps(t *testing.T) { + out, checks := doctorWithTools(t, `math = "latex"`, "pdftex", "pdftocairo") + if c := latexToolsCheck(t, checks, out); !c.OK || len(c.Hint) != 0 { + t.Fatalf("tools present, yet: %+v", c) + } +} + +func TestLatexInstallHintPerSystem(t *testing.T) { + for _, tc := range []struct{ goos, distro, want string }{ + {"darwin", "", "brew install --cask basictex"}, + {"darwin", "", "sudo tlmgr install " + latexPackages}, + {"linux", "ubuntu debian", "sudo apt install texlive-latex-extra"}, + {"linux", "pop ubuntu debian", "sudo apt install"}, + {"linux", "debian", "poppler-utils"}, + {"linux", "fedora", "sudo dnf install texlive-scheme-basic"}, + {"linux", "rocky rhel centos fedora", "sudo dnf install"}, + {"linux", "manjaro arch", "sudo pacman -S texlive-basic"}, + {"linux", "opensuse-tumbleweed opensuse suse", "packages " + latexPackages}, + {"linux", "", "poppler for pdftocairo"}, + {"windows", "", "MiKTeX"}, + {"freebsd", "", "TeX Live"}, + } { + if got := strings.Join(latexInstallHint(tc.goos, tc.distro), "\n"); !strings.Contains(got, tc.want) { + t.Errorf("%s %q: hint lacks %q:\n%s", tc.goos, tc.distro, tc.want, got) + } + } +} diff --git a/internal/config/prefs.go b/internal/config/prefs.go index cd48217..cffb461 100644 --- a/internal/config/prefs.go +++ b/internal/config/prefs.go @@ -368,8 +368,9 @@ mouse = true # [appearance], so the terminal matches the desktop app. theme = "" # math = typst | latex: the engine for display math in the preview. latex -# needs pdftex (TeX Live or MacTeX) and pdftocairo (poppler) on PATH, and -# reads its packages from preamble.tex next to this file. +# runs TeX on this computer, which zn does not include: install pdftex +# (TeX Live, MacTeX or MiKTeX) and pdftocairo (poppler) yourself; zn doctor +# prints the commands. Its packages come from preamble.tex next to this file. math = "typst" # Seconds one diagram or math render may take, and how many run at once. render_timeout = 10 diff --git a/internal/tui/app.go b/internal/tui/app.go index a06e6ae..bea4663 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -143,6 +143,9 @@ type App struct { assetPaths map[string]string previewDepth int previewPath string + // latexNoticeShown is set once the current preview render has shown the + // missing-TeX notice, so only a note's first LaTeX block carries it. + latexNoticeShown bool systemDark bool exHistory []string diff --git a/internal/tui/embeds.go b/internal/tui/embeds.go index 252d2bb..1d9d91e 100644 --- a/internal/tui/embeds.go +++ b/internal/tui/embeds.go @@ -495,11 +495,15 @@ func (a *App) fitted(key string, img *termImage, maxCols int) *termImage { // latexErrorRows is the first line of a failed latex render, wrapped rather // than truncated because the cause sits at the end of long paths. A render -// that worked, is still running or lacks its tools has none. +// that worked or is still running has none. Missing tools get the install +// notice instead, once per note. func (a *App) latexErrorRows(img *termImage, srcLine, width int) []previewLine { - if img == nil || img.err == nil || errors.Is(img.err, exec.ErrNotFound) { + if img == nil || img.err == nil { return nil } + if errors.Is(img.err, exec.ErrNotFound) { + return a.latexToolsNotice(srcLine, width) + } out := []previewLine{} for _, row := range wrapCommentText(firstLine(img.err.Error()), width-2) { out = append(out, previewLine{text: " " + a.theme.StatusError.Render(row), srcLine: srcLine}) @@ -507,6 +511,37 @@ func (a *App) latexErrorRows(img *termImage, srcLine, width int) []previewLine { return out } +// missingLatexTools names the tools math = "latex" runs that are not on +// PATH, with where each comes from. +func missingLatexTools() []string { + missing := []string{} + for _, tool := range []struct{ name, from string }{{"pdftex", "TeX Live or MacTeX"}, {"pdftocairo", "poppler"}} { + if _, err := exec.LookPath(tool.name); err != nil { + missing = append(missing, tool.name+" ("+tool.from+")") + } + } + return missing +} + +// latexToolsNotice tells the reader, above the first LaTeX block of a note, +// that math = "latex" runs TeX installed on this machine and which part is +// missing. zn ships no TeX, and without this the blocks just show their +// source with no hint why. Later blocks of the same note stay quiet. +func (a *App) latexToolsNotice(srcLine, width int) []previewLine { + missing := missingLatexTools() + if a.latexNoticeShown || len(missing) == 0 { + return nil + } + a.latexNoticeShown = true + style := lipgloss.NewStyle().Foreground(a.theme.Accent) + text := "LaTeX math needs " + strings.Join(missing, " and ") + " installed on this computer; zn does not include them. Run zn doctor for the install steps." + out := []previewLine{} + for _, row := range wrapCommentText(text, width-2) { + out = append(out, previewLine{text: " " + style.Render(row), srcLine: srcLine}) + } + return out +} + // externalImage returns a rendered picture for a key once a background // render finished; it starts the render on first call and returns nil // until then; a failed render is returned with its err set. Renders need diff --git a/internal/tui/helpdata.go b/internal/tui/helpdata.go index 228f043..2a48e6b 100644 --- a/internal/tui/helpdata.go +++ b/internal/tui/helpdata.go @@ -174,7 +174,7 @@ func helpLines(a *App, filter string) []string { "[terminal] theme\tA theme for zn alone, when the terminal should not match the desktop app", ":style \tReading view style: auto (zennotes, matches the palette), dark, light, dracula, tokyo-night, pink, ascii, notty, a Glamour JSON file, or the built-in zen renderer", "[terminal] preview_style\tThe same setting in config.toml, next to mouse = true|false", - "[terminal] math\tEngine for display math: typst (default) or latex, which also renders \\[ \\], align and tikzpicture blocks and ```tikz fences; it needs pdftex (a basic TeX Live plus the packages the README lists) and pdftocairo (poppler); packages come from preamble.tex next to config.toml. Leave latex off for vaults you do not trust: a note can show any file you can read", + "[terminal] math\tEngine for display math: typst (default) or latex. latex runs TeX on this computer, which zn does not include: install pdftex (TeX Live, MacTeX or MiKTeX) and pdftocairo (poppler) yourself; zn doctor prints the commands for your system. It renders $$, \\[ \\], align and tikzpicture blocks and ```tikz fences, with packages from preamble.tex next to config.toml. Leave latex off for vaults you do not trust: a note can show any file you can read", "[terminal] render_timeout\tSeconds one diagram or math render may take (default 10); render_concurrency caps how many run at once (default 2, after a restart)", "", "## Preview mode", diff --git a/internal/tui/latex_notice_test.go b/internal/tui/latex_notice_test.go new file mode 100644 index 0000000..ebd488b --- /dev/null +++ b/internal/tui/latex_notice_test.go @@ -0,0 +1,77 @@ +package tui + +import ( + "io" + "strings" + "testing" +) + +// noLatexTools puts an empty dir on PATH, so neither pdftex nor pdftocairo +// is found. +func noLatexTools(t *testing.T) { + t.Helper() + latexEnv(t) + t.Setenv("PATH", t.TempDir()) +} + +func TestMissingTexShowsNoticeOnceAboveTheFirstBlock(t *testing.T) { + noLatexTools(t) + a, _ := newDatabaseTestApp(t) + a.prefs = prefsView{Prefs: mathPrefs("latex", "kitty")} + a.imageStore = &imageStore{byKey: map[string]*termImage{}, nextID: 1000, cellW: 10, cellH: 20, out: io.Discard} + watchRenders(t, a) + body := "# Doc\n\n$$\nx^2\n$$\n\nBetween.\n\n\\[\ny^2\n\\]\n" + a.latexNoticeShown = false + out, err := a.renderGlamourLinesFrom(body, 100, "Doc.md") + if err != nil { + t.Fatal(err) + } + all := joinPreview(out) + if n := strings.Count(all, "zn does not include them"); n != 1 { + t.Fatalf("notice shown %d times, want once:\n%s", n, all) + } + for _, want := range []string{"pdftex (TeX Live or MacTeX)", "pdftocairo (poppler)", "zn doctor"} { + if !strings.Contains(strings.Join(strings.Fields(all), " "), want) { + t.Fatalf("notice lacks %q:\n%s", want, all) + } + } + if notice, src := rowIndex(out, "LaTeX math needs"), rowIndex(out, "x^2"); notice < 0 || src < 0 || notice > src { + t.Fatalf("notice is not above the first block (notice %d, source %d):\n%s", notice, src, all) + } + if !strings.Contains(all, "y^2") { + t.Fatalf("the second block's source is gone:\n%s", all) + } +} + +func TestNoNoticeWhenTexIsInstalled(t *testing.T) { + latexEnv(t) + fakeTeX(t, texFake{}) + a, _ := newDatabaseTestApp(t) + a.prefs = prefsView{Prefs: mathPrefs("latex", "kitty")} + a.imageStore = &imageStore{byKey: map[string]*termImage{}, nextID: 1000, cellW: 10, cellH: 20, out: io.Discard} + watchRenders(t, a) + out, err := a.renderGlamourLinesFrom("$$\nx^2\n$$\n", 100, "Doc.md") + if err != nil { + t.Fatal(err) + } + if strings.Contains(joinPreview(out), "does not include") { + t.Fatalf("notice shown with the tools on PATH:\n%s", joinPreview(out)) + } +} + +func TestSwitchingToLatexWithoutTexWarns(t *testing.T) { + noLatexTools(t) + a, _ := newDatabaseTestApp(t) + p := a.rawPref + p.TerminalMath = "latex" + a.applyPreferences(p) + found := false + for _, toast := range a.toasts { + if toast.isErr && strings.Contains(toast.text, "zn doctor") && strings.Contains(toast.text, "pdftex") { + found = true + } + } + if !found { + t.Fatalf("no warning toast after switching to latex without TeX: %+v", a.toasts) + } +} diff --git a/internal/tui/preview.go b/internal/tui/preview.go index b1be58b..8758e6d 100644 --- a/internal/tui/preview.go +++ b/internal/tui/preview.go @@ -475,6 +475,7 @@ func (a *App) previewLinesFor(t *tab, buf *noteBuffer, width int) []previewLine if pv.cache == nil || pv.cache.version != buf.ed.Version() || pv.cache.width != width || pv.cache.style != style || pv.cache.dark != a.theme.Dark { var lines []previewLine a.previewPath = buf.path + a.latexNoticeShown = false if strings.EqualFold(style, "zen") { lines = a.renderPreviewLines(buf.ed.Text(), width) } else { diff --git a/internal/tui/settings_live.go b/internal/tui/settings_live.go index 5197493..31a1857 100644 --- a/internal/tui/settings_live.go +++ b/internal/tui/settings_live.go @@ -10,6 +10,7 @@ import ( "github.com/charmbracelet/x/editor" "os" "reflect" + "strings" "time" ) @@ -29,6 +30,11 @@ func (a *App) applyPreferences(p config.Prefs) { } a.prefs = prefsView{Prefs: p, TabsOff: !p.TabsEnabled} a.rawPref = p + if old.TerminalMath != "latex" && p.TerminalMath == "latex" { + if missing := missingLatexTools(); len(missing) > 0 { + a.notifyError("math = latex needs " + strings.Join(missing, " and ") + " installed on this computer; zn does not include them. Run zn doctor for the install steps.") + } + } a.keymap = keymaps.NewResolver(p.KeymapOverrides) if err := a.setTheme(sel); err != nil || selErr != nil { a.notifyError(errors.Join(selErr, err).Error()) diff --git a/packaging/homebrew/Formula/zn.rb b/packaging/homebrew/Formula/zn.rb index 0a0c7ca..04a06c9 100644 --- a/packaging/homebrew/Formula/zn.rb +++ b/packaging/homebrew/Formula/zn.rb @@ -31,12 +31,14 @@ def install def caveats <<~EOS - zn renders display math with typst by default. With math = "latex" under - [terminal] in config.toml, it needs pdftex and pdftocairo on PATH. A - basic TeX Live is enough, plus poppler and the packages zn uses: + zn renders display math with typst by default. To draw LaTeX math instead + (math = "latex" under [terminal] in config.toml), zn runs pdftex and + pdftocairo installed on your computer; it does not include TeX. On macOS: + brew install --cask basictex brew install poppler - tlmgr install standalone varwidth preview xcolor amsmath amsfonts mhchem pgf - Add other packages with tlmgr later, as your preamble.tex needs them. + sudo tlmgr install standalone varwidth preview xcolor amsmath amsfonts mhchem pgf + On Linux, install your distribution's TeX Live and poppler packages. + Run zn doctor to check; it prints the commands for your system. EOS end From 3f1f638a555450891d378b4b67ab7d9eae2f5226 Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Tue, 27 Oct 2026 13:30:12 -0500 Subject: [PATCH 2/2] Docs: keep the embedded-LaTeX coverage audit for later Before deciding whether zn could draw LaTeX math without the user installing TeX, a 124-case corpus ran through zn 0.12.0's pdftex path (the baseline, 120 render), d2lang/mathjax-go and the desktop's node-tikzjax engine. Neither embedded option matches the TeX path without real work: mathjax-go draws 68 of 86 math cases and drops equation numbers silently; tikzjax draws 75 of 86 math and 33 of 34 TikZ, lacks mhchem and crashes on a missing bold-math font. The decision is to keep using the user's own TeX. This folder keeps the corpus, the harness to rerun all three engines and the per-case results, with the numbers and the route to take if the question comes back. The Go harness files end in .txt so they stay out of zn's module. --- docs/embedded-latex-coverage/README.md | 66 ++ .../harness/Dockerfile.texlive | 2 + .../harness/corpus.json | 750 +++++++++++++ .../embedded-latex-coverage/harness/corpus.py | 144 +++ .../harness/mathjax-go.mod | 15 + .../harness/mathjax-go.sum | 13 + .../harness/mathjax-run.go.txt | 108 ++ .../harness/pdftex-docker-wrapper.sh | 6 + .../harness/pdftex_coverage_test.go.txt | 57 + .../harness/tikzjax-run.cjs | 59 ++ .../results/mathjax-go.json | 632 +++++++++++ .../results/pdftex.json | 994 ++++++++++++++++++ .../results/tikzjax.json | 994 ++++++++++++++++++ 13 files changed, 3840 insertions(+) create mode 100644 docs/embedded-latex-coverage/README.md create mode 100644 docs/embedded-latex-coverage/harness/Dockerfile.texlive create mode 100644 docs/embedded-latex-coverage/harness/corpus.json create mode 100644 docs/embedded-latex-coverage/harness/corpus.py create mode 100644 docs/embedded-latex-coverage/harness/mathjax-go.mod create mode 100644 docs/embedded-latex-coverage/harness/mathjax-go.sum create mode 100644 docs/embedded-latex-coverage/harness/mathjax-run.go.txt create mode 100755 docs/embedded-latex-coverage/harness/pdftex-docker-wrapper.sh create mode 100644 docs/embedded-latex-coverage/harness/pdftex_coverage_test.go.txt create mode 100644 docs/embedded-latex-coverage/harness/tikzjax-run.cjs create mode 100644 docs/embedded-latex-coverage/results/mathjax-go.json create mode 100644 docs/embedded-latex-coverage/results/pdftex.json create mode 100644 docs/embedded-latex-coverage/results/tikzjax.json diff --git a/docs/embedded-latex-coverage/README.md b/docs/embedded-latex-coverage/README.md new file mode 100644 index 0000000..11841cd --- /dev/null +++ b/docs/embedded-latex-coverage/README.md @@ -0,0 +1,66 @@ +# Embedded LaTeX: coverage audit (2026-10-27, parked) + +Question: could zn render `math = "latex"` without the user installing TeX, +and would users lose anything? Decision: **not now**. zn keeps using the +user's own `pdftex` and `pdftocairo`; this folder keeps the evidence for a +later look. + +## Method + +`harness/corpus.py` writes `harness/corpus.json`: 90 math and 34 TikZ cases +covering every display form zn detects, amsmath, amssymb, mhchem, macros +defined in a block, colour, TikZ with each default preamble library, and +fences that add their own `\usetikzlibrary` or `\usepackage`. Each case ran +through: + +- zn 0.12.0's own latex path (`latexPNG`, TeX Live 2026 basic plus the + README's `tlmgr` list, in Docker): the baseline, 120 of 124 render. +- `github.com/d2lang/mathjax-go` v0.1.0 (math only). +- node-tikzjax 1.0.5, the desktop's TikZ engine (e-TeX in WebAssembly), + configured as `apps/desktop/src/main/tikz.ts` calls it; math wrapped in a + TikZ node. + +Results per case are in `results/`. Every case was also compared visually +against the TeX picture (pages built from the outputs; regenerate them by +rerunning the harness). + +## Results (of what the TeX path renders) + +| | Math (86) | TikZ (34) | +|---|---|---| +| mathjax-go | 68 | none | +| tikzjax | 75 | 33 | +| either | 83 | 33 | + +- **mathjax-go** misses `\newcommand`/`\renewcommand`/`\def`, `eqnarray`, + `subequations`, `\intertext`, `\hline` in arrays, `\pmb`, `\pod`, + `\mathstrut`, `\leftroot`, `\overleftrightarrow`, mhchem `<=>>` and `&` + inside `\ce`. Silent differences: no automatic equation numbers, wrong + `alignat` spacing, literal `$` inside `\colorbox`. About 0.16 ms a block. + oksvg rendered none of its SVG. +- **tikzjax** misses mhchem (not in its bundle), crashes on `\pmb` and + `\boldsymbol` (missing font `cmmib5`; the throw escapes its promise and + kills the Node process, which matters for the desktop too), and breaks on + `\substack`. Silent differences: `patterns` render empty, `matrix of nodes` + loses node borders. About 0.36 s a block including Node start-up. +- Nothing embedded covers packages a user adds to `preamble.tex`. + +## If this is reopened + +The strongest route was one embedded engine (tikzjax's wasm under wazero) +with mhchem and the bold-math fonts added, a Go DVI-to-PNG renderer, and the +installed `pdftex` as automatic fallback for custom preambles. Open +questions: wazero speed, renderer effort, and dvi2html's unclear licence. + +## Rerunning + +- TeX: build `harness/Dockerfile.texlive` as `zn-texlive-readme`, put + `harness/pdftex-docker-wrapper.sh` on PATH as `pdftex`, and use + `harness/pdftex_coverage_test.go.txt`. +- mathjax-go: copy `harness/mathjax-run.go.txt` to `main.go` with + `mathjax-go.mod`/`.sum` as `go.mod`/`go.sum` in an empty folder; + `go run . corpus.json `. (The `.txt` names keep these files out of + zn's own module.) +- tikzjax: `node harness/tikzjax-run.cjs corpus.json [id]` (needs + the desktop repo's `node_modules`; run one id per process, a missing font + kills the process). diff --git a/docs/embedded-latex-coverage/harness/Dockerfile.texlive b/docs/embedded-latex-coverage/harness/Dockerfile.texlive new file mode 100644 index 0000000..fd9be00 --- /dev/null +++ b/docs/embedded-latex-coverage/harness/Dockerfile.texlive @@ -0,0 +1,2 @@ +FROM texlive/texlive:latest-basic +RUN tlmgr install standalone varwidth preview xcolor amsmath amsfonts mhchem pgf diff --git a/docs/embedded-latex-coverage/harness/corpus.json b/docs/embedded-latex-coverage/harness/corpus.json new file mode 100644 index 0000000..609e7a2 --- /dev/null +++ b/docs/embedded-latex-coverage/harness/corpus.json @@ -0,0 +1,750 @@ +{ + "math": [ + { + "id": "m001", + "cat": "form", + "kind": "math", + "src": "$$ x^2 + y^2 = z^2 $$" + }, + { + "id": "m002", + "cat": "form", + "kind": "math", + "src": "\\[ \\int_0^1 x^2\\,dx = \\tfrac13 \\]" + }, + { + "id": "m003", + "cat": "form", + "kind": "math", + "src": "\\begin{equation} E = mc^2 \\end{equation}" + }, + { + "id": "m004", + "cat": "form", + "kind": "math", + "src": "\\begin{equation*} E = mc^2 \\end{equation*}" + }, + { + "id": "m005", + "cat": "form", + "kind": "math", + "src": "\\begin{align} a &= b + c \\\\ d &= e \\end{align}" + }, + { + "id": "m006", + "cat": "form", + "kind": "math", + "src": "\\begin{align*} a &= b + c \\\\ d &= e \\end{align*}" + }, + { + "id": "m007", + "cat": "form", + "kind": "math", + "src": "\\begin{gather} x = 1 \\\\ yy = 22 \\end{gather}" + }, + { + "id": "m008", + "cat": "form", + "kind": "math", + "src": "\\begin{gather*} x = 1 \\\\ yy = 22 \\end{gather*}" + }, + { + "id": "m009", + "cat": "form", + "kind": "math", + "src": "\\begin{multline} a + b + c \\\\ + d + e \\end{multline}" + }, + { + "id": "m010", + "cat": "form", + "kind": "math", + "src": "\\begin{multline*} a + b + c \\\\ + d + e \\end{multline*}" + }, + { + "id": "m011", + "cat": "form", + "kind": "math", + "src": "\\begin{alignat}{2} x &= y & \\quad a &= b \\\\ x' &= y' & a' &= b' \\end{alignat}" + }, + { + "id": "m012", + "cat": "form", + "kind": "math", + "src": "\\begin{alignat*}{2} x &= y & \\quad a &= b \\end{alignat*}" + }, + { + "id": "m013", + "cat": "form", + "kind": "math", + "src": "\\begin{flalign} x &= y & a &= b \\end{flalign}" + }, + { + "id": "m014", + "cat": "form", + "kind": "math", + "src": "\\begin{flalign*} x &= y & a &= b \\end{flalign*}" + }, + { + "id": "m015", + "cat": "form", + "kind": "math", + "src": "\\begin{eqnarray} a &=& b \\\\ c &=& d \\end{eqnarray}" + }, + { + "id": "m016", + "cat": "form", + "kind": "math", + "src": "\\begin{eqnarray*} a &=& b \\end{eqnarray*}" + }, + { + "id": "m017", + "cat": "ams", + "kind": "math", + "src": "\\begin{equation} \\begin{split} a &= b + c \\\\ &= d \\end{split} \\end{equation}" + }, + { + "id": "m018", + "cat": "ams", + "kind": "math", + "src": "$$ f(x) = \\begin{cases} x & x > 0 \\\\ -x & \\text{otherwise} \\end{cases} $$" + }, + { + "id": "m019", + "cat": "ams", + "kind": "math", + "src": "$$ \\begin{aligned} a &= 1 \\\\ b &= 2 \\end{aligned} \\qquad \\begin{gathered} c \\\\ dd \\end{gathered} $$" + }, + { + "id": "m020", + "cat": "ams", + "kind": "math", + "src": "$$ \\begin{pmatrix} 1 & 2 \\\\ 3 & 4 \\end{pmatrix} \\begin{bmatrix} 1 \\\\ 2 \\end{bmatrix} \\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix} $$" + }, + { + "id": "m021", + "cat": "ams", + "kind": "math", + "src": "$$ \\begin{Vmatrix} a \\end{Vmatrix} \\begin{Bmatrix} a \\end{Bmatrix} \\begin{matrix} a & b \\end{matrix} \\left(\\begin{smallmatrix} a & b \\\\ c & d \\end{smallmatrix}\\right) $$" + }, + { + "id": "m022", + "cat": "ams", + "kind": "math", + "src": "\\begin{align} a &= b \\tag{A} \\\\ c &= d \\notag \\\\ e &= f \\nonumber \\end{align}" + }, + { + "id": "m023", + "cat": "ams", + "kind": "math", + "src": "\\begin{equation} x = y \\tag*{(star)} \\end{equation}" + }, + { + "id": "m024", + "cat": "ams", + "kind": "math", + "src": "\\begin{align} a &= b \\\\ \\intertext{and so} c &= d \\end{align}" + }, + { + "id": "m025", + "cat": "ams", + "kind": "math", + "src": "$$ \\sum_{\\substack{0 < i < m \\\\ 0 < j < n}} P(i,j) $$" + }, + { + "id": "m026", + "cat": "ams", + "kind": "math", + "src": "$$ \\overset{!}{=} \\quad \\underset{x}{\\arg\\max} \\quad \\stackrel{\\text{def}}{=} $$" + }, + { + "id": "m027", + "cat": "ams", + "kind": "math", + "src": "$$ A \\xrightarrow[\\text{below}]{\\text{above}} B \\xleftarrow{f} C $$" + }, + { + "id": "m028", + "cat": "ams", + "kind": "math", + "src": "$$ \\boxed{E = mc^2} $$" + }, + { + "id": "m029", + "cat": "ams", + "kind": "math", + "src": "$$ \\frac{a}{b} \\dfrac{a}{b} \\tfrac{a}{b} \\cfrac{1}{1+\\cfrac{1}{x}} $$" + }, + { + "id": "m030", + "cat": "ams", + "kind": "math", + "src": "$$ \\binom{n}{k} \\dbinom{n}{k} \\tbinom{n}{k} \\genfrac{[}{]}{0pt}{}{n}{k} $$" + }, + { + "id": "m031", + "cat": "ams", + "kind": "math", + "src": "$$ \\operatorname{sgn}(x) \\quad \\operatorname*{arg\\,min}_x f(x) $$" + }, + { + "id": "m032", + "cat": "ams", + "kind": "math", + "src": "$$ \\iint_D \\iiint_V \\iiiint \\idotsint \\oint_C $$" + }, + { + "id": "m033", + "cat": "ams", + "kind": "math", + "src": "$$ a_1, \\dotsc, a_n \\quad a_1 + \\dotsb + a_n \\quad \\dotsm \\quad \\dotsi \\quad \\dotso $$" + }, + { + "id": "m034", + "cat": "ams", + "kind": "math", + "src": "$$ a \\bmod b \\quad a \\pmod{n} \\quad a \\mod{n} \\quad a \\pod{n} $$" + }, + { + "id": "m035", + "cat": "ams", + "kind": "math", + "src": "$$ \\overbrace{a+b}^{n} \\quad \\underbrace{c+d}_{m} $$" + }, + { + "id": "m036", + "cat": "ams", + "kind": "math", + "src": "$$ \\left( \\frac{a}{b} \\middle| c \\right) \\bigl( \\Bigl( \\biggl( \\Biggl( x \\Biggr) \\biggr) \\Bigr) \\bigr) $$" + }, + { + "id": "m037", + "cat": "ams", + "kind": "math", + "src": "$$ \\phantom{x} \\hphantom{y} \\vphantom{\\int} \\smash{\\int} \\mathstrut a $$" + }, + { + "id": "m038", + "cat": "ams", + "kind": "math", + "src": "$$ \\lim_{x \\to 0} \\frac{\\sin x}{x} = 1 \\quad \\lim\\limits_{n} \\quad \\sum\\nolimits_k $$" + }, + { + "id": "m039", + "cat": "ams", + "kind": "math", + "src": "$$ \\sideset{_a^b}{_c^d}\\sum \\quad \\leftidx{_1}{A}{_2} $$" + }, + { + "id": "m040", + "cat": "ams", + "kind": "math", + "src": "$$ \\sqrt[3]{x} \\quad \\sqrt[\\leftroot{2}\\uproot{2}n]{x} $$" + }, + { + "id": "m041", + "cat": "ams", + "kind": "math", + "src": "$$ \\text{hello } \\textbf{bold} \\textit{it} \\textrm{rm} \\mbox{box} $$" + }, + { + "id": "m042", + "cat": "ams", + "kind": "math", + "src": "$$ \\mathrm{d}x \\; \\mathbf{v} \\; \\mathit{it} \\; \\mathsf{sf} \\; \\mathtt{tt} \\; \\boldsymbol{\\alpha} \\; \\pmb{\\beta} $$" + }, + { + "id": "m043", + "cat": "ams", + "kind": "math", + "src": "$$ \\hat{a} \\check{a} \\tilde{a} \\acute{a} \\grave{a} \\dot{a} \\ddot{a} \\dddot{a} \\breve{a} \\bar{a} \\vec{a} \\widehat{abc} \\widetilde{abc} $$" + }, + { + "id": "m044", + "cat": "ams", + "kind": "math", + "src": "$$ \\overline{AB} \\underline{CD} \\overrightarrow{AB} \\overleftarrow{AB} \\overleftrightarrow{AB} \\underrightarrow{AB} $$" + }, + { + "id": "m045", + "cat": "ams", + "kind": "math", + "src": "$$ \\lvert x \\rvert \\lVert x \\rVert \\langle x \\rangle \\lfloor x \\rfloor \\lceil x \\rceil $$" + }, + { + "id": "m046", + "cat": "ams", + "kind": "math", + "src": "$$ \\because \\therefore \\implies \\impliedby \\iff \\land \\lor \\neg $$" + }, + { + "id": "m047", + "cat": "ams", + "kind": "math", + "src": "$$ \\DeclareMathOperator{\\Tr}{Tr} \\Tr(A) $$" + }, + { + "id": "m048", + "cat": "ams", + "kind": "math", + "src": "\\begin{subequations} \\begin{align} a &= b \\\\ c &= d \\end{align} \\end{subequations}" + }, + { + "id": "m049", + "cat": "ams", + "kind": "math", + "src": "$$ \\numberwithin{equation}{section} x $$" + }, + { + "id": "m050", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\mathbb{R} \\mathbb{N} \\mathbb{Z} \\mathbb{Q} \\mathbb{C} \\mathfrak{g} \\mathcal{L} $$" + }, + { + "id": "m051", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\nexists \\varnothing \\emptyset \\complement \\hbar \\hslash \\ell \\beth \\gimel \\daleth \\Bbbk $$" + }, + { + "id": "m052", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\lesssim \\gtrsim \\leqslant \\geqslant \\nleq \\ngeq \\lneq \\gneq \\leqq \\geqq \\ll \\gg \\lll \\ggg $$" + }, + { + "id": "m053", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\subsetneq \\supsetneq \\subseteqq \\Subset \\Supset \\sqsubset \\sqsupset \\nsubseteq $$" + }, + { + "id": "m054", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\rightleftharpoons \\leftrightharpoons \\twoheadrightarrow \\hookrightarrow \\rightsquigarrow \\circlearrowleft \\curvearrowright \\Rrightarrow \\mapsto \\longmapsto $$" + }, + { + "id": "m055", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\blacksquare \\square \\lozenge \\blacklozenge \\bigstar \\measuredangle \\sphericalangle \\triangleq \\doteqdot \\approxeq \\backsim \\checkmark \\maltese \\circledR $$" + }, + { + "id": "m056", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\smallsetminus \\backslash \\setminus \\divideontimes \\ltimes \\rtimes \\boxplus \\boxtimes \\circledast \\intercal \\veebar \\barwedge $$" + }, + { + "id": "m057", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\nmid \\nparallel \\ncong \\nsim \\nVdash \\vDash \\Vdash \\models \\top \\bot \\diagup \\diagdown $$" + }, + { + "id": "m058", + "cat": "amssymb", + "kind": "math", + "src": "$$ \\ulcorner x \\urcorner \\llcorner y \\lrcorner \\digamma \\varkappa \\varrho \\varsigma \\varepsilon \\vartheta \\varphi \\varpi $$" + }, + { + "id": "m059", + "cat": "core", + "kind": "math", + "src": "$$ \\alpha\\beta\\gamma\\delta\\epsilon\\zeta\\eta\\theta\\iota\\kappa\\lambda\\mu\\nu\\xi\\pi\\rho\\sigma\\tau\\upsilon\\phi\\chi\\psi\\omega \\Gamma\\Delta\\Theta\\Lambda\\Xi\\Pi\\Sigma\\Upsilon\\Phi\\Psi\\Omega $$" + }, + { + "id": "m060", + "cat": "core", + "kind": "math", + "src": "$$ \\infty \\partial \\nabla \\forall \\exists \\in \\notin \\ni \\subset \\supset \\cup \\cap \\bigcup \\bigcap \\bigoplus \\bigotimes \\prod \\coprod $$" + }, + { + "id": "m061", + "cat": "core", + "kind": "math", + "src": "$$ \\le \\ge \\ne \\approx \\equiv \\sim \\simeq \\cong \\propto \\pm \\mp \\times \\div \\cdot \\circ \\bullet \\star \\ast \\oplus \\otimes \\odot $$" + }, + { + "id": "m062", + "cat": "core", + "kind": "math", + "src": "$$ \\to \\gets \\Rightarrow \\Leftarrow \\Leftrightarrow \\leftrightarrow \\longrightarrow \\Longrightarrow \\uparrow \\downarrow \\nearrow \\searrow $$" + }, + { + "id": "m063", + "cat": "core", + "kind": "math", + "src": "$$ \\sin \\cos \\tan \\log \\ln \\exp \\det \\gcd \\max \\min \\sup \\inf \\arg \\deg \\dim \\ker \\hom \\Pr $$" + }, + { + "id": "m064", + "cat": "core", + "kind": "math", + "src": "$$ \\begin{array}{c|lr} a & b & c \\\\ \\hline 1 & 2 & 3 \\end{array} $$" + }, + { + "id": "m065", + "cat": "core", + "kind": "math", + "src": "$$ x^{2^{3}} \\; x_{i_{j}} \\; {}^{14}_{6}C \\; f'(x) \\; f''(x) \\; \\prime $$" + }, + { + "id": "m066", + "cat": "core", + "kind": "math", + "src": "$$ \\left\\{ \\begin{array}{ll} a & b \\\\ c & d \\end{array} \\right. $$" + }, + { + "id": "m067", + "cat": "core", + "kind": "math", + "src": "$$ a \\quad b \\qquad c \\, d \\: e \\; f \\! g \\enspace h \\hspace{1cm} i $$" + }, + { + "id": "m068", + "cat": "core", + "kind": "math", + "src": "$$ \\displaystyle\\sum_i \\textstyle\\sum_i \\scriptstyle x \\scriptscriptstyle y $$" + }, + { + "id": "m069", + "cat": "core", + "kind": "math", + "src": "$$ \\overleftrightarrow{x} \\not= \\not\\in \\cancel{x} $$" + }, + { + "id": "m070", + "cat": "core", + "kind": "math", + "src": "$$ \\ldots \\cdots \\vdots \\ddots $$" + }, + { + "id": "m071", + "cat": "core", + "kind": "math", + "src": "$$ \\mathop{\\mathrm{lcm}}\\nolimits(a,b) \\mathbin{\\#} \\mathrel{\\sim} \\mathord{-} $$" + }, + { + "id": "m072", + "cat": "core", + "kind": "math", + "src": "$$ \\stackrel{?}{=} {a \\atop b} {n \\choose k} $$" + }, + { + "id": "m073", + "cat": "core", + "kind": "math", + "src": "$$ \\textstyle \\frac{1}{2} \\displaystyle \\frac{1}{2} $$" + }, + { + "id": "m074", + "cat": "core", + "kind": "math", + "src": "$$ \\rule{1cm}{0.4pt} \\raisebox{1ex}{x} $$" + }, + { + "id": "m075", + "cat": "macros", + "kind": "math", + "src": "$$ \\newcommand{\\R}{\\mathbb{R}} f\\colon \\R \\to \\R $$" + }, + { + "id": "m076", + "cat": "macros", + "kind": "math", + "src": "$$ \\renewcommand{\\vec}[1]{\\mathbf{#1}} \\vec{v} $$" + }, + { + "id": "m077", + "cat": "macros", + "kind": "math", + "src": "$$ \\def\\half{\\frac12} \\half $$" + }, + { + "id": "m078", + "cat": "macros", + "kind": "math", + "src": "$$ \\newcommand{\\abs}[1]{\\left|#1\\right|} \\abs{x} $$" + }, + { + "id": "m079", + "cat": "color", + "kind": "math", + "src": "$$ \\color{red} x + \\textcolor{blue}{y} + {\\color{green} z} $$" + }, + { + "id": "m080", + "cat": "color", + "kind": "math", + "src": "$$ \\colorbox{yellow}{$x$} \\fcolorbox{red}{white}{$y$} $$" + }, + { + "id": "m081", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{H2O} \\quad \\ce{CO2 + C -> 2 CO} $$" + }, + { + "id": "m082", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{2H2 + O2 -> 2H2O} $$" + }, + { + "id": "m083", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{A <=> B} \\quad \\ce{A <-> B} \\quad \\ce{A <=>> B} \\quad \\ce{A ->[\\Delta] B} $$" + }, + { + "id": "m084", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{Fe^{2+}} \\quad \\ce{SO4^2-} \\quad \\ce{^{227}_{90}Th} \\quad \\ce{Hg^2+ ->[I-] HgI2} $$" + }, + { + "id": "m085", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{SO4^2- + Ba^2+ -> BaSO4 v} \\quad \\ce{CaCO3 ->[\\Delta] CaO + CO2 ^} $$" + }, + { + "id": "m086", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{x Na(NH4)HPO4 ->[\\Delta] (NaPO3)_x + x NH3 ^ + x H2O} $$" + }, + { + "id": "m087", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{C=C} \\quad \\ce{A-B=C#D} \\quad \\ce{CH3-CH2-OH} $$" + }, + { + "id": "m088", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\pu{123 kJ/mol} \\quad \\pu{1.2e3 m/s} $$" + }, + { + "id": "m089", + "cat": "mhchem", + "kind": "math", + "src": "$$ \\ce{NaCl(aq)} \\quad \\ce{H2O(l)} \\quad \\ce{[Cu(NH3)4]^2+} \\quad \\ce{KCr(SO4)2*12H2O} $$" + }, + { + "id": "m090", + "cat": "mhchem", + "kind": "math", + "src": "\\begin{align*} \\ce{H2O &<=> H+ + OH-} \\\\ \\ce{NH3 + H2O &<=> NH4+ + OH-} \\end{align*}" + } + ], + "tikz": [ + { + "id": "t001", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw (0,0) -- (2,1); \\draw (0,0) circle (0.5); \\draw (1,0) rectangle (2,0.5); \\end{tikzpicture}" + }, + { + "id": "t002", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\node[draw] (a) at (0,0) {A}; \\node[draw, circle] (b) at (2,0) {$x^2$}; \\draw[->] (a) -- (b); \\end{tikzpicture}" + }, + { + "id": "t003", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw[help lines] (0,0) grid (3,2); \\draw[thick, red, dashed] (0,0) -- (3,2); \\end{tikzpicture}" + }, + { + "id": "t004", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\foreach \\x in {0,...,4} \\draw (\\x,0) circle (0.2); \\end{tikzpicture}" + }, + { + "id": "t005", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw[domain=0:6.28, smooth, variable=\\x] plot ({\\x},{sin(\\x r)}); \\end{tikzpicture}" + }, + { + "id": "t006", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\fill[blue!30] (0,0) -- (1,0) -- (0.5,1) -- cycle; \\filldraw[fill=green!20, draw=black] (2,0) circle (0.5); \\end{tikzpicture}" + }, + { + "id": "t007", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\shade[left color=red, right color=blue] (0,0) rectangle (2,1); \\end{tikzpicture}" + }, + { + "id": "t008", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\fill[red, opacity=0.5] (0,0) circle (1); \\fill[blue, fill opacity=0.5] (1,0) circle (1); \\end{tikzpicture}" + }, + { + "id": "t009", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\clip (0,0) circle (1); \\fill[orange] (0,0) rectangle (2,2); \\end{tikzpicture}" + }, + { + "id": "t010", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\begin{scope}[rotate=30, scale=0.8] \\draw (0,0) rectangle (2,1); \\end{scope} \\end{tikzpicture}" + }, + { + "id": "t011", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw (0,0) .. controls (1,1) and (2,-1) .. (3,0); \\draw (0,-1) to[out=90, in=180] (2,0); \\end{tikzpicture}" + }, + { + "id": "t012", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw[->] (-1,0) -- (3,0) node[right] {$x$}; \\draw[->] (0,-1) -- (0,2) node[above] {$y$}; \\node[below left] at (0,0) {$O$}; \\end{tikzpicture}" + }, + { + "id": "t013", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\tikz \\draw (0,0) -- (1,1);" + }, + { + "id": "t014", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture}[every node/.style={draw, rounded corners}] \\node (a) {one}; \\node (b) at (2,0) {two}; \\draw (a) edge[bend left, ->] (b); \\end{tikzpicture}" + }, + { + "id": "t015", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw (0,0) arc (0:270:1); \\draw (2,0) ellipse (1 and 0.5); \\end{tikzpicture}" + }, + { + "id": "t016", + "cat": "tikz-basic", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\node[draw, align=center, text width=2cm] {two\\\\lines}; \\end{tikzpicture}" + }, + { + "id": "t017", + "cat": "arrows.meta", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw[-Stealth] (0,0) -- (2,0); \\draw[-{Latex[length=3mm]}] (0,-0.5) -- (2,-0.5); \\draw[{Bar}-{Triangle[open]}] (0,-1) -- (2,-1); \\end{tikzpicture}" + }, + { + "id": "t018", + "cat": "calc", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\coordinate (a) at (0,0); \\coordinate (b) at (2,1); \\fill ($(a)!0.5!(b)$) circle (2pt); \\draw (a) -- ($(b)+(1,0)$); \\end{tikzpicture}" + }, + { + "id": "t019", + "cat": "positioning", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\node[draw] (a) {A}; \\node[draw, right=of a] (b) {B}; \\node[draw, below=1cm of a] (c) {C}; \\draw[->] (a) -- (b); \\draw[->] (a) -- (c); \\end{tikzpicture}" + }, + { + "id": "t020", + "cat": "shapes", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\node[draw, diamond] at (0,0) {d}; \\node[draw, ellipse] at (1.5,0) {e}; \\node[draw, regular polygon, regular polygon sides=6] at (3,0) {h}; \\node[draw, star] at (4.5,0) {s}; \\end{tikzpicture}" + }, + { + "id": "t021", + "cat": "shapes", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\node[draw, rounded rectangle] {rr}; \\node[draw, cylinder, shape border rotate=90] at (2,0) {db}; \\node[draw, single arrow] at (4,0) {go}; \\end{tikzpicture}" + }, + { + "id": "t022", + "cat": "decorations.pathreplacing", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw (0,0) -- (3,0); \\draw[decorate, decoration={brace, amplitude=6pt}] (0,0.1) -- node[above=6pt] {width} (3,0.1); \\end{tikzpicture}" + }, + { + "id": "t023", + "cat": "intersections", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw[name path=l1] (0,0) -- (2,2); \\draw[name path=l2] (0,2) -- (2,0); \\fill[red, name intersections={of=l1 and l2}] (intersection-1) circle (2pt); \\end{tikzpicture}" + }, + { + "id": "t024", + "cat": "patterns", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\fill[pattern=north east lines] (0,0) rectangle (1,1); \\fill[pattern=crosshatch dots] (1.5,0) rectangle (2.5,1); \\end{tikzpicture}" + }, + { + "id": "t025", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usetikzlibrary{matrix}\n\\begin{tikzpicture} \\matrix[matrix of nodes, nodes={draw}] { a & b \\\\ c & d \\\\ }; \\end{tikzpicture}" + }, + { + "id": "t026", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usetikzlibrary{arrows}\n\\begin{tikzpicture} \\draw[-latex] (0,0) -- (1,0); \\draw[->>] (0,-0.3) -- (1,-0.3); \\end{tikzpicture}" + }, + { + "id": "t027", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usepackage{amsmath}\n\\begin{tikzpicture} \\node {$\\begin{aligned} a &= b \\\\ c &= d \\end{aligned}$}; \\end{tikzpicture}" + }, + { + "id": "t028", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usetikzlibrary{decorations.markings}\n\\begin{tikzpicture} \\draw[postaction={decorate}, decoration={markings, mark=at position 0.5 with {\\arrow{>}}}] (0,0) -- (2,0); \\end{tikzpicture}" + }, + { + "id": "t029", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usetikzlibrary{automata}\n\\begin{tikzpicture}[shorten >=1pt, node distance=2cm, auto] \\node[state, initial] (q0) {$q_0$}; \\node[state, accepting] (q1) [right of=q0] {$q_1$}; \\path[->] (q0) edge node {a} (q1); \\end{tikzpicture}" + }, + { + "id": "t030", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usetikzlibrary{trees}\n\\begin{tikzpicture} \\node {root} child {node {a}} child {node {b}}; \\end{tikzpicture}" + }, + { + "id": "t031", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usetikzlibrary{3d}\n\\begin{tikzpicture}[x={(1cm,0cm)}, y={(0.5cm,0.5cm)}, z={(0cm,1cm)}] \\draw[->] (0,0,0) -- (1,0,0); \\draw[->] (0,0,0) -- (0,1,0); \\draw[->] (0,0,0) -- (0,0,1); \\end{tikzpicture}" + }, + { + "id": "t032", + "cat": "fence-preamble", + "kind": "tikz", + "src": "\\usetikzlibrary{backgrounds, fit}\n\\begin{tikzpicture} \\node (a) {A}; \\node (b) at (1,0) {B}; \\begin{scope}[on background layer] \\node[fill=gray!20, fit=(a) (b)] {}; \\end{scope} \\end{tikzpicture}" + }, + { + "id": "t033", + "cat": "math-in-tikz", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\node at (0,0) {$\\int_0^1 f(x)\\,dx$}; \\node at (3,0) {$\\mathbb{R}^n$}; \\node at (5,0) {$\\ce{H2O}$}; \\end{tikzpicture}" + }, + { + "id": "t034", + "cat": "color", + "kind": "tikz", + "src": "\\begin{tikzpicture} \\draw[red!60!black, line width=2pt] (0,0) -- (1,0); \\fill[blue!20] (1.5,0) circle (0.3); \\node[text=orange] at (3,0) {txt}; \\end{tikzpicture}" + } + ] +} \ No newline at end of file diff --git a/docs/embedded-latex-coverage/harness/corpus.py b/docs/embedded-latex-coverage/harness/corpus.py new file mode 100644 index 0000000..97378ee --- /dev/null +++ b/docs/embedded-latex-coverage/harness/corpus.py @@ -0,0 +1,144 @@ +# Coverage corpus: what zn's math = "latex" path (pdftex + default preamble) renders. +# kind "math": block as written in a note ($$, \[ \] or a top-level environment). +# kind "tikz": a ```tikz fence body (or a bare tikzpicture block). +import json +M = [] +def m(cat, src): M.append({"id": f"m{len(M)+1:03d}", "cat": cat, "kind": "math", "src": src}) +T = [] +def t(cat, src): T.append({"id": f"t{len(T)+1:03d}", "cat": cat, "kind": "tikz", "src": src}) + +# --- display forms zn detects --- +m("form", r"$$ x^2 + y^2 = z^2 $$") +m("form", r"\[ \int_0^1 x^2\,dx = \tfrac13 \]") +m("form", r"\begin{equation} E = mc^2 \end{equation}") +m("form", r"\begin{equation*} E = mc^2 \end{equation*}") +m("form", r"\begin{align} a &= b + c \\ d &= e \end{align}") +m("form", r"\begin{align*} a &= b + c \\ d &= e \end{align*}") +m("form", r"\begin{gather} x = 1 \\ yy = 22 \end{gather}") +m("form", r"\begin{gather*} x = 1 \\ yy = 22 \end{gather*}") +m("form", r"\begin{multline} a + b + c \\ + d + e \end{multline}") +m("form", r"\begin{multline*} a + b + c \\ + d + e \end{multline*}") +m("form", r"\begin{alignat}{2} x &= y & \quad a &= b \\ x' &= y' & a' &= b' \end{alignat}") +m("form", r"\begin{alignat*}{2} x &= y & \quad a &= b \end{alignat*}") +m("form", r"\begin{flalign} x &= y & a &= b \end{flalign}") +m("form", r"\begin{flalign*} x &= y & a &= b \end{flalign*}") +m("form", r"\begin{eqnarray} a &=& b \\ c &=& d \end{eqnarray}") +m("form", r"\begin{eqnarray*} a &=& b \end{eqnarray*}") +# --- amsmath structures --- +m("ams", r"\begin{equation} \begin{split} a &= b + c \\ &= d \end{split} \end{equation}") +m("ams", r"$$ f(x) = \begin{cases} x & x > 0 \\ -x & \text{otherwise} \end{cases} $$") +m("ams", r"$$ \begin{aligned} a &= 1 \\ b &= 2 \end{aligned} \qquad \begin{gathered} c \\ dd \end{gathered} $$") +m("ams", r"$$ \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} \begin{bmatrix} 1 \\ 2 \end{bmatrix} \begin{vmatrix} a & b \\ c & d \end{vmatrix} $$") +m("ams", r"$$ \begin{Vmatrix} a \end{Vmatrix} \begin{Bmatrix} a \end{Bmatrix} \begin{matrix} a & b \end{matrix} \left(\begin{smallmatrix} a & b \\ c & d \end{smallmatrix}\right) $$") +m("ams", r"\begin{align} a &= b \tag{A} \\ c &= d \notag \\ e &= f \nonumber \end{align}") +m("ams", r"\begin{equation} x = y \tag*{(star)} \end{equation}") +m("ams", r"\begin{align} a &= b \\ \intertext{and so} c &= d \end{align}") +m("ams", r"$$ \sum_{\substack{0 < i < m \\ 0 < j < n}} P(i,j) $$") +m("ams", r"$$ \overset{!}{=} \quad \underset{x}{\arg\max} \quad \stackrel{\text{def}}{=} $$") +m("ams", r"$$ A \xrightarrow[\text{below}]{\text{above}} B \xleftarrow{f} C $$") +m("ams", r"$$ \boxed{E = mc^2} $$") +m("ams", r"$$ \frac{a}{b} \dfrac{a}{b} \tfrac{a}{b} \cfrac{1}{1+\cfrac{1}{x}} $$") +m("ams", r"$$ \binom{n}{k} \dbinom{n}{k} \tbinom{n}{k} \genfrac{[}{]}{0pt}{}{n}{k} $$") +m("ams", r"$$ \operatorname{sgn}(x) \quad \operatorname*{arg\,min}_x f(x) $$") +m("ams", r"$$ \iint_D \iiint_V \iiiint \idotsint \oint_C $$") +m("ams", r"$$ a_1, \dotsc, a_n \quad a_1 + \dotsb + a_n \quad \dotsm \quad \dotsi \quad \dotso $$") +m("ams", r"$$ a \bmod b \quad a \pmod{n} \quad a \mod{n} \quad a \pod{n} $$") +m("ams", r"$$ \overbrace{a+b}^{n} \quad \underbrace{c+d}_{m} $$") +m("ams", r"$$ \left( \frac{a}{b} \middle| c \right) \bigl( \Bigl( \biggl( \Biggl( x \Biggr) \biggr) \Bigr) \bigr) $$") +m("ams", r"$$ \phantom{x} \hphantom{y} \vphantom{\int} \smash{\int} \mathstrut a $$") +m("ams", r"$$ \lim_{x \to 0} \frac{\sin x}{x} = 1 \quad \lim\limits_{n} \quad \sum\nolimits_k $$") +m("ams", r"$$ \sideset{_a^b}{_c^d}\sum \quad \leftidx{_1}{A}{_2} $$") +m("ams", r"$$ \sqrt[3]{x} \quad \sqrt[\leftroot{2}\uproot{2}n]{x} $$") +m("ams", r"$$ \text{hello } \textbf{bold} \textit{it} \textrm{rm} \mbox{box} $$") +m("ams", r"$$ \mathrm{d}x \; \mathbf{v} \; \mathit{it} \; \mathsf{sf} \; \mathtt{tt} \; \boldsymbol{\alpha} \; \pmb{\beta} $$") +m("ams", r"$$ \hat{a} \check{a} \tilde{a} \acute{a} \grave{a} \dot{a} \ddot{a} \dddot{a} \breve{a} \bar{a} \vec{a} \widehat{abc} \widetilde{abc} $$") +m("ams", r"$$ \overline{AB} \underline{CD} \overrightarrow{AB} \overleftarrow{AB} \overleftrightarrow{AB} \underrightarrow{AB} $$") +m("ams", r"$$ \lvert x \rvert \lVert x \rVert \langle x \rangle \lfloor x \rfloor \lceil x \rceil $$") +m("ams", r"$$ \because \therefore \implies \impliedby \iff \land \lor \neg $$") +m("ams", r"$$ \DeclareMathOperator{\Tr}{Tr} \Tr(A) $$") +m("ams", r"\begin{subequations} \begin{align} a &= b \\ c &= d \end{align} \end{subequations}") +m("ams", r"$$ \numberwithin{equation}{section} x $$") +# --- amssymb / amsfonts --- +m("amssymb", r"$$ \mathbb{R} \mathbb{N} \mathbb{Z} \mathbb{Q} \mathbb{C} \mathfrak{g} \mathcal{L} $$") +m("amssymb", r"$$ \nexists \varnothing \emptyset \complement \hbar \hslash \ell \beth \gimel \daleth \Bbbk $$") +m("amssymb", r"$$ \lesssim \gtrsim \leqslant \geqslant \nleq \ngeq \lneq \gneq \leqq \geqq \ll \gg \lll \ggg $$") +m("amssymb", r"$$ \subsetneq \supsetneq \subseteqq \Subset \Supset \sqsubset \sqsupset \nsubseteq $$") +m("amssymb", r"$$ \rightleftharpoons \leftrightharpoons \twoheadrightarrow \hookrightarrow \rightsquigarrow \circlearrowleft \curvearrowright \Rrightarrow \mapsto \longmapsto $$") +m("amssymb", r"$$ \blacksquare \square \lozenge \blacklozenge \bigstar \measuredangle \sphericalangle \triangleq \doteqdot \approxeq \backsim \checkmark \maltese \circledR $$") +m("amssymb", r"$$ \smallsetminus \backslash \setminus \divideontimes \ltimes \rtimes \boxplus \boxtimes \circledast \intercal \veebar \barwedge $$") +m("amssymb", r"$$ \nmid \nparallel \ncong \nsim \nVdash \vDash \Vdash \models \top \bot \diagup \diagdown $$") +m("amssymb", r"$$ \ulcorner x \urcorner \llcorner y \lrcorner \digamma \varkappa \varrho \varsigma \varepsilon \vartheta \varphi \varpi $$") +# --- core math --- +m("core", r"$$ \alpha\beta\gamma\delta\epsilon\zeta\eta\theta\iota\kappa\lambda\mu\nu\xi\pi\rho\sigma\tau\upsilon\phi\chi\psi\omega \Gamma\Delta\Theta\Lambda\Xi\Pi\Sigma\Upsilon\Phi\Psi\Omega $$") +m("core", r"$$ \infty \partial \nabla \forall \exists \in \notin \ni \subset \supset \cup \cap \bigcup \bigcap \bigoplus \bigotimes \prod \coprod $$") +m("core", r"$$ \le \ge \ne \approx \equiv \sim \simeq \cong \propto \pm \mp \times \div \cdot \circ \bullet \star \ast \oplus \otimes \odot $$") +m("core", r"$$ \to \gets \Rightarrow \Leftarrow \Leftrightarrow \leftrightarrow \longrightarrow \Longrightarrow \uparrow \downarrow \nearrow \searrow $$") +m("core", r"$$ \sin \cos \tan \log \ln \exp \det \gcd \max \min \sup \inf \arg \deg \dim \ker \hom \Pr $$") +m("core", r"$$ \begin{array}{c|lr} a & b & c \\ \hline 1 & 2 & 3 \end{array} $$") +m("core", r"$$ x^{2^{3}} \; x_{i_{j}} \; {}^{14}_{6}C \; f'(x) \; f''(x) \; \prime $$") +m("core", r"$$ \left\{ \begin{array}{ll} a & b \\ c & d \end{array} \right. $$") +m("core", r"$$ a \quad b \qquad c \, d \: e \; f \! g \enspace h \hspace{1cm} i $$") +m("core", r"$$ \displaystyle\sum_i \textstyle\sum_i \scriptstyle x \scriptscriptstyle y $$") +m("core", r"$$ \overleftrightarrow{x} \not= \not\in \cancel{x} $$") +m("core", r"$$ \ldots \cdots \vdots \ddots $$") +m("core", r"$$ \mathop{\mathrm{lcm}}\nolimits(a,b) \mathbin{\#} \mathrel{\sim} \mathord{-} $$") +m("core", r"$$ \stackrel{?}{=} \atop \above \choose $$".replace(r"\atop \above \choose", r"{a \atop b} {n \choose k}")) +m("core", r"$$ \textstyle \frac{1}{2} \displaystyle \frac{1}{2} $$") +m("core", r"$$ \rule{1cm}{0.4pt} \raisebox{1ex}{x} $$") +# --- macros defined in the block --- +m("macros", r"$$ \newcommand{\R}{\mathbb{R}} f\colon \R \to \R $$") +m("macros", r"$$ \renewcommand{\vec}[1]{\mathbf{#1}} \vec{v} $$") +m("macros", r"$$ \def\half{\frac12} \half $$") +m("macros", r"$$ \newcommand{\abs}[1]{\left|#1\right|} \abs{x} $$") +# --- color (xcolor is in zn's format) --- +m("color", r"$$ \color{red} x + \textcolor{blue}{y} + {\color{green} z} $$") +m("color", r"$$ \colorbox{yellow}{$x$} \fcolorbox{red}{white}{$y$} $$") +# --- mhchem --- +m("mhchem", r"$$ \ce{H2O} \quad \ce{CO2 + C -> 2 CO} $$") +m("mhchem", r"$$ \ce{2H2 + O2 -> 2H2O} $$") +m("mhchem", r"$$ \ce{A <=> B} \quad \ce{A <-> B} \quad \ce{A <=>> B} \quad \ce{A ->[\Delta] B} $$") +m("mhchem", r"$$ \ce{Fe^{2+}} \quad \ce{SO4^2-} \quad \ce{^{227}_{90}Th} \quad \ce{Hg^2+ ->[I-] HgI2} $$") +m("mhchem", r"$$ \ce{SO4^2- + Ba^2+ -> BaSO4 v} \quad \ce{CaCO3 ->[\Delta] CaO + CO2 ^} $$") +m("mhchem", r"$$ \ce{x Na(NH4)HPO4 ->[\Delta] (NaPO3)_x + x NH3 ^ + x H2O} $$") +m("mhchem", r"$$ \ce{C=C} \quad \ce{A-B=C#D} \quad \ce{CH3-CH2-OH} $$") +m("mhchem", r"$$ \pu{123 kJ/mol} \quad \pu{1.2e3 m/s} $$") +m("mhchem", r"$$ \ce{NaCl(aq)} \quad \ce{H2O(l)} \quad \ce{[Cu(NH3)4]^2+} \quad \ce{KCr(SO4)2*12H2O} $$") +m("mhchem", r"\begin{align*} \ce{H2O &<=> H+ + OH-} \\ \ce{NH3 + H2O &<=> NH4+ + OH-} \end{align*}") + +# --- TikZ: default preamble libraries and common constructs --- +t("tikz-basic", r"\begin{tikzpicture} \draw (0,0) -- (2,1); \draw (0,0) circle (0.5); \draw (1,0) rectangle (2,0.5); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \node[draw] (a) at (0,0) {A}; \node[draw, circle] (b) at (2,0) {$x^2$}; \draw[->] (a) -- (b); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \draw[help lines] (0,0) grid (3,2); \draw[thick, red, dashed] (0,0) -- (3,2); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \foreach \x in {0,...,4} \draw (\x,0) circle (0.2); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \draw[domain=0:6.28, smooth, variable=\x] plot ({\x},{sin(\x r)}); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \fill[blue!30] (0,0) -- (1,0) -- (0.5,1) -- cycle; \filldraw[fill=green!20, draw=black] (2,0) circle (0.5); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \shade[left color=red, right color=blue] (0,0) rectangle (2,1); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \fill[red, opacity=0.5] (0,0) circle (1); \fill[blue, fill opacity=0.5] (1,0) circle (1); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \clip (0,0) circle (1); \fill[orange] (0,0) rectangle (2,2); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \begin{scope}[rotate=30, scale=0.8] \draw (0,0) rectangle (2,1); \end{scope} \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \draw (0,0) .. controls (1,1) and (2,-1) .. (3,0); \draw (0,-1) to[out=90, in=180] (2,0); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \draw[->] (-1,0) -- (3,0) node[right] {$x$}; \draw[->] (0,-1) -- (0,2) node[above] {$y$}; \node[below left] at (0,0) {$O$}; \end{tikzpicture}") +t("tikz-basic", r"\tikz \draw (0,0) -- (1,1);") +t("tikz-basic", r"\begin{tikzpicture}[every node/.style={draw, rounded corners}] \node (a) {one}; \node (b) at (2,0) {two}; \draw (a) edge[bend left, ->] (b); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \draw (0,0) arc (0:270:1); \draw (2,0) ellipse (1 and 0.5); \end{tikzpicture}") +t("tikz-basic", r"\begin{tikzpicture} \node[draw, align=center, text width=2cm] {two\\lines}; \end{tikzpicture}") +t("arrows.meta", r"\begin{tikzpicture} \draw[-Stealth] (0,0) -- (2,0); \draw[-{Latex[length=3mm]}] (0,-0.5) -- (2,-0.5); \draw[{Bar}-{Triangle[open]}] (0,-1) -- (2,-1); \end{tikzpicture}") +t("calc", r"\begin{tikzpicture} \coordinate (a) at (0,0); \coordinate (b) at (2,1); \fill ($(a)!0.5!(b)$) circle (2pt); \draw (a) -- ($(b)+(1,0)$); \end{tikzpicture}") +t("positioning", r"\begin{tikzpicture} \node[draw] (a) {A}; \node[draw, right=of a] (b) {B}; \node[draw, below=1cm of a] (c) {C}; \draw[->] (a) -- (b); \draw[->] (a) -- (c); \end{tikzpicture}") +t("shapes", r"\begin{tikzpicture} \node[draw, diamond] at (0,0) {d}; \node[draw, ellipse] at (1.5,0) {e}; \node[draw, regular polygon, regular polygon sides=6] at (3,0) {h}; \node[draw, star] at (4.5,0) {s}; \end{tikzpicture}") +t("shapes", r"\begin{tikzpicture} \node[draw, rounded rectangle] {rr}; \node[draw, cylinder, shape border rotate=90] at (2,0) {db}; \node[draw, single arrow] at (4,0) {go}; \end{tikzpicture}") +t("decorations.pathreplacing", r"\begin{tikzpicture} \draw (0,0) -- (3,0); \draw[decorate, decoration={brace, amplitude=6pt}] (0,0.1) -- node[above=6pt] {width} (3,0.1); \end{tikzpicture}") +t("intersections", r"\begin{tikzpicture} \draw[name path=l1] (0,0) -- (2,2); \draw[name path=l2] (0,2) -- (2,0); \fill[red, name intersections={of=l1 and l2}] (intersection-1) circle (2pt); \end{tikzpicture}") +t("patterns", r"\begin{tikzpicture} \fill[pattern=north east lines] (0,0) rectangle (1,1); \fill[pattern=crosshatch dots] (1.5,0) rectangle (2.5,1); \end{tikzpicture}") +t("fence-preamble", "\\usetikzlibrary{matrix}\n\\begin{tikzpicture} \\matrix[matrix of nodes, nodes={draw}] { a & b \\\\ c & d \\\\ }; \\end{tikzpicture}") +t("fence-preamble", "\\usetikzlibrary{arrows}\n\\begin{tikzpicture} \\draw[-latex] (0,0) -- (1,0); \\draw[->>] (0,-0.3) -- (1,-0.3); \\end{tikzpicture}") +t("fence-preamble", "\\usepackage{amsmath}\n\\begin{tikzpicture} \\node {$\\begin{aligned} a &= b \\\\ c &= d \\end{aligned}$}; \\end{tikzpicture}") +t("fence-preamble", "\\usetikzlibrary{decorations.markings}\n\\begin{tikzpicture} \\draw[postaction={decorate}, decoration={markings, mark=at position 0.5 with {\\arrow{>}}}] (0,0) -- (2,0); \\end{tikzpicture}") +t("fence-preamble", "\\usetikzlibrary{automata}\n\\begin{tikzpicture}[shorten >=1pt, node distance=2cm, auto] \\node[state, initial] (q0) {$q_0$}; \\node[state, accepting] (q1) [right of=q0] {$q_1$}; \\path[->] (q0) edge node {a} (q1); \\end{tikzpicture}") +t("fence-preamble", "\\usetikzlibrary{trees}\n\\begin{tikzpicture} \\node {root} child {node {a}} child {node {b}}; \\end{tikzpicture}") +t("fence-preamble", "\\usetikzlibrary{3d}\n\\begin{tikzpicture}[x={(1cm,0cm)}, y={(0.5cm,0.5cm)}, z={(0cm,1cm)}] \\draw[->] (0,0,0) -- (1,0,0); \\draw[->] (0,0,0) -- (0,1,0); \\draw[->] (0,0,0) -- (0,0,1); \\end{tikzpicture}") +t("fence-preamble", "\\usetikzlibrary{backgrounds, fit}\n\\begin{tikzpicture} \\node (a) {A}; \\node (b) at (1,0) {B}; \\begin{scope}[on background layer] \\node[fill=gray!20, fit=(a) (b)] {}; \\end{scope} \\end{tikzpicture}") +t("math-in-tikz", r"\begin{tikzpicture} \node at (0,0) {$\int_0^1 f(x)\,dx$}; \node at (3,0) {$\mathbb{R}^n$}; \node at (5,0) {$\ce{H2O}$}; \end{tikzpicture}") +t("color", r"\begin{tikzpicture} \draw[red!60!black, line width=2pt] (0,0) -- (1,0); \fill[blue!20] (1.5,0) circle (0.3); \node[text=orange] at (3,0) {txt}; \end{tikzpicture}") +json.dump({"math": M, "tikz": T}, open(__file__.replace("corpus.py", "corpus.json"), "w"), indent=1) +print(len(M), "math,", len(T), "tikz") diff --git a/docs/embedded-latex-coverage/harness/mathjax-go.mod b/docs/embedded-latex-coverage/harness/mathjax-go.mod new file mode 100644 index 0000000..6ee6513 --- /dev/null +++ b/docs/embedded-latex-coverage/harness/mathjax-go.mod @@ -0,0 +1,15 @@ +module covmj + +go 1.27.3 + +require ( + github.com/d2lang/mathjax-go v0.1.0 + github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef +) + +require ( + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect + golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect + golang.org/x/text v0.3.6 // indirect +) diff --git a/docs/embedded-latex-coverage/harness/mathjax-go.sum b/docs/embedded-latex-coverage/harness/mathjax-go.sum new file mode 100644 index 0000000..f35b3cc --- /dev/null +++ b/docs/embedded-latex-coverage/harness/mathjax-go.sum @@ -0,0 +1,13 @@ +github.com/d2lang/mathjax-go v0.1.0 h1:wwEYeMNpjqZrKqoZYNA3ZvVxQuN0EzBwEPClUT/KcwE= +github.com/d2lang/mathjax-go v0.1.0/go.mod h1:ltfD9ACCPWhnU2KVOCKaUyBa3vUDGZoc1njExXypym4= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/docs/embedded-latex-coverage/harness/mathjax-run.go.txt b/docs/embedded-latex-coverage/harness/mathjax-run.go.txt new file mode 100644 index 0000000..f969929 --- /dev/null +++ b/docs/embedded-latex-coverage/harness/mathjax-run.go.txt @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/json" + "fmt" + "image" + "image/png" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + mathjax "github.com/d2lang/mathjax-go" + "github.com/srwiley/oksvg" + "github.com/srwiley/rasterx" +) + +type item struct{ ID, Cat, Kind, Src string } + +var ( + errAttr = regexp.MustCompile(`data-mjx-error="([^"]*)"`) + dims = regexp.MustCompile(`width="([0-9.]+)ex" height="([0-9.]+)ex"`) +) + +// mathInput is what MathJax gets: $$ and \[ \] lose their delimiters, a +// top-level environment passes whole, as zn's latex path compiles it. +func mathInput(src string) string { + s := strings.TrimSpace(src) + if strings.HasPrefix(s, "$$") && strings.HasSuffix(s, "$$") && len(s) >= 4 { + return strings.TrimSpace(s[2 : len(s)-2]) + } + if strings.HasPrefix(s, `\[`) && strings.HasSuffix(s, `\]`) { + return strings.TrimSpace(s[2 : len(s)-2]) + } + return s +} + +func main() { + corpus, out := os.Args[1], os.Args[2] + var c map[string][]item + b, _ := os.ReadFile(corpus) + if err := json.Unmarshal(b, &c); err != nil { + panic(err) + } + type res struct { + ID, Cat, Err string + OK bool + Us int64 + } + var results []res + for _, it := range c["math"] { + start := time.Now() + svg, err := mathjax.Render(mathInput(it.Src)) + r := res{ID: it.ID, Cat: it.Cat, Us: time.Since(start).Microseconds()} + switch { + case err != nil: + r.Err = "go error: " + err.Error() + case strings.Contains(svg, `data-mml-node="merror"`): + if m := errAttr.FindStringSubmatch(svg); m != nil { + r.Err = m[1] + } else { + r.Err = "merror" + } + default: + r.OK = true + } + results = append(results, r) + if err == nil { + _ = os.WriteFile(filepath.Join(out, it.ID+".svg"), []byte(svg), 0o644) + if perr := raster(svg, filepath.Join(out, it.ID+".png")); perr != nil && r.OK { + results[len(results)-1].Err = "raster: " + perr.Error() + } + } + } + j, _ := json.MarshalIndent(results, "", " ") + _ = os.WriteFile(filepath.Join(out, "results.json"), j, 0o644) + fmt.Println(len(results), "rendered") +} + +// raster draws the SVG at about the size pdftex gives 10pt type at 260 dpi +// (an em of 36 px; MathJax sizes in ex at 8 px each for a 16 px em). +func raster(svg, path string) error { + svg = strings.ReplaceAll(svg, "currentColor", "#D4BE98") + m := dims.FindStringSubmatch(svg) + if m == nil { + return fmt.Errorf("no size") + } + wex, _ := strconv.ParseFloat(m[1], 64) + hex, _ := strconv.ParseFloat(m[2], 64) + scale := 36.0 / 16.0 + w, h := int(wex*8*scale)+1, int(hex*8*scale)+1 + icon, err := oksvg.ReadIconStream(strings.NewReader(svg), oksvg.IgnoreErrorMode) + if err != nil { + return err + } + icon.SetTarget(0, 0, float64(w), float64(h)) + img := image.NewRGBA(image.Rect(0, 0, w, h)) + sc := rasterx.NewScannerGV(w, h, img, img.Bounds()) + icon.Draw(rasterx.NewDasher(w, h, sc), 1) + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + return png.Encode(f, img) +} diff --git a/docs/embedded-latex-coverage/harness/pdftex-docker-wrapper.sh b/docs/embedded-latex-coverage/harness/pdftex-docker-wrapper.sh new file mode 100755 index 0000000..bbd8256 --- /dev/null +++ b/docs/embedded-latex-coverage/harness/pdftex-docker-wrapper.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# Runs the container's pdftex with the work dir, the format dir and $TMPDIR at the same paths. +FMT="${TEXFORMATS%%:*}" +T="${TMPDIR%/}" +set -- -v "$PWD:$PWD" -w "$PWD" ${FMT:+-v "$FMT:$FMT"} ${T:+-v "$T:$T"} -e HOME=/tmp -e openin_any -e openout_any -e TEXFORMATS -e TEXMFOUTPUT zn-texlive-readme pdftex "$@" +exec docker run --rm -i -u "$(id -u):$(id -g)" "$@" diff --git a/docs/embedded-latex-coverage/harness/pdftex_coverage_test.go.txt b/docs/embedded-latex-coverage/harness/pdftex_coverage_test.go.txt new file mode 100644 index 0000000..33b2c5c --- /dev/null +++ b/docs/embedded-latex-coverage/harness/pdftex_coverage_test.go.txt @@ -0,0 +1,57 @@ +// Copy into internal/tui/ as zz_coverage_test.go to render the corpus through +// zn's own latex path (latexPNG), then run: +// COV_CORPUS= COV_OUT= PATH=:$PATH \ +// go test ./internal/tui -run TestZZCoverage -count=1 -timeout 30m + +package tui + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + "time" +) + +func TestZZCoverage(t *testing.T) { + corpus, out := os.Getenv("COV_CORPUS"), os.Getenv("COV_OUT") + if corpus == "" { + t.Skip("COV_CORPUS not set") + } + t.Setenv("ZENNOTES_CONFIG_DIR", t.TempDir()) + t.Setenv("HOME", filepath.Join(out, "home")) + var c map[string][]struct{ ID, Cat, Kind, Src string } + b, _ := os.ReadFile(corpus) + if err := json.Unmarshal(b, &c); err != nil { + t.Fatal(err) + } + type res struct { + ID, Cat, Kind, Err string + OK bool + Ms int64 + } + var results []res + var formats latexFormats + for _, group := range []string{"math", "tikz"} { + for _, it := range c[group] { + kind := "latex" + if it.Kind == "tikz" { + kind = "tikz" + } + ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + start := time.Now() + png, err := latexPNG(ctx, &formats, kind, it.Src, "#D4BE98") + cancel() + r := res{ID: it.ID, Cat: it.Cat, Kind: it.Kind, OK: err == nil, Ms: time.Since(start).Milliseconds()} + if err != nil { + r.Err = err.Error() + } else { + _ = os.WriteFile(filepath.Join(out, it.ID+".png"), png, 0o644) + } + results = append(results, r) + } + } + j, _ := json.MarshalIndent(results, "", " ") + _ = os.WriteFile(filepath.Join(out, "results.json"), j, 0o644) +} diff --git a/docs/embedded-latex-coverage/harness/tikzjax-run.cjs b/docs/embedded-latex-coverage/harness/tikzjax-run.cjs new file mode 100644 index 0000000..a39701e --- /dev/null +++ b/docs/embedded-latex-coverage/harness/tikzjax-run.cjs @@ -0,0 +1,59 @@ +// Renders the corpus through node-tikzjax configured as the desktop's tikz.ts does. +const fs = require("fs"); +const path = require("path"); +const mod = require("/Users/adibhanna/Developer/opensource/zennotes/node_modules/node-tikzjax"); +const tex2svg = typeof mod === "function" ? mod : (mod.default.default || mod.default); +const [corpusPath, out, onlyId] = process.argv.slice(2); +const corpus = JSON.parse(fs.readFileSync(corpusPath, "utf8")); +const LIBS = "arrows.meta,calc,positioning,shapes,decorations.pathreplacing,intersections,patterns"; + +// Desktop wrapSource (apps/desktop/src/main/tikz.ts). +function wrapSource(source) { + const trimmed = source.trim(); + const noClass = trimmed.replace(/^\s*\\documentclass(?:\[[^\]]*])?\{[^}]+\}\s*$/gm, "").trim(); + if (/\\begin\{document\}/.test(noClass) && /\\end\{document\}/.test(noClass)) return noClass; + const bare = noClass.replace(/\\begin\{document\}/g, "").replace(/\\end\{document\}/g, "").trim(); + const cands = [/\\begin\{(?!document\b)[^}]+\}/, /\\tikz\b/, /\\draw\b/, /\\path\b/, /\\node\b/, /\\coordinate\b/, /\\matrix\b/, /\\graph\b/]; + let at = -1; + for (const p of cands) { const m = p.exec(bare); if (m && (at < 0 || m.index < at)) at = m.index; } + if (at > 0) return `${bare.slice(0, at).trim()}\n\\begin{document}\n${bare.slice(at).trim()}\n\\end{document}`; + return `\\begin{document}\n${bare}\n\\end{document}`; +} +function mathDoc(src) { + let s = src.trim(); + if (s.startsWith("$$") && s.endsWith("$$")) s = "\\[ " + s.slice(2, -2).trim() + " \\]"; + return `\\begin{document}\n\\begin{tikzpicture}\\node[inner sep=0pt]{\\begin{minipage}{14cm}\n${s}\n\\end{minipage}};\\end{tikzpicture}\n\\end{document}`; +} +(async () => { + const results = []; + for (const group of ["tikz", "math"]) { + for (const it of corpus[group]) { + if (onlyId && it.id !== onlyId) continue; + const texPackages = { amsmath: "", amssymb: "" }; + if (it.src.includes("pgfplots") || it.src.includes("axis") || it.src.includes("plot")) texPackages.pgfplots = ""; + if (it.src.includes("circuitikz")) texPackages.circuitikz = ""; + if (group === "math" && it.src.includes("\\ce")) texPackages.mhchem = "version=4"; + const input = group === "tikz" ? wrapSource(it.src) : mathDoc(it.src); + const start = Date.now(); + let r = { ID: it.id, Cat: it.cat, Kind: group, OK: false, Err: "", Ms: 0 }; + const logs = []; + const orig = console.log; + console.log = (...a) => logs.push(a.join(" ")); + try { + const svg = await tex2svg(input, { showConsole: true, texPackages, tikzLibraries: LIBS }); + r.OK = true; + fs.writeFileSync(path.join(out, it.id + ".svg"), svg); + } catch (e) { + const bang = logs.filter((l) => l.startsWith("!")); + r.Err = (bang[0] || String(e && e.message || e)).slice(0, 200); + } finally { + console.log = orig; + } + r.Ms = Date.now() - start; + results.push(r); + } + } + if (onlyId) { fs.writeFileSync(path.join(out, onlyId + ".json"), JSON.stringify(results[0])); return; } + fs.writeFileSync(path.join(out, "results.json"), JSON.stringify(results, null, 1)); + console.log(results.length, "rendered"); +})(); diff --git a/docs/embedded-latex-coverage/results/mathjax-go.json b/docs/embedded-latex-coverage/results/mathjax-go.json new file mode 100644 index 0000000..605b700 --- /dev/null +++ b/docs/embedded-latex-coverage/results/mathjax-go.json @@ -0,0 +1,632 @@ +[ + { + "ID": "m001", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 343 + }, + { + "ID": "m002", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 243 + }, + { + "ID": "m003", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 141 + }, + { + "ID": "m004", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 63 + }, + { + "ID": "m005", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 229 + }, + { + "ID": "m006", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 436 + }, + { + "ID": "m007", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 121 + }, + { + "ID": "m008", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 110 + }, + { + "ID": "m009", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 102 + }, + { + "ID": "m010", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 370 + }, + { + "ID": "m011", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 307 + }, + { + "ID": "m012", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 414 + }, + { + "ID": "m013", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 106 + }, + { + "ID": "m014", + "Cat": "form", + "Err": "", + "OK": true, + "Us": 162 + }, + { + "ID": "m015", + "Cat": "form", + "Err": "Unknown environment 'eqnarray'", + "OK": false, + "Us": 70 + }, + { + "ID": "m016", + "Cat": "form", + "Err": "Unknown environment 'eqnarray*'", + "OK": false, + "Us": 54 + }, + { + "ID": "m017", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 131 + }, + { + "ID": "m018", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 279 + }, + { + "ID": "m019", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 202 + }, + { + "ID": "m020", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 263 + }, + { + "ID": "m021", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 457 + }, + { + "ID": "m022", + "Cat": "ams", + "Err": "raster: no size", + "OK": true, + "Us": 180 + }, + { + "ID": "m023", + "Cat": "ams", + "Err": "raster: no size", + "OK": true, + "Us": 78 + }, + { + "ID": "m024", + "Cat": "ams", + "Err": "Undefined control sequence \\intertext", + "OK": false, + "Us": 43 + }, + { + "ID": "m025", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 202 + }, + { + "ID": "m026", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 269 + }, + { + "ID": "m027", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 231 + }, + { + "ID": "m028", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 112 + }, + { + "ID": "m029", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 235 + }, + { + "ID": "m030", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 260 + }, + { + "ID": "m031", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 173 + }, + { + "ID": "m032", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 251 + }, + { + "ID": "m033", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 176 + }, + { + "ID": "m034", + "Cat": "ams", + "Err": "Undefined control sequence \\pod", + "OK": false, + "Us": 76 + }, + { + "ID": "m035", + "Cat": "ams", + "Err": "Double exponent: use braces to clarify", + "OK": false, + "Us": 103 + }, + { + "ID": "m036", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 406 + }, + { + "ID": "m037", + "Cat": "ams", + "Err": "Undefined control sequence \\mathstrut", + "OK": false, + "Us": 97 + }, + { + "ID": "m038", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 246 + }, + { + "ID": "m039", + "Cat": "ams", + "Err": "Undefined control sequence \\leftidx", + "OK": false, + "Us": 79 + }, + { + "ID": "m040", + "Cat": "ams", + "Err": "Undefined control sequence \\leftroot", + "OK": false, + "Us": 56 + }, + { + "ID": "m041", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 100 + }, + { + "ID": "m042", + "Cat": "ams", + "Err": "Undefined control sequence \\pmb", + "OK": false, + "Us": 150 + }, + { + "ID": "m043", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 334 + }, + { + "ID": "m044", + "Cat": "ams", + "Err": "Undefined control sequence \\overleftrightarrow", + "OK": false, + "Us": 76 + }, + { + "ID": "m045", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 154 + }, + { + "ID": "m046", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 180 + }, + { + "ID": "m047", + "Cat": "ams", + "Err": "", + "OK": true, + "Us": 80 + }, + { + "ID": "m048", + "Cat": "ams", + "Err": "Unknown environment 'subequations'", + "OK": false, + "Us": 44 + }, + { + "ID": "m049", + "Cat": "ams", + "Err": "Undefined control sequence \\numberwithin", + "OK": false, + "Us": 39 + }, + { + "ID": "m050", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 151 + }, + { + "ID": "m051", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 104 + }, + { + "ID": "m052", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 159 + }, + { + "ID": "m053", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 94 + }, + { + "ID": "m054", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 349 + }, + { + "ID": "m055", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 116 + }, + { + "ID": "m056", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 196 + }, + { + "ID": "m057", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 266 + }, + { + "ID": "m058", + "Cat": "amssymb", + "Err": "", + "OK": true, + "Us": 125 + }, + { + "ID": "m059", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 269 + }, + { + "ID": "m060", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 181 + }, + { + "ID": "m061", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 195 + }, + { + "ID": "m062", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 163 + }, + { + "ID": "m063", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 314 + }, + { + "ID": "m064", + "Cat": "core", + "Err": "Undefined control sequence \\hline", + "OK": false, + "Us": 100 + }, + { + "ID": "m065", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 548 + }, + { + "ID": "m066", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 112 + }, + { + "ID": "m067", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 174 + }, + { + "ID": "m068", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 120 + }, + { + "ID": "m069", + "Cat": "core", + "Err": "Undefined control sequence \\overleftrightarrow", + "OK": false, + "Us": 32 + }, + { + "ID": "m070", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 66 + }, + { + "ID": "m071", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 378 + }, + { + "ID": "m072", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 111 + }, + { + "ID": "m073", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 99 + }, + { + "ID": "m074", + "Cat": "core", + "Err": "", + "OK": true, + "Us": 52 + }, + { + "ID": "m075", + "Cat": "macros", + "Err": "Undefined control sequence \\newcommand", + "OK": false, + "Us": 37 + }, + { + "ID": "m076", + "Cat": "macros", + "Err": "Undefined control sequence \\renewcommand", + "OK": false, + "Us": 39 + }, + { + "ID": "m077", + "Cat": "macros", + "Err": "Undefined control sequence \\def", + "OK": false, + "Us": 28 + }, + { + "ID": "m078", + "Cat": "macros", + "Err": "Undefined control sequence \\newcommand", + "OK": false, + "Us": 26 + }, + { + "ID": "m079", + "Cat": "color", + "Err": "", + "OK": true, + "Us": 252 + }, + { + "ID": "m080", + "Cat": "color", + "Err": "", + "OK": true, + "Us": 138 + }, + { + "ID": "m081", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 1488 + }, + { + "ID": "m082", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 378 + }, + { + "ID": "m083", + "Cat": "mhchem", + "Err": "Undefined control sequence \\longrightleftharpoons", + "OK": false, + "Us": 141 + }, + { + "ID": "m084", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 1051 + }, + { + "ID": "m085", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 676 + }, + { + "ID": "m086", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 1233 + }, + { + "ID": "m087", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 769 + }, + { + "ID": "m088", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 231 + }, + { + "ID": "m089", + "Cat": "mhchem", + "Err": "", + "OK": true, + "Us": 1141 + }, + { + "ID": "m090", + "Cat": "mhchem", + "Err": "Misplaced alignment tab character \u0026amp;", + "OK": false, + "Us": 174 + } +] \ No newline at end of file diff --git a/docs/embedded-latex-coverage/results/pdftex.json b/docs/embedded-latex-coverage/results/pdftex.json new file mode 100644 index 0000000..7ca70a1 --- /dev/null +++ b/docs/embedded-latex-coverage/results/pdftex.json @@ -0,0 +1,994 @@ +[ + { + "ID": "m001", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 7191 + }, + { + "ID": "m002", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 332 + }, + { + "ID": "m003", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 302 + }, + { + "ID": "m004", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 316 + }, + { + "ID": "m005", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 299 + }, + { + "ID": "m006", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 313 + }, + { + "ID": "m007", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 305 + }, + { + "ID": "m008", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 298 + }, + { + "ID": "m009", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 291 + }, + { + "ID": "m010", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 290 + }, + { + "ID": "m011", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 306 + }, + { + "ID": "m012", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 290 + }, + { + "ID": "m013", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 278 + }, + { + "ID": "m014", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 409 + }, + { + "ID": "m015", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 957 + }, + { + "ID": "m016", + "Cat": "form", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1621 + }, + { + "ID": "m017", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1035 + }, + { + "ID": "m018", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1368 + }, + { + "ID": "m019", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1652 + }, + { + "ID": "m020", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1587 + }, + { + "ID": "m021", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1655 + }, + { + "ID": "m022", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1151 + }, + { + "ID": "m023", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1763 + }, + { + "ID": "m024", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1145 + }, + { + "ID": "m025", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1127 + }, + { + "ID": "m026", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1195 + }, + { + "ID": "m027", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1343 + }, + { + "ID": "m028", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1302 + }, + { + "ID": "m029", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1203 + }, + { + "ID": "m030", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1667 + }, + { + "ID": "m031", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1698 + }, + { + "ID": "m032", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 1178 + }, + { + "ID": "m033", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 311 + }, + { + "ID": "m034", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 367 + }, + { + "ID": "m035", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 319 + }, + { + "ID": "m036", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 284 + }, + { + "ID": "m037", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 273 + }, + { + "ID": "m038", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 277 + }, + { + "ID": "m039", + "Cat": "ams", + "Kind": "math", + "Err": "latex: ! Undefined control sequence.", + "OK": false, + "Ms": 247 + }, + { + "ID": "m040", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 281 + }, + { + "ID": "m041", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 288 + }, + { + "ID": "m042", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 276 + }, + { + "ID": "m043", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 272 + }, + { + "ID": "m044", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 293 + }, + { + "ID": "m045", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 271 + }, + { + "ID": "m046", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 264 + }, + { + "ID": "m047", + "Cat": "ams", + "Kind": "math", + "Err": "latex: ! LaTeX Error: Can be used only in preamble.", + "OK": false, + "Ms": 242 + }, + { + "ID": "m048", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 282 + }, + { + "ID": "m049", + "Cat": "ams", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 262 + }, + { + "ID": "m050", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 285 + }, + { + "ID": "m051", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 283 + }, + { + "ID": "m052", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 266 + }, + { + "ID": "m053", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 271 + }, + { + "ID": "m054", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 268 + }, + { + "ID": "m055", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 268 + }, + { + "ID": "m056", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 273 + }, + { + "ID": "m057", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 277 + }, + { + "ID": "m058", + "Cat": "amssymb", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 273 + }, + { + "ID": "m059", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 292 + }, + { + "ID": "m060", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 292 + }, + { + "ID": "m061", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 275 + }, + { + "ID": "m062", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 284 + }, + { + "ID": "m063", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 283 + }, + { + "ID": "m064", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 281 + }, + { + "ID": "m065", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 305 + }, + { + "ID": "m066", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 300 + }, + { + "ID": "m067", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 272 + }, + { + "ID": "m068", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 271 + }, + { + "ID": "m069", + "Cat": "core", + "Kind": "math", + "Err": "latex: ! Undefined control sequence.", + "OK": false, + "Ms": 248 + }, + { + "ID": "m070", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 284 + }, + { + "ID": "m071", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 267 + }, + { + "ID": "m072", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 282 + }, + { + "ID": "m073", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 265 + }, + { + "ID": "m074", + "Cat": "core", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 284 + }, + { + "ID": "m075", + "Cat": "macros", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 300 + }, + { + "ID": "m076", + "Cat": "macros", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 280 + }, + { + "ID": "m077", + "Cat": "macros", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 276 + }, + { + "ID": "m078", + "Cat": "macros", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 293 + }, + { + "ID": "m079", + "Cat": "color", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 287 + }, + { + "ID": "m080", + "Cat": "color", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 276 + }, + { + "ID": "m081", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 340 + }, + { + "ID": "m082", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 338 + }, + { + "ID": "m083", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 322 + }, + { + "ID": "m084", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 369 + }, + { + "ID": "m085", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 387 + }, + { + "ID": "m086", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 413 + }, + { + "ID": "m087", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 351 + }, + { + "ID": "m088", + "Cat": "mhchem", + "Kind": "math", + "Err": "latex: ! Undefined control sequence.", + "OK": false, + "Ms": 246 + }, + { + "ID": "m089", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 430 + }, + { + "ID": "m090", + "Cat": "mhchem", + "Kind": "math", + "Err": "", + "OK": true, + "Ms": 468 + }, + { + "ID": "t001", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 278 + }, + { + "ID": "t002", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 292 + }, + { + "ID": "t003", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 279 + }, + { + "ID": "t004", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 279 + }, + { + "ID": "t005", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 298 + }, + { + "ID": "t006", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 280 + }, + { + "ID": "t007", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 275 + }, + { + "ID": "t008", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 292 + }, + { + "ID": "t009", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 286 + }, + { + "ID": "t010", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 281 + }, + { + "ID": "t011", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 286 + }, + { + "ID": "t012", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 305 + }, + { + "ID": "t013", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 283 + }, + { + "ID": "t014", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 292 + }, + { + "ID": "t015", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 305 + }, + { + "ID": "t016", + "Cat": "tikz-basic", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 288 + }, + { + "ID": "t017", + "Cat": "arrows.meta", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 301 + }, + { + "ID": "t018", + "Cat": "calc", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 279 + }, + { + "ID": "t019", + "Cat": "positioning", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 300 + }, + { + "ID": "t020", + "Cat": "shapes", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 298 + }, + { + "ID": "t021", + "Cat": "shapes", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 303 + }, + { + "ID": "t022", + "Cat": "decorations.pathreplacing", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 292 + }, + { + "ID": "t023", + "Cat": "intersections", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 292 + }, + { + "ID": "t024", + "Cat": "patterns", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 299 + }, + { + "ID": "t025", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 293 + }, + { + "ID": "t026", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 293 + }, + { + "ID": "t027", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 284 + }, + { + "ID": "t028", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 293 + }, + { + "ID": "t029", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 313 + }, + { + "ID": "t030", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 296 + }, + { + "ID": "t031", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 279 + }, + { + "ID": "t032", + "Cat": "fence-preamble", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 289 + }, + { + "ID": "t033", + "Cat": "math-in-tikz", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 319 + }, + { + "ID": "t034", + "Cat": "color", + "Kind": "tikz", + "Err": "", + "OK": true, + "Ms": 290 + } +] \ No newline at end of file diff --git a/docs/embedded-latex-coverage/results/tikzjax.json b/docs/embedded-latex-coverage/results/tikzjax.json new file mode 100644 index 0000000..e522b68 --- /dev/null +++ b/docs/embedded-latex-coverage/results/tikzjax.json @@ -0,0 +1,994 @@ +[ + { + "ID": "t001", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 338 + }, + { + "ID": "t002", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 374 + }, + { + "ID": "t003", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 357 + }, + { + "ID": "t004", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 355 + }, + { + "ID": "t005", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 701 + }, + { + "ID": "t006", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 347 + }, + { + "ID": "t007", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "t008", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 354 + }, + { + "ID": "t009", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 355 + }, + { + "ID": "t010", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 352 + }, + { + "ID": "t011", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 356 + }, + { + "ID": "t012", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 380 + }, + { + "ID": "t013", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 346 + }, + { + "ID": "t014", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 389 + }, + { + "ID": "t015", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 357 + }, + { + "ID": "t016", + "Cat": "tikz-basic", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 356 + }, + { + "ID": "t017", + "Cat": "arrows.meta", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 369 + }, + { + "ID": "t018", + "Cat": "calc", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 356 + }, + { + "ID": "t019", + "Cat": "positioning", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 379 + }, + { + "ID": "t020", + "Cat": "shapes", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 369 + }, + { + "ID": "t021", + "Cat": "shapes", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 391 + }, + { + "ID": "t022", + "Cat": "decorations.pathreplacing", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 358 + }, + { + "ID": "t023", + "Cat": "intersections", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 348 + }, + { + "ID": "t024", + "Cat": "patterns", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 355 + }, + { + "ID": "t025", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 384 + }, + { + "ID": "t026", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 382 + }, + { + "ID": "t027", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "t028", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "t029", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 399 + }, + { + "ID": "t030", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 414 + }, + { + "ID": "t031", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "t032", + "Cat": "fence-preamble", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 369 + }, + { + "ID": "t033", + "Cat": "math-in-tikz", + "Kind": "tikz", + "OK": false, + "Err": "! Undefined control sequence.", + "Ms": 323 + }, + { + "ID": "t034", + "Cat": "color", + "Kind": "tikz", + "OK": true, + "Err": "", + "Ms": 353 + }, + { + "ID": "m001", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 364 + }, + { + "ID": "m002", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m003", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 357 + }, + { + "ID": "m004", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 359 + }, + { + "ID": "m005", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 363 + }, + { + "ID": "m006", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m007", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 354 + }, + { + "ID": "m008", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 347 + }, + { + "ID": "m009", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m010", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 361 + }, + { + "ID": "m011", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m012", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 350 + }, + { + "ID": "m013", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 358 + }, + { + "ID": "m014", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 358 + }, + { + "ID": "m015", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 361 + }, + { + "ID": "m016", + "Cat": "form", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 367 + }, + { + "ID": "m017", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 369 + }, + { + "ID": "m018", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 626 + }, + { + "ID": "m019", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 383 + }, + { + "ID": "m020", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 385 + }, + { + "ID": "m021", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 379 + }, + { + "ID": "m022", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 382 + }, + { + "ID": "m023", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 363 + }, + { + "ID": "m024", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 364 + }, + { + "ID": "m025", + "Cat": "ams", + "Kind": "math", + "OK": false, + "Err": ":1:942: Invalid character in tag name", + "Ms": 359 + }, + { + "ID": "m026", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 363 + }, + { + "ID": "m027", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 366 + }, + { + "ID": "m028", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 365 + }, + { + "ID": "m029", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 446 + }, + { + "ID": "m030", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 550 + }, + { + "ID": "m031", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 698 + }, + { + "ID": "m032", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 387 + }, + { + "ID": "m033", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 376 + }, + { + "ID": "m034", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 371 + }, + { + "ID": "m035", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 378 + }, + { + "ID": "m036", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 366 + }, + { + "ID": "m037", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 367 + }, + { + "ID": "m038", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 384 + }, + { + "ID": "m039", + "Cat": "ams", + "Kind": "math", + "OK": false, + "Err": "! Undefined control sequence.", + "Ms": 542 + }, + { + "ID": "m040", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 377 + }, + { + "ID": "m041", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 357 + }, + { + "ID": "m042", + "Cat": "ams", + "Kind": "math", + "OK": false, + "Err": "crash: throw Error(\"Could not find font \" + fontname);", + "Ms": 0 + }, + { + "ID": "m043", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 373 + }, + { + "ID": "m044", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 358 + }, + { + "ID": "m045", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 353 + }, + { + "ID": "m046", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m047", + "Cat": "ams", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: Can be used only in preamble.", + "Ms": 307 + }, + { + "ID": "m048", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 355 + }, + { + "ID": "m049", + "Cat": "ams", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 360 + }, + { + "ID": "m050", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 397 + }, + { + "ID": "m051", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 367 + }, + { + "ID": "m052", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m053", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 356 + }, + { + "ID": "m054", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 370 + }, + { + "ID": "m055", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 374 + }, + { + "ID": "m056", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 371 + }, + { + "ID": "m057", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 361 + }, + { + "ID": "m058", + "Cat": "amssymb", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m059", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 362 + }, + { + "ID": "m060", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 361 + }, + { + "ID": "m061", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 363 + }, + { + "ID": "m062", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 354 + }, + { + "ID": "m063", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 365 + }, + { + "ID": "m064", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 361 + }, + { + "ID": "m065", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 360 + }, + { + "ID": "m066", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 358 + }, + { + "ID": "m067", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 357 + }, + { + "ID": "m068", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 353 + }, + { + "ID": "m069", + "Cat": "core", + "Kind": "math", + "OK": false, + "Err": "! Undefined control sequence.", + "Ms": 312 + }, + { + "ID": "m070", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 359 + }, + { + "ID": "m071", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 360 + }, + { + "ID": "m072", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 348 + }, + { + "ID": "m073", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 352 + }, + { + "ID": "m074", + "Cat": "core", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 355 + }, + { + "ID": "m075", + "Cat": "macros", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 348 + }, + { + "ID": "m076", + "Cat": "macros", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 347 + }, + { + "ID": "m077", + "Cat": "macros", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 352 + }, + { + "ID": "m078", + "Cat": "macros", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 357 + }, + { + "ID": "m079", + "Cat": "color", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 355 + }, + { + "ID": "m080", + "Cat": "color", + "Kind": "math", + "OK": true, + "Err": "", + "Ms": 353 + }, + { + "ID": "m081", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 176 + }, + { + "ID": "m082", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 178 + }, + { + "ID": "m083", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 171 + }, + { + "ID": "m084", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 170 + }, + { + "ID": "m085", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 178 + }, + { + "ID": "m086", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 201 + }, + { + "ID": "m087", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 167 + }, + { + "ID": "m088", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! Undefined control sequence.", + "Ms": 318 + }, + { + "ID": "m089", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 163 + }, + { + "ID": "m090", + "Cat": "mhchem", + "Kind": "math", + "OK": false, + "Err": "! LaTeX Error: File `mhchem.sty' not found.", + "Ms": 165 + } +] \ No newline at end of file