Skip to content

feat/LaTeX rendering - #17

Merged
adibhanna merged 5 commits into
ZenNotes:mainfrom
spirit-of-light:feat/latex
Oct 27, 2026
Merged

adibhanna merged 5 commits into
ZenNotes:mainfrom
spirit-of-light:feat/latex

Conversation

@spirit-of-light

Copy link
Copy Markdown
Contributor

Functionality

This feature adds the (optional) functionality of rendering LaTeX.
Screenshot 2026-10-26 at 23 20 19
Screenshot 2026-10-26 at 23 21 31

Only content in display math environments ($$ and \[ \]), as well as package-defined environments, such as equation, align, gather, multline, tikzpicture, etc. is rendered. Content inside list items, code fences, quotes, or callouts is not rendered.
To accelerate compilation speed, a precompiled preamble is stored in the same directory as config.toml. If a user wishes to use a package which is not included by default they may add it to the preamble, along with any other settings.

Inline rendering is not implemented, as it affects individual line height and requires more complex implementation.

Settings added

  • [terminal] math = "typst" | "latex" (default = "typst")
  • [terminal] render_timeout = n seconds (default = 10)
  • [terminal] render_concurrency = n (default = 2)

Behavior changes

  • typst pictures now re-render when switching between dark and light themes.
  • Timed-out mermaid and typst renders are retried when a note is opened.
  • Syntax highlighting within LaTeX blocks.
  • Enter closes [ and \begin{…}, under latex only.
  • The MCP note-writing instructions ask for a bare tikzpicture under latex.

Dependencies

These dependencies are not included in the base zn installation. Users who wish to use LaTeX rendering must install these themselves.

  • pdftex
  • pdftocairo
  • standalone, varwidth, preview, xcolor, amsmath, amsfonts, mhchem and pgf LaTeX packages

On macOS:

brew install poppler
brew install --cask basictex
eval "$(/usr/libexec/path_helper)"   # or open a new terminal, so /Library/TeX/texbin is on PATH
sudo tlmgr update --self
sudo tlmgr install standalone varwidth preview xcolor amsmath amsfonts mhchem pgf

The reading view renders display math only through typst, and passes the
block to typst verbatim, so real LaTeX such as \frac{a}{b} falls back to
source. This adds the settings the LaTeX engine builds on: math picks
typst (the default) or latex, render_timeout bounds one external render
in seconds (default 10), and render_concurrency caps how many run at
once (default 2).

A bad value never stops zn from starting: an unknown engine reads as
typst and a non-positive number as the default. zn doctor reports it,
and under math = "latex" it also names a missing pdftex or pdftocairo.
Saving a setting stays strict, the way images already is. The starter
config.toml carries the keys with a comment naming the tools, and the
settings screen offers typst or latex.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
spirit-of-light and others added 4 commits October 27, 2026 00:32
With [terminal] math = "latex", display math renders through the user's
own pdftex instead of typst, so full LaTeX works: amsmath environments,
mhchem and TikZ. $$ blocks, \[ \] (one or many lines) and top-level
equation, align, gather, multline, alignat, flalign, eqnarray (starred
too) and tikzpicture blocks are detected; inside code fences, $$, lists,
quotes and callouts they stay text, and under typst nothing changes.

Each block is a standalone document over preamble.tex in the config
dir. zn writes the default (amsmath, amssymb, mhchem, tikz with
arrows.meta and calc) on the first render and never rewrites it. The
preamble is dumped once into a pdftex format named by its hash, so a
block only pays for itself; a changed preamble builds a new format.
pdftex runs with -no-shell-escape and openin_any=p/openout_any=p, so a
note cannot run commands or write outside the work dir. pdftocairo
turns the PDF into a transparent PNG, cropped to the formula so its
size does not shrink with the pane, coloured with the theme's text
colour, and cached on disk under zn/tex.

Every external render (mmdc, typst, pdftex, pdftocairo) now runs through
one runner that kills it after render_timeout, and through one pool of
render_concurrency slots. A TeX error shows its first line above the
source and is remembered; a timeout is not, so the block renders again
when its note is opened, without looping on every redraw. The editor
highlights math bodies with chroma's tex or typst lexer to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The editor already closes $$ and code fences on Enter. Under latex,
\[ now gets \] and \begin{name} gets \end{name} for any environment
name, with the cursor on the empty line between, and no closer is added
when one already follows. Under typst these lines get a plain Enter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The note-writing instructions tell an AI to emit ```tikz fences, which
the latex engine does not render; it renders a top-level tikzpicture
and loads libraries from preamble.tex. Under math = "latex" the server
now serves a copy of the instructions with the TikZ guidance rewritten
for that: a bare \begin{tikzpicture} block, no \usetikzlibrary line.
The copy comes from six exact replacements, and a test fails if any of
them stops matching, so the typst text stays the single source. The
typst text and a user's override file are unchanged, and the setting is
read once when the server starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README's reading-view section and the in-app help describe the math
engine choice, render_timeout and render_concurrency, where preamble.tex
lives and what it loads. They also say what latex needs: pdftex from a
basic TeX Live plus the packages zn uses, and pdftocairo from poppler.
They warn that TeX can read any file by absolute path, so a note can
show a readable file in the preview, and advise leaving latex off for
untrusted vaults. The Homebrew formula gains caveats with the same
install steps and no new dependency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adibhanna
adibhanna merged commit 844c19f into ZenNotes:main Oct 27, 2026
5 checks passed
@adibhanna

Copy link
Copy Markdown
Contributor

Thank you for this, @spirit-of-light. It's a big, careful piece of work, and the real-TeX tests made reviewing it much easier. It landed unchanged in 844c19f (via #18) and shipped in zn v0.12.0.

The review turned up a few things. They're fixed in separate follow-up commits so your commits stay as you wrote them:

  • Wide blocks were clipped (ab7dede). standalone's varwidth page is \linewidth wide, so a long \[ \] lost its tail and a 20 cm tikzpicture stopped at about 12 cm. When the TeX log reports Overfull \hbox (Npt too wide), that block now renders again with \standaloneconfig{varwidth=\dimexpr\linewidth+Npt\relax}. A block that fits still renders once, so multline and equation numbers stay where they were.
  • MCP instructions (2e3d1f6). The vault is shared with the desktop app, the phones and the share viewer, and they all render ```tikz fences but show a bare tikzpicture as text. So zn mcp keeps serving the desktop's instructions byte for byte under either engine. The preview handles the fence instead: under latex, ```tikz renders through your pipeline, normalised like the desktop's wrapSource (\documentclass is dropped, and \usepackage/\usetikzlibrary lines go before \begin{document}). The default preamble now loads the desktop's TikZ libraries too. Bare tikzpicture blocks still render.
  • Editor colours under typst (47a0609). $$ holds KaTeX LaTeX, and chroma's typst lexer paints every backslash in it as an error (red). $$ bodies are now lexed as tex under both engines, and ```tikz fences as tex as well.
  • Cache growth (21940b4). Formats and PNGs that haven't been used for 30 days are deleted. A session touches the ones it uses, so a second zn with its own preamble keeps its format.
  • Comments (354723e). The AC/Step references point to a spec that isn't in the repo, so they now state the constraint itself. The texEnv comment also said reads were confined, but openin_any=p doesn't stop TeX Live 2026 from reading absolute or ../ paths. Your README warning had that right.

Everything else is as you built it: the shared runner and pool, the format single-flight, the crop, timeout retry on open, the Enter closers, the doctor check and the docs.

MichaelC001 pushed a commit to MichaelC001/zennotes that referenced this pull request Oct 28, 2026
The bundled CLI is the floor a desktop build guarantees, and 0.12.2 is
the newest release (ZenNotes/tui#20). It brings LaTeX to the terminal
preview: with [terminal] math = "latex", zn draws display math and TikZ
through the TeX installed on the user's computer (ZenNotes/tui#17), says
plainly that zn does not include TeX and prints the install commands in
zn doctor, and fits preview pictures to the pane as it grows or shrinks.
The pin is the release's terminal-release.json copied byte for byte
after it verified against packaging/release-signing/zn-release-1.pub in
ZenNotes/tui (source commit 3f1f638a, integration protocol 1, all four
archives it names matching checksums.txt). Staged through
`npm run terminal:stage`; the native darwin-arm64 binary reports
zn v0.12.2. The in-app manual's CLI card says 0.12.2 and what 0.12.0 to
0.12.2 change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants