Skip to content

engine: an align cell keeps its nested environments whole - #153

Merged
tannevaled merged 1 commit into
mainfrom
align-nested-env
Aug 30, 2026
Merged

engine: an align cell keeps its nested environments whole#153
tannevaled merged 1 commit into
mainfrom
align-nested-env

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

collectAlignBody tracked brace depth — and a matrix is not braced. So the & and \\ inside \begin{bmatrix}…&…\\…\end{bmatrix} were read as the outer alignment's own separators, and one matrix became four cells over two rows. The maths layer then refused each fragment:

texmath: \begin{bmatrix} without \end   " = \begin {bmatrix} \mathrm {mat}_\ell \left [\mathcal {D}^1\right ] "
texmath: unexpected "}"                 " \begin {bmatrix} 1}"

A nested \end at depth 0 was worse than mis-split: its name was read and thrown away, so an \end{cases} inside a cell left the environment it closes open.

Environments are now counted as well as braces, and while one is open the separators belong to it. \begin/\end are written back into the cell with the space scanMathSource puts after a control word, so the maths layer reads them exactly as it would have.

Measured — every formula the maths layer drops, 200 arXiv papers

formulas dropped
main 4228
this PR 3702

On the worst documents: 112 → 4, 78 → 10, 122 → 58, 232 → 204.

39 documents change and 4 repaginate — three closer to tectonic, one further — for +2087 glyphs. The page error against the 157 tectonic renders falls 672 → 670 with one more exact match. beamer does not move by one glyph.

Test

alignnestedenv_test.go: bmatrix, cases and a nested array inside align* must each leave the maths layer with nothing to drop — and align must still number two rows (1)(2), since the guard is nesting, not blindness. The first fails on main on all three.

collectAlignBody tracked brace depth, and a matrix is not braced. So the & and \\
inside \begin{bmatrix}…&…\\…\end{bmatrix} were read as the OUTER alignment's own
separators, and one matrix became four cells over two rows. The maths layer then
refused each fragment:

	texmath: \begin{bmatrix} without \end   " = \begin {bmatrix} \mathrm {mat}…"
	texmath: unexpected "}"                 " \begin {bmatrix} 1}"

A nested \end at depth 0 was worse than mis-split: its name was read and thrown
away, so \end{cases} inside a cell left the environment open.

Environments are now counted as well as braces, and while one is open the
separators belong to it. \begin/\end are written back into the cell with the space
scanMathSource puts after a control word, so the maths layer reads them exactly as
it would have.

Measured over the 200-paper arXiv corpus, counting every formula the maths layer
drops: 4228 → 3702, and on the worst documents 112 → 4, 78 → 10, 122 → 58. 39
documents change, 4 repaginate (three closer to tectonic, one further), +2087
glyphs; the page error against the 157 tectonic renders falls 672 → 670 with one
more exact. beamer does not move.
@tannevaled
tannevaled merged commit f2ec042 into main Aug 30, 2026
18 checks passed
tannevaled added a commit that referenced this pull request Aug 31, 2026
#153 said "a matrix is not braced". That is true of this engine and FALSE of TeX,
and the difference is worth stating where the code is read. tex.web §6738-6742:
align_state is increased by 1 for a { and decreased by 1 for a }, and §7259-7264: an
alignment entry ends at a tab or \cr only when align_state is back where it started.
A nested matrix is protected by a real brace, an implicit one:

	\newenvironment{bmatrix}{\left[\env@matrix}{\endmatrix\right]}   amsmath.sty:1082
	\def\env@matrix{…\array{*\c@MaxMatrixCols c}}                    amsmath.sty:1058
	\def\@array[#1]#2{…\bgroup …}                                    latex.ltx:12101

This engine never expands \bmatrix — the maths layer parses the source text itself —
so that \bgroup never arrives and the counter cannot see it. Counting the
environment is the same protection by another road, and the comment now says so.

The reading also turned up something the code did not do: tex.web §7492-7493 counts
IMPLICIT braces, \bgroup and \egroup, in align_state. The cell scanner looked only
at category-1 and -2 characters, so a & inside \bgroup…\egroup cut a cell that TeX
would not have cut. implicitChar is what the box scanner already uses for \bgroup;
the cell scanner now uses it too.

Measured over 200 arXiv papers and 200 beamer talks: not one document changes — the
corpus does not exercise it. The unit test does, on the scanner directly: with
\bgroup x & y\egroup a row has two cells, and had three.
tannevaled added a commit that referenced this pull request Aug 31, 2026
collectTabularBody tracked brace depth, and a matrix is not braced here — the same
fault #153 fixed in the maths alignment scanner, in the scanner next door. So

	$\begin{matrix} a & b \end{matrix}$ & $\begin{bmatrix} c & d \end{bmatrix}$

became four cells of fragments, and the maths layer refused each one:

	texmath: unexpected "}"   "\begin {matrix} d_{\ell _2}}"

224 of the corpus's dropped formulas are that, all of them tables of matrices.

TeX protects a nested alignment with the brace \@array opens (latex.ltx:12101,
reached through amsmath's \env@matrix), counted in align_state (tex.web §6738-6742,
§7259-7264). This engine never expands \matrix, so the environment is counted
instead, and \bgroup/\egroup count as the braces they are (§7492-7493).

A nested tabular was broken the same way and is fixed with it: its \end closed the
OUTER table, so everything after it was swallowed.

Measured over 200 arXiv papers and 200 beamer talks: 15 documents change and 7
repaginate, for +5094 glyphs. One goes 5 -> 14 pages, exactly what tectonic renders;
another 41 -> 35 against 34. The page error against the 157 tectonic renders falls
670 -> 656. beamer does not move by one glyph.
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.

1 participant