Re: groff performance and a critique of mandoc(1)'s PDF generation (was: Git snapshot tarballs, multiple-pass formatting, and pdfmom)
John Gardner <[email protected]> Sat, 1 Aug 2026 15:05:41 +1000
| Newsgroups | gmane.comp.printing.groff.general |
|---|---|
| Message-ID | <CAGcdajeSrVmDW9pHdt83aTu+aGdooxXUe8HrWh62sjU=KWKcCg@mail.gmail.com> |
At 2026-07-26T16:40:39-0500, G. Branden Robinson wrote: > mandoc(1), unsurprisingly, doesn't typeset pic(1) diagrams (p. 117). > Unfortunately because it also doesn't set a man(7) `EX`/`EE` display in a > monospaced typeface, the fallback ASCII art is mostly unintelligible. I'm surprised mandoc(1) hasn't adopted Pikchr <https://pikchr.org/home/doc/trunk/doc/differences.md> as a \[open-shock-quote]=E2=9C=8C=EF=B8=8F*mOdERn*=E2=9C=8C=EF=B8=8F\[closed-sho= ck-quote] alternative to pic(1). They're even more security- and performance-conscious than mandoc(1)'s devs are (because it does, after all, have to run client-side in browsers to generate some potentially very complex diagrams). Pikchr is designed to be embedded in Markdown code blocks, like this:[1] ``` pikchr arrow right 200% "Markdown" "Source" box rad 10px "Markdown" "Formatter" "(markdown.c)" fit arrow right 200% "HTML+SVG" "Output" arrow <-> down 70% from last box.s box same "Pikchr" "Formatter" "(pikchr.c)" fit ``` which from a preprocessing perspective, isn't much different than a code-block delimited by .PS and .PE macros. Pikchr is a project closely connected to SQLite, and it's used to render railroad diagrams of SQL clause syntax. It's quite efficient, though their disparaging remarks about Troff being a historically significant but now obsolete markup language low-key pisses me off, especially knowing they implicitly mean Markdown is Troff's "modern" replacement. But hey, a tone of similarly-undeserved arrogance can be felt reading mandoc(1)'s description of the Troff typesetting system or the Roff language in general (it's more subdued, but you damn well know they wish request-free mdoc(7) was the only macro package the world uses and cares about). Anyway, Mandoc isn't a typesetting system, and it makes no allusions to being one. I'm not even sure why they bothered with PDF output (because it really *is* shit), and even its HTML output could do with some sanding, so to speak. It's great at what it does: render manual pages for terminal display quickly and competently, where there isn't much wiggle-room for the type of typography bikeshedding that leads to people arguing about the use of fixed-pitch fonts for certain (inline) manual page elements (which happens to be exactly what led to the creation of mono.tmac <https://github.com/Alhadis/Mono/blob/80e8d1df113c6718406ef8a137827301ed7e1= cb2/ono.tmac#L49-L79>'s original macros for controlling how constant-width gets rendered in terminal, if it does at all). Sorry for yet another long-winded post, =E2=80=94 Alhadis *Footnotes:* 1. PIC[2] source taken from Pikchr's homepage <https://pikchr.org/home/doc/trunk/homepage.md>. 2. I adamantly refuse to call this the "Pikchr" language when it's really just a subset of the pic(1) language with extra features thrown i= n. Not unlike how Markdown is just HTML with a niceties used wherever contr= ol over the layout of the output or better styling control isn't necessary = or desired, at which point I just write in the subset of HTML markup permit= ted in Markdown comments. 3. There is no third footnote, I just ramble too much shit. On Mon, 27 Jul 2026 at 07:42, G. Branden Robinson < [email protected]> wrote: > Hi Larry, > > At 2026-07-26T00:50:49-0400, Larry Kollar wrote: > > > G. Branden Robinson <[email protected]> wrote: > > > I'd like to avoid doing what TeX does (or what its usual front-end > > > does), and do what appears to be multi-pass formatting even when > > > it's not necessary. Or maybe it _doesn't_ apply multiple passes > > > unnecessarily, but its ordinary operation is so garrulous that I > > > struggle to tell the difference. > > > > In the years around 2004-2010, I maintained a pretty large[1] firmware > > guide in Groff. Standard procedure was a two-pass format: build the > > table of contents during the first pass, along with all forward > > references, then the second pass resolved all the page numbers. > > Rarely, maybe once or twice a year, I needed a third pass when > > pagination interfered with cross-references. But even on a blue Mac G3 > > (1999 model), typing =E2=80=9Cmake=E2=80=9D and walking to the bathroom= gave me a > > clean PDF before I returned. > > > > I=E2=80=99m not saying there are no use cases where 2026-vintage hardwa= re > > needs help building groff, but I believe it=E2=80=99s true that (beside= s > > Deri=E2=80=99s eye-popping use case) the vast majority of groff users a= ren=E2=80=99t > > going to notice an extra pass when it comes to processor usage. > > Deri's pretty good at finding mad-scientist applications for groff. > It's great! :D > > [...] > > [1] =E2=80=9CPretty large=E2=80=9D =3D around 1000 pages across four do= cs. Later on, I > > consolidated it into a single manual that ran 700-900 pages depending > > on build-time options. > > On my system using my working copy, which is about 28 commits ahead of > our master branch in Savannah, groff produces our 408-page collected man > pages document in about 7 seconds. > > $ rm ./build/doc/groff-man-pages.pdf > $ time make -C build > make: Entering directory '/home/branden/src/GIT/groff/build' > make all-recursive > make[1]: Entering directory '/home/branden/src/GIT/groff/build' > make[2]: Entering directory '/home/branden/src/GIT/groff/build' > GROFF doc/groff-man-pages.pdf > troff: backtrace: file '<standard input>':1075 > troff:<standard input>:1075: warning: special character '.j' not defined > [-w char] > troff: backtrace: file '<standard input>':1481 > troff:<standard input>:1481: warning: special character 'vA' not defined > [-w char] > troff: backtrace: file '<standard input>':1611 > troff:<standard input>:1611: warning: special character 'bs' not defined > [-w char] > troff: backtrace: file '<standard input>':1784 > troff:<standard input>:1784: warning: special character '-+' not defined > [-w char] > troff: backtrace: file '<standard input>':1833 > troff:<standard input>:1833: warning: special character 'coproduct' not > defined [-w char] > troff: backtrace: file '<standard input>':1933 > troff:<standard input>:1933: warning: special character '+e' not defined > [-w char] > make[2]: Leaving directory '/home/branden/src/GIT/groff/build' > make[1]: Leaving directory '/home/branden/src/GIT/groff/build' > make: Leaving directory '/home/branden/src/GIT/groff/build' > > real 0m7.148s > user 0m9.101s > sys 0m0.140s > > This was likely with hot memory and disk buffer caches, though, since > I'd just done a build for another purpose. > > mandoc(1) advocates frequently boast of its speed relative to groff, and > they have a point. I can rig up a rough apples-to-apples comparison by > having that tool produce a PDF of all of groff's man pages. > > $ cat ATTIC/MANS_BUILT > ./build/contrib/chem/chem.1 > ./build/contrib/eqn2graph/eqn2graph.1 > ./build/contrib/gdiffmk/gdiffmk.1 > ./build/contrib/glilypond/glilypond.1 > ./build/contrib/gperl/gperl.1 > ./build/contrib/gpinyin/gpinyin.1 > ./build/contrib/grap2graph/grap2graph.1 > ./build/contrib/hdtbl/groff_hdtbl.7 > ./build/contrib/mm/groff_mm.7 > ./build/contrib/mm/groff_mmse.7 > ./build/contrib/mm/mmroff.1 > ./build/contrib/mom/groff_mom.7 > ./build/contrib/pic2graph/pic2graph.1 > ./build/contrib/rfc1345/groff_rfc1345.7 > ./build/man/groff.7 > ./build/man/groff_char.7 > ./build/man/groff_diff.7 > ./build/man/groff_font.5 > ./build/man/groff_out.5 > ./build/man/groff_tmac.5 > ./build/man/roff.7 > ./build/src/devices/grodvi/grodvi.1 > ./build/src/devices/grohtml/grohtml.1 > ./build/src/devices/grolbp/grolbp.1 > ./build/src/devices/grolj4/grolj4.1 > ./build/src/devices/gropdf/gropdf.1 > ./build/src/devices/grops/grops.1 > ./build/src/devices/grotty/grotty.1 > ./build/src/devices/xditview/gxditview.1 > ./build/src/preproc/eqn/eqn.1 > ./build/src/preproc/eqn/neqn.1 > ./build/src/preproc/grn/grn.1 > ./build/src/preproc/pic/pic.1 > ./build/src/preproc/preconv/preconv.1 > ./build/src/preproc/refer/refer.1 > ./build/src/preproc/soelim/soelim.1 > ./build/src/preproc/tbl/tbl.1 > ./build/src/roff/groff/groff.1 > ./build/src/roff/nroff/nroff.1 > ./build/src/roff/troff/troff.1 > ./build/src/utils/addftinfo/addftinfo.1 > ./build/src/utils/afmtodit/afmtodit.1 > ./build/src/utils/grog/grog.1 > ./build/src/utils/hpftodit/hpftodit.1 > ./build/src/utils/indxbib/indxbib.1 > ./build/src/utils/lkbib/lkbib.1 > ./build/src/utils/lookbib/lookbib.1 > ./build/src/utils/pdfmom/pdfmom.1 > ./build/src/utils/pfbtops/pfbtops.1 > ./build/src/utils/tfmtodit/tfmtodit.1 > ./build/src/utils/xtotroff/xtotroff.1 > ./build/tmac/groff_man.7 > ./build/tmac/groff_man_style.7 > ./build/tmac/groff_mdoc.7 > ./build/tmac/groff_me.7 > ./build/tmac/groff_ms.7 > ./build/tmac/groff_trace.7 > ./build/tmac/groff_www.7 > $ MANS=3D$(< ATTIC/MANS_BUILT) > $ time mandoc -Tpdf $MANS > groff-man-pages-generated-by-mandoc.pdf > > real 0m0.334s > user 0m0.316s > sys 0m0.019s > > Pretty big difference. > > 'Course, there are important caveats. Browsing through the output, I > have some observations. > > * mandoc(1)'s output is not hyperlinked at all, neither internally nor > to external URLs. > > * Because there are no internal hyperlinks, mandoc(1) didn't need to > perform multiple passes. That virtually guarantees a win on elapsed > time to generate the document. > > * There's no navigation pane. Wanna navigate the document? Key in a > page number and play Battleship to find the material you seek, or work > that scroll bar, plebe. > > * In my opinion, mandoc(1)'s output is esthetically inferior, with the > possible exception of the base type size being larger and thus > arguably gentler on the reader. > > * On the other hand, that decision swells the document to 675 pages. > > * We can _easily_ increase the base type size from 10p to 11p or 12p if > we decide that's a good thing to do. I predict the impact on > production time to be negligible. (Worth testing, though!) > > * mandoc(1)'s bullet glyphs are ugly--they're an 'o' overstruck with a > '+', to me, the glyphs seem to lack a precisely identical center. > > * Similar ugliness afflicts groff_mmse(7) (p. 78). Notice how =C3=B6 is > rendered. > > * Neutral apostrophes ' render incorrectly as =E2=80=99 and copy-and-past= e > that way, too. > > * A hyphen-minus - _renders_ as a hyphen =E2=80=90, but copies and pastes > correctly. > > * =C2=B1 renders unhelpfully as "+-" (p. 144). > > * Between bad table layout (see below), refusal to format non-Basic > Latin glyphs, and 1970s line-printer-style overstriking, the entire > groff_char(7) document (pp. 187-222) is an utter disaster. > > * I see errors (missing space between words) in synopsis formatting; > mandoc(1)'s support for the groff man `SY` and `YS` extensions may be > lacking--or, since mandoc(1) hasn't had a release in a few years, > those flaws might be fixed in its "HEAD".[A] > > * To my surprise, example displays are not set in a monospaced font. > People have ridden our rears about failing to do that when generating > HTML,[B] and traditionally mdoc(7) mavens are proud to a fault of > spattering their documents with Courier like cream pie filling at the > end of a Keystone Kops short feature. > > * Table layout is poor. See, e.g., page 46. > > + There's no separation between the columns. > > + The existence of the `BI` (bold-italic) font is ignored. > > + The width of text blocks is computed strongly suboptimally. (The > "x" column modifier appears to be completely ignored.) > > + Even worse, line wrapping is permitted before the period at the end > of a sentence. That's _really_ bad, QA-wise. mandoc(1) doesn't > seem to mess this up when formatting regular text; why would it > here? Ahh, it may be a parser issue. The line that gets > incorrectly broken is a macro call inside a text block. > I@T{ > Indent text by > .BR \[rs]n[Si] . > T} > I'll bet that a macro argument boundary is being misinterpreted as > a break opportunity. This might be due to misinterpretation or > misimplementation of the `\c` escape sequence. Or not, since > mandoc(1) is not a *roff, and the "macros" it recognizes are not > implemented atop a substrate of formatter requests and escape > sequences. > > + In fact, macro calls within tbl(1) text blocks seem to be largely > ignored. Changes of typeface that should occur, don't. See, e.g., > the table on page 47. > > + Table columns are misaligned when the headings are in bold but the > rest of the table is not. See, e.g., the table on page 49. > > + The table spanning pp. 128-129 is so badly damaged that it's nearly > incomprehensible. > > + mandoc(1) oversets not just the line but the page when employing its > "semantic" glyph fallbacks. See p. 220. > > * Some of the widow/orphan management is cringe, but ours often can be > too--this is one reason people want Knuth-Plass. On the other hand, > groff's man pages (and other documents) often mitigate this with > careful use of `ne` requests and I think mandoc(1) might be ignoring > these. So mandoc(1) makes our documents look worse than they should. > (Update: Maybe not. I checked a few cases; I might be seeing more > widows and orphans simply because the type size mandoc(1) uses is > larger, creating more _opportunities_ for widows and orphans.) > > * mandoc(1), unsurprisingly, doesn't typeset pic(1) diagrams (p. 117). > Unfortunately because it also doesn't set a man(7) `EX`/`EE` display > in a monospaced typeface, the fallback ASCII art is mostly > unintelligible. > > * mandoc(1) also doesn't typeset eqn(1) equations (pp. 408-409). I > cannot tell if eqn production is _supposed_ to be supported or not; > the man page disclaims eqn(1) support for its "-T man" and "-T > markdown" output forms, while claiming it for "-T html" but is silent > on the matter for "-T pdf". > > * Separation of man(7) paragraph tags from the paragraph body is too > narrow; see, e.g., the bottom of p. 144. To be fair, groff man(7) > used to have this problem, too.[C] > > * I observe that the mandoc(1)-generated PDF is 11 MB; ours is 1.6 MB. > Bad old bloated GNU stuff once again, eh?[D] > > I terminated my survey (except to peek ahead at eqn(1)) at page 223 of > 675. I think I've seen enough to produce an informed opinion. > > I've attached the respective PDF documents. The different _ordering_ of > the man page documents within is not mandoc(1)'s fault but merely an > artifact of my laziness in employing a shell variable as shown above. > > I don't expect any of these observations to impact BSD/mdoc/mandoc(1) > partisans' assessment of its superiority to groff in any way. It: > > * goes fast; > * isn't copylefted; > * isn't in C++. > > That, it seems, is all they require of it. > > Whether mandoc(1) faithfully manifests the principle of the Unix > philosophy holding that a tool should "do one thing and do it well", I > leave open to consideration. > > Regards, > Branden > > [A] https://cvsweb.bsd.lv/mandoc/?sortby=3Ddate#dirlist > [B] Good news, though! https://savannah.gnu.org/bugs/?68309 > [C] > https://cgit.git.savannah.gnu.org/cgit/groff.git/tree/NEWS?h=3D1.24.1#n54= 7 > [D] > https://www.undeadly.org/cgi?action=3Darticle&sid=3D20100604082319&mode= =3Dexpanded >