diff --git a/classprims_test.go b/classprims_test.go index 17e2a1d..611386f 100644 --- a/classprims_test.go +++ b/classprims_test.go @@ -310,16 +310,22 @@ func pageChars(e *Engine) string { } } } - walk(e.mvl) - walk(e.parList) - // Column-region spans are typeset material too — revtex's frontmatter, a - // figure*/table* band — and they reach the page without passing through the main - // vertical list. + // In PAGE ORDER: a column region's span is typeset material that reaches the page + // ABOVE the region's own material — revtex's frontmatter is one, a figure*/table* + // band is another — and it never passes through the main vertical list. Walking + // the list first and the spans after put a title block AFTER the body it heads. + at := 0 for _, r := range e.colRegions { + if r.at > at { + walk(e.mvl[at:r.at]) + at = r.at + } if r.span != nil { walk([]node{r.span}) } } + walk(e.mvl[at:]) + walk(e.parList) return string(s) } diff --git a/revtex.go b/revtex.go index 9c71e67..6ab1dcc 100644 --- a/revtex.go +++ b/revtex.go @@ -25,6 +25,15 @@ package engine // match against the reference even though every word is present (measured: 2605.12538, // 7 authors, went to 5.8 layout-divergence at 94.5% recall). Comma/semicolon joining // keeps the block height near revtex's, recovering that layout without losing content. +// The ABSTRACT is captured, not typeset where it stands. In revtex the abstract is +// written BEFORE \maketitle and emitted BY it, after the authors; the generic +// \abstract (latex.go) sets it in place, so the playground and every rendered revtex +// paper carried its abstract ABOVE its own title. It is collected into a box with +// \global\setbox — local would be restored by the \endgroup that \end{abstract} +// runs, and the abstract would vanish — and \maketitle places it. An abstract that +// comes AFTER \maketitle (some papers do) is emitted as soon as it closes, so the +// order is right either way and nothing is ever dropped. +// // \author accumulates here — revtex allows several, each followed by its own // \affiliation — rather than overwriting as the base article \author does. It runs at // document time (from \documentclass), where @ is an "other" character, so it opens @@ -47,7 +56,14 @@ const RevtexAuthorBlock = ` \def\preprint#1{} \def\pacs#1{} \def\keywords#1{} -\long\def\maketitle{\par\begin{center}{\large\bfseries\@title\par}\medskip{\@revtexauthors\par}\smallskip{\itshape\@revtexaffils\par}\end{center}\par\bigskip\gotex@revtexbodytwocol} +\newbox\@revtexabsbox +\newif\if@revtexmadetitle +\long\def\abstract{\global\setbox\@revtexabsbox\vbox\bgroup + \centerline{\small\bfseries Abstract}\smallskip + \leftskip=20pt \rightskip=20pt \small} +\def\endabstract{\egroup\if@revtexmadetitle\@revtexputabstract\fi} +\def\@revtexputabstract{\par\bigskip\noindent\box\@revtexabsbox\par} +\long\def\maketitle{\par\begin{center}{\large\bfseries\@title\par}\medskip{\@revtexauthors\par}\smallskip{\itshape\@revtexaffils\par}\end{center}\@revtexputabstract\@revtexmadetitletrue\par\bigskip\gotex@revtexbodytwocol} \makeatother ` diff --git a/revtexabstract_test.go b/revtexabstract_test.go new file mode 100644 index 0000000..13059e1 --- /dev/null +++ b/revtexabstract_test.go @@ -0,0 +1,54 @@ +// Copyright (c) the go-tex/engine authors. +// SPDX-License-Identifier: BSD-3-Clause + +package engine + +import ( + "strings" + "testing" +) + +// In revtex the abstract is written BEFORE \maketitle and emitted BY it, after the +// authors. The generic \abstract sets it where it stands, so every rendered revtex +// paper carried its abstract ABOVE its own title — visible on the deployed +// playground, and on 2203.15077 whose page 1 opened with "Abstract Growing a flat +// lamina…" where the reference opens with "How to grow a flat leaf". +// +// It costs no page, so Σ cannot see it, which is why it survived. +func TestRevtexAbstractFollowsTheTitle(t *testing.T) { + src := `\documentclass[reprint,aps]{revtex4-2}\begin{document}` + + `\title{TITREICI}\author{AUTEURICI}\affiliation{AFFILIATIONICI}` + + `\begin{abstract}RESUMEICI\end{abstract}\maketitle CORPSICI\end{document}` + e, err := compile([]byte(src), Options{Lenient: true}) + if err != nil { + t.Fatal(err) + } + got := pageChars(e) + order := []string{"TITREICI", "AUTEURICI", "AFFILIATIONICI", "RESUMEICI", "CORPSICI"} + at := -1 + for _, want := range order { + i := strings.Index(got, want) + if i < 0 { + t.Fatalf("%q is not on the page: %q", want, got) + } + if i < at { + t.Errorf("%q comes before what should precede it — order is %v in %q", want, order, got) + } + at = i + } +} + +// An abstract written AFTER \maketitle (some papers do) is emitted as soon as it +// closes, so nothing is ever captured into a box that no one empties. +func TestRevtexAbstractAfterMaketitleIsStillSet(t *testing.T) { + src := `\documentclass[reprint,aps]{revtex4-2}\begin{document}` + + `\title{TITREICI}\author{AUTEURICI}\maketitle` + + `\begin{abstract}RESUMEICI\end{abstract} CORPSICI\end{document}` + e, err := compile([]byte(src), Options{Lenient: true}) + if err != nil { + t.Fatal(err) + } + if got := pageChars(e); !strings.Contains(got, "RESUMEICI") { + t.Errorf("the abstract vanished: %q", got) + } +} diff --git a/theorem_test.go b/theorem_test.go index 03beb57..2c955e8 100644 --- a/theorem_test.go +++ b/theorem_test.go @@ -28,16 +28,21 @@ func hasRuleNode(nodes []node) bool { // treeText reads back every typeset character in the main vertical list. func treeText(e *Engine) string { var b strings.Builder - collectChars(e.mvl, &b) - // A column region's SPAN is typeset material that reaches the page without ever - // being on the main vertical list — revtex's frontmatter is one, a figure*/table* - // band is another. Walking mvl alone made a revtex title block look absent when - // it was merely somewhere else. + // In PAGE ORDER: a column region's SPAN reaches the page ABOVE the region's own + // material and never passes through the main vertical list — revtex's frontmatter + // is one, a figure*/table* band is another. Walking mvl alone made a revtex title + // block look absent; walking it first and the spans after put it after the body. + at := 0 for _, r := range e.colRegions { + if r.at > at { + collectChars(e.mvl[at:r.at], &b) + at = r.at + } if r.span != nil { collectChars([]node{r.span}, &b) } } + collectChars(e.mvl[at:], &b) return b.String() }