diff --git a/primitives.go b/primitives.go index 81b613c..58ec4f9 100644 --- a/primitives.go +++ b/primitives.go @@ -1859,7 +1859,9 @@ func (e *Engine) loadMore() { e.prim("bibliography", func(e *Engine) { e.doBibliography() }) // read .bib, emit thebibliography e.prim("gotex@putbib", func(e *Engine) { e.doPutbib() }) // bibunits: \input the current unit's bu.bbl e.prim("tabular", func(e *Engine) { e.doTabular() }) - e.prim("endtabular", func(e *Engine) {}) // consumed by doTabular; defined for safety + e.prim("tabular*", func(e *Engine) { e.doTabularStar() }) + e.prim("endtabular*", func(e *Engine) {}) // consumed by doTabularStar + e.prim("endtabular", func(e *Engine) {}) // consumed by doTabular; defined for safety e.prim("tabularx", func(e *Engine) { e.doTabularx() }) e.prim("endtabularx", func(e *Engine) {}) // consumed by doTabularx; defined for safety e.prim("minipage", func(e *Engine) { e.doMinipage() }) diff --git a/tabular.go b/tabular.go index c2d118c..fdc094b 100644 --- a/tabular.go +++ b/tabular.go @@ -106,6 +106,32 @@ func (e *Engine) doTabular() { e.place(e.buildTabularBox(aligns, pwidths, vrules, items)) } +// doTabularStar typesets a tabular* environment: \begin{tabular*}{W}[pos]{spec}. +// LaTeX reads the width and then IS tabular, with the alignment packed to it +// (latex.ltx:12092): +// +// \@namedef{tabular*}#1{\setlength\dimen@{#1}\edef\@halignto{to\the\dimen@}\@tabular} +// +// so the width is a target for the inter-column glue, and the body, the rules, +// \\ and & behave exactly as in tabular. Here the assembled box is packed to that +// width, which is what \extracolsep{\fill} in the preamble is asking for; when the +// natural table is wider, packing to a smaller width would overlap columns, so the +// natural box is kept (an overfull \hbox in TeX's terms). +// +// Undefined, its 10 uses in one IEEEtran paper each set their body as running text +// and the paper came out 43 pages against a reference of 27. +func (e *Engine) doTabularStar() { + width := e.readBraceDimen() + e.scanOptBracketToks() // optional [t]/[b]/[c], after the width for tabular* + aligns, pwidths, vrules := e.scanColSpec() + items := e.collectTabularBody("tabular*") + box := e.buildTabularBox(aligns, pwidths, vrules, items) + if width > box.width { + box = hpackSP([]node{box, glueNode{spec: glueSpec{stretch: unity, stretchOrder: 1}}}, packTo, width) + } + e.place(box) +} + // doTabularx typesets a tabularx environment: \begin{tabularx}{W}{spec}. Unlike // tabular it takes a leading {width} argument (a rigid , e.g. \hsize or // \linewidth); every X column in {spec} is a paragraph column whose width is diff --git a/tabular_test.go b/tabular_test.go index c0991ef..28f56dc 100644 --- a/tabular_test.go +++ b/tabular_test.go @@ -1,6 +1,9 @@ package engine -import "testing" +import ( + "strings" + "testing" +) func lastVbox(e *Engine) *boxNode { for i := len(e.mvl) - 1; i >= 0; i-- { @@ -283,3 +286,38 @@ func TestTabularxOptionalPlacement(t *testing.T) { } } } + +// tabular* takes a {width} before its column spec (latex.ltx:12092) and is then +// tabular. Undefined, the width and the spec were typeset as running text and the +// body set as a paragraph: one IEEEtran paper used it ten times. +func TestTabularStarTakesItsWidthAndSetsATable(t *testing.T) { + e := New() + if err := e.LoadLaTeX(); err != nil { + t.Fatal(err) + } + e.SetFont(spMock{}) + src := `\begin{tabular*}{200pt}{@{\extracolsep{\fill}}lr}` + + `un & deux \\ trois & quatre \\` + + `\end{tabular*}\par` + if _, err := e.Run(src); err != nil { + t.Fatal(err) + } + txt := mvlText(e.mvl) + for _, leak := range []string{"200pt", "extracolsep", "lr"} { + if strings.Contains(txt, leak) { + t.Errorf("%q leaked into the text: %q", leak, txt) + } + } + var box *boxNode // packed to the requested width, so an hbox around the table vbox + for _, n := range e.mvl { + if b, ok := n.(*boxNode); ok { + box = b + } + } + if box == nil { + t.Fatal("no table box was placed") + } + if box.width < ptToSP(200) { + t.Errorf("table width %d, want at least the requested 200pt (%d)", box.width, ptToSP(200)) + } +}