I need to step up and help but I just wanted to say that I think this moves
groff forward in a big way.  Well done.

On Thu, Apr 23, 2026 at 03:52:48PM +0100, Deri via discussion of the GNU roff 
typesetting system and related software wrote:
> Hello,
> 
> I am hoping for some help testing some changes to the ms macros. You will 
> need 
> to be running 1.24.0 or current git version (requires Branden's numerous 
> fixes 
> to .asciify). The changes are to add automatic pdf features to any ms 
> document. The features are activated for "-Tpdf" by including "-rPDFFEAT=1" 
> on 
> the groff command line, otherwise the document should render exactly as it 
> does now.
> 
> If you copy the attached files to a new directory and cd into it, you can do 
> the tests with the command:-
> 
> groff -T pdf -M. -ms -rPDFFEAT=1 (other usual options) document.ms > 
> document.pdf
> 
> So:-
> 
> groff -Tpdf -M. -ms -pet -dpapersz=letter -rPDFFEAT=1 -P-pletter /usr/local/
> share/doc/groff-1.24.0/ms.ms > ms.pdf
> 
> Should generate a document with an overview panel, with a title and author in 
> the pdf meta-data, and a clickable Table of Contents. (If you add the line 
> ".RP" before the ".TL" line near the top of the document to add a title page, 
> the TOC should appear after the title page). Setting PDFFEAT to 0, or 
> omitting 
> it, should produce a completely vanilla pdf, as it does now. If using -Tps - 
> PDFFEAT is ignored.
> 
> A new macro call:-
> 
> .XR name [after [before [hotlink-text]]]
> 
> Allows you to insert a local link to elsewhere in the document and an 
> extension to:-
> 
> .NH depth [name]
> 
> Establishes a named destination for an XR link. The attached document "Groff-
> PDF-Features.ms" illustrates the use of named destinations and documents the 
> above additions to ms. Sometimes you want to link to a section "later" in the 
> document, and since groff is a single pass system it is necessary to use a 
> special command. To create the Groff-PDF-Features.pdf use this command:-
> 
> pdfmom --roff -M. -ms -msboxes -tU -P-pa4 -ww -rPDFFEAT=1 Groff-PDF-
> Features.ms > Groff-PDF-Features.pdf
> 
> If you set up a symbolic link as pdfms to pdfmom, this command can be 
> shortened to:-
> 
> pdfms -M. -msboxes -petU -ww -rPDFFEAT=1 Groff-PDF-Features.ms > Groff-PDF-
> Features.pdf
> 
> NOTES
> =====
> 
> If you use .NH with following .XN (rather than the older .NH / text / .XS / 
> text / .XE) the .XN must be on the line immediately following .NH.
> 
> Only the first OUTPUT line created by the text following .NH is used in pdf 
> bookmarks, i.e. if the heading occupies more than 1 output line.
> 
> If your document is already using pdf features, either with pdfmark/spdf or 
> your own macros, don't run with -rPDFFEAT=1, but do check the document is not 
> affected by this version of s.tmac.
> 
> The changes introduced to s.tmac (when -rPDFFEAT=1) are intended to work with 
> any ms document which was written for postscript with no changes required to 
> add pdf features.
> 
> 
> If you have time to test this s.tmac please let me know your results and 
> thoughts.
> 
> Cheers
> 
> Deri

> .ig
> 
> Copyright 1989-2021,2026 Free Software Foundation, Inc.
>           2022-2023 G. Branden Robinson
> 
> Written by James Clark ([email protected])
> Enhanced by: Werner Lemberg <[email protected]>
>              Keith Marshall <[email protected]>
>              G. Branden Robinson <[email protected]>
> 
> This file is part of groff, the GNU roff typesetting system.
> 
> groff is free software; you can redistribute it and/or modify it under
> the terms of the GNU General Public License as published by the Free
> Software Foundation, either version 3 of the License, or
> (at your option) any later version.
> 
> groff is distributed in the hope that it will be useful, but WITHOUT ANY
> WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> for more details.
> 
> You should have received a copy of the GNU General Public License
> along with this program.  If not, see <http://www.gnu.org/licenses/>.
> ..
> .if !\n(.g \
> .  ab groff ms macros require groff extensions; aborting
> .
> .if \n(.C \
> .  ab groff ms macros do not work in compatibility mode; aborting
> .
> .\" See if already loaded.
> .if r GS .nx
> .nr GS 1
> .
> .mso devtag.tmac
> .nr s@devtag-needs-end-of-heading 0
> .nr s@devtag-needs-end-of-title 0
> .
> .\" Define a string for use in diagnostic messages.
> .ds @s s.tmac\"
> .
> .nr @is-initialized 0
> .
> .de @diag
> .     ds *file \" empty
> .     ds *line \" empty
> .     if !'\\n[.F]'' .ds *file \\n[.F]:\"
> .     if !'\\n[.c]'0' .ds *line \\n[.c]:\"
> .     tm \*[@s]:\\*[*file]\\*[*line] \\$*
> .     rm *file
> .     rm *line
> ..
> .
> .de @error
> .     @diag error: \\$*
> ..
> .
> .de @warning
> .     @diag warning: \\$*
> ..
> .
> .de @nop
> ..
> .
> .de @not-implemented
> .@error sorry, .\\$0 not implemented
> .als \\$0 @nop
> ..
> .
> .\" documented Unix Version 7 ms macros that we don't implement
> .als EG @not-implemented \" engineer's notes
> .als IM @not-implemented \" internal memorandum
> .als MF @not-implemented \" memorandum for file
> .als MR @not-implemented \" memorandum for record
> .als TM @not-implemented \" technical memorandum
> .als TR @not-implemented \" technical report
> .als AT @not-implemented \" attachments
> .als CS @not-implemented \" cover sheet info for `TM` documents
> .als CT @not-implemented \" copies to
> .als OK @not-implemented \" "other keywords" for `TM` documents
> .als SG @not-implemented \" signatures for `TM` documents
> .als HO @not-implemented \" Holmdel
> .als IH @not-implemented \" Naperville (Indian Hill)
> .als MH @not-implemented \" Murray Hill
> .als PY @not-implemented \" Piscataway
> .als WH @not-implemented \" Whippany
> .als UX @not-implemented \" Unix w/ footnote trademark
> .
> .\" 4.2BSD ms macros documented by Tuthill 1983 that we don't implement
> .\"als TM @not-implemented \" thesis mode (already handled)
> .\"als CT @not-implemented \" chapter title for thesis (already handled)
> .
> .\" Wrap the .di request to save the name of the file being processed
> .\" when a diversion is started.  This aids diagnostics when diversions
> .\" are still open after input has been processed and \n[.F] is empty.
> .als @divert di
> .de di
> .     if \\n[.$] .ds @last-file-seen \\n[.F]\"
> .     @divert \\$*
> ..
> .
> .if !r PDFFEAT .nr PDFFEAT 0
> .if !'\?\*[.T]\?'\?pdf\?' .nr PDFFEAT 0
> .if \n[PDFFEAT]=1 \{\
> .nr PDFHREF.VIEW.LEADING  10.0p
> .nr nhct 0 1
> .nr shct 0 1
> .nr gottitle*pdf 0
> .ds spdf:txt_default see: +
> .ds pdf:curcol default
> .nr XN*first 0
> .\}
> .
> .de XR
> .if \\n[PDFFEAT]>0 \{\
> .  ds spdf!txt \\*[spdf:txt_default]
> .  ds spdf!opts -D "\\$1"
> .  if \\n(.$>1 .as spdf!opts " -A "\\$2"
> .  if \\n(.$>2 .as spdf!opts " -P "\\$3"
> .  if \\n(.$>3 .ds spdf!txt \\$4
> .  ds spdf!ast \\*[spdf!txt]
> .  ds spdf!ast_q
> .  substring spdf!ast -1 -1
> .  if '\\*[spdf!ast]'+' \{\
> .     ds spdf!ast *
> .     ds spdf!ast_q ""
> .  \}
> .  pdf:lookup \\$1
> .\" .  nop \c
> .  ie !'\\*[pdf:lookup-result]'' \{\
> .    if '\\*[spdf!ast]'*' \{\
> .      chop spdf!txt
> .      as spdf!txt \&\\*[spdf!ast_q]\\*[pdf:lookup-value]\\*[spdf!ast_q]
> .    \}
> .  \}
> .  el \{\
> .    as spdf!txt " Unknown
> .    if !rspdf!unk .tm \
> [pdfms]: \\n[.F]:\\n[.c]: forward reference to '\\$1' detected (please run 
> using 'pdfmom --roff -ms')
> .     nr spdf!unk 1
> .  \}
> .  pdfhref L \\*[spdf!opts] -- \\*[spdf!txt]\c
> .  rm spdf!opts
> .  rm spdf!txt
> .  rm spdf!ast
> .  rm spdf!ast_q
> .\}
> ..
> .\}
> .\" Wrap the `bp` request so that we disregard the enablement of
> .\" no-space mode, which we enter after displays and equations to
> .\" prevent multiple sources of vertical space (like the DD and PD
> .\" registers) from incorrectly accumulating.  Thus, if the user
> .\" requests a page break, we honor it.  See Savannah #62688 & #64005.
> .als @break-page bp
> .de bp
> .     nr @saved-no-space-mode \\n[.ns]
> .     rs
> .     ie \\n[.br] .@break-page \\$1
> .     el          '@break-page \\$1
> .     if \\n[@saved-no-space-mode] .ns
> .     rr @saved-no-space-mode
> ..
> .
> .\" Initialize environments `k` and `nf` immediately so that keeps can
> .\" be used on a cover page.
> .ev k
> .evc 0
> .ev
> .
> .ev nf
> .evc 0
> 'nf
> .ev
> .
> .de @init
> .if !rPO .nr PO \\n(.o
> .\" a non-empty environment
> .ev ne
> \c
> .ev
> .nr @is-initialized 1
> ..
> .ds REFERENCES References
> .ds ABSTRACT ABSTRACT
> .ds TOC Table of Contents
> .ds MONTH1 January
> .ds MONTH2 February
> .ds MONTH3 March
> .ds MONTH4 April
> .ds MONTH5 May
> .ds MONTH6 June
> .ds MONTH7 July
> .ds MONTH8 August
> .ds MONTH9 September
> .ds MONTH10 October
> .ds MONTH11 November
> .ds MONTH12 December
> .ds MO \E*[MONTH\n[mo]]
> .ds DY \n[dy] \*[MO] \n[year]
> .de ND
> .if \\n[.$] .ds DY "\\$*
> ..
> .de DA
> .if \\n[.$] .ds DY "\\$*
> .ds CF \\*[DY]
> ..
> .\" print an error message and then try to recover
> .de @error-recover
> .@error \\$@ (recovering)
> .nr *pop-count 0
> .while !'\\n(.z'' \{\
> .     \"@warning automatically terminating diversion \\n(.z
> .     ie d @div-end!\\n(.z .@div-end!\\n(.z
> .     el .*div-end-default
> .     nr *pop-count +1
> .     \" ensure that we don't loop forever
> .     if \\n[*pop-count]>20 .ab \*[@s]: fatal error: recovery failed
> .\}
> .while !'\\n[.ev]'0' .ev
> .par@reset-env
> .par@reset
> ..
> .de *div-end-default
> .ds *last-div \\n(.z
> .br
> .di
> .ev nf
> .\\*[*last-div]
> .ev
> ..
> .\" ****************************
> .\" ******** module cov ********
> .\" ****************************
> .\" Cover sheet and first page.
> .de cov*err-not-after-first-page
> .@error .\\$0 is not allowed after the first page has started
> ..
> .de cov*err-not-before-tl
> .@error .\\$0 is not allowed before .TL
> ..
> .de cov*err-not-again
> .@error .\\$0 is not allowed more than once
> ..
> .de cov*err-not-after-ab
> .@error .\\$0 is not allowed after first .AB, .LP, .PP, .IP, .SH or .NH
> ..
> .als AU cov*err-not-before-tl
> .als AI cov*err-not-before-tl
> .als AB cov*err-not-before-tl
> .de cov*first-page-init
> .\" Invoked by '.wh 0' trap on first page.
> .\" We should not come here again, but at short page length,
> .\" recursion may occur; remove trap and macro to avoid it.
> .ch cov*first-page-init
> .rm cov*first-page-init
> .if !'\\n[.ev]'0' \{\
> .     ds cov*msg must be in top-level environment, not '\\n[.ev]',\"
> .     as cov*msg " when first page is started\"
> .     @error \\*[cov*msg]
> .     rm cov*msg
> .\}
> .par@init
> .als RP cov*err-not-after-first-page
> .@init
> .ie \\n[cov*use-rp-format] \{\
> .     pg@cs-top
> .     als FS cov*FS
> .     als FE cov*FE
> .\}
> .el \{\
> .     pg@top
> .     als FS @FS
> .     als FE @FE
> .\}
> .wh 0 pg@top
> .CHECK-FOOTER-AND-KEEP
> ..
> .wh 0 cov*first-page-init
> .\" This handles the case where FS occurs before TL or LP.
> .de FS
> .br
> \\*[FS]\\
> ..
> .nr cov*use-rp-format 0
> .\" If we add more cover page formats, these behaviors and names could
> .\" be generalized.
> .nr cov*rp-no-repeat-info 0
> .nr cov*rp-no-renumber 0
> .\" report (AT&T: "released paper") document type
> .de RP
> .nr cov*use-rp-format 1
> .while \\n[.$] \{\
> .     if '\\$1'no' .nr cov*rp-no-repeat-info 1
> .     if '\\$1'no-repeat-info' .nr cov*rp-no-repeat-info 1
> .     if '\\$1'no-renumber' .nr cov*rp-no-renumber 1
> .     shift
> .\}
> .if rPO .po \\n(POu
> .if \\n[PDFFEAT]=1 \{\
> .  if \\n[cov*rp-no-repeat-info]=1 .nr cov*pdf 1
> .  pdfpagename title
> .  nr gottitle*pdf 1
> .
> .\}
> ..
> .de TL
> .br
> .als TL cov*err-not-again
> .rn @AB AB
> .rn @AU AU
> .rn @AI AI
> .di cov*tl-div
> .par@reset
> .ft B
> .ps +2
> .vs +3p
> .ll (u;\\n[LL]*5/6)
> .nr cov*n-au 0
> .DEVTAG-TL
> ..
> .de @AU
> .par@reset
> .if !'\\n(.z'' \{\
> .     br
> .     di
> .\}
> .nr cov*n-au +1
> .di cov*au-div!\\n[cov*n-au]
> .nf
> .ft I
> .ie (\\n[PS] >= 1000) \
> .     ps (\\n[PS]z / 1000u)
> .el \
> .     ps \\n[PS]
> ..
> .de @AI
> .par@reset
> .if !'\\n(.z'' \{\
> .     br
> .     di
> .\}
> .ie !\\n[cov*n-au] .@error .AI before .AU
> .el \{\
> .     di cov*ai-div!\\n[cov*n-au]
> .     nf
> .     ft R
> .     ie (\\n[PS] >= 1000) \
> .             ps (\\n[PS]z / 1000u)
> .     el \
> .             ps \\n[PS]
> .\}
> ..
> .
> .de LP
> .if !'\\n[.z]'' \{\
> .     br
> .     di
> .\}
> .br
> .cov*ab-init
> .cov*print
> .nop \\*[\\$0]\\
> ..
> .
> .als IP LP
> .als PP LP
> .als XP LP
> .als QP LP
> .als RS LP
> .als NH LP
> .als SH LP
> .als MC LP
> .als RT LP
> .als XS LP
> .
> .de cov*ab-init
> .als cov*ab-init @nop
> .als LP @LP
> .als IP @IP
> .als PP @PP
> .als XP @XP
> .als RT @RT
> .als XS @XS
> .als SH @SH
> .als NH @NH
> .als QP @QP
> .als RS @RS
> .als RE @RE
> .als QS @QS
> .als QE @QE
> .als MC @MC
> .als EQ @EQ
> .als EN @EN
> .als PS @PS
> .als TS @TS
> .als AB cov*err-not-after-ab
> .als AU par@AU
> .als AI par@AI
> .als TL par@TL
> ..
> .
> .de @AB
> .if !'\\n(.z'' \{\
> .     br
> .     di
> .\}
> .cov*ab-init
> .ie '\*(.T'html' \{\
> .     cov*tl-au-print
> .     als cov*tl-au-print @nop
> .\}
> .el .di cov*ab-div
> .par@ab-indent
> .par@reset
> .if !'\\$1'no' \{\
> .     if '\*(.T'html' \{\
> .             nf
> .             sp
> .     \}
> .     ft I
> .     ce 1
> \\*[ABSTRACT]
> .     sp
> .     ft R
> .\}
> .ns
> .@PP
> .if '\*(.T'html' \{\
> .     cov*tl-au-print
> .     als cov*tl-au-print @nop
> .     par@reset-env
> .     par@reset
> .     cov*print
> .\}
> ..
> .de AE
> .ie '\*(.T'html' \{\
> .     als AE cov*err-not-again
> .\}
> .el \{\
> .  ie '\\n(.z'cov*ab-div' \{\
> .     als AE cov*err-not-again
> .     br
> .     di
> .\"   nr cov*ab-height \\n[dn]
> .     par@reset-env
> .     par@reset
> .     cov*print
> .  \}
> .  el .@error .AE without .AB
> .\}
> ..
> .de @div-end!cov*ab-div
> .AE
> ..
> .de cov*break-page
> .ie \\n[cov*rp-no-renumber] .@break-page
> .el .@break-page 1
> ..
> .de cov*print
> .als cov*print @nop
> .ie d cov*tl-div \{\
> .     ie \\n[cov*use-rp-format] .cov*rp-print
> .     el .cov*draft-print
> .\}
> .el \{\
> .     if \\n[cov*use-rp-format] \{\
> .             @warning .RP format but no .TL
> .             .cov*break-page
> .             als FS @FS
> .             als FE @FE
> .             CHECK-FOOTER-AND-KEEP
> .     \}
> .     br
> .\}
> .if \\n[PDFFEAT]=1 \{\
> .  if d cov*tl-div \{\
> .    asciify cov*tl-div
> .    chop cov*tl-div
> .    pdfinfo /Title \\*[cov*tl-div]
> .  \}
> .  ds pdfauthor \"
> .  nr cov*i 1
> .  while \\n[cov*i]<=\\n[cov*n-au] \{\
> .    asciify cov*au-div!\\n[cov*i]
> .    chop cov*au-div!\\n[cov*i]
> .    if \\n[cov*i]>1 .as pdfauthor ", "
> .    as pdfauthor \\*[cov*au-div!\\n[cov*i]]
> .    nr cov*i +1
> .  \}
> .  pdfinfo /Author "\\*[pdfauthor]"
> .\}
> ..
> .de cov*rp-print
> .nr cov*page-length \\n[.p]
> .pl 1000i
> .cov*tl-au-print
> .sp 3
> .if d cov*ab-div \{\
> .  if !'\*(.T'html'  .nf
> .       cov*ab-div
> .\}
> .sp 3
> .par@reset
> \\*[DY]
> .br
> .if \\n[cov*fn-height] \{\
> .     sp |(u;\\n[cov*page-length]-\\n[FM]\
> -\\n[cov*fn-height]-\\n[fn@sep-dist]>?\\n[nl])
> .     fn@print-sep
> .     ev nf
> .     cov*fn-div
> .     ev
> .     ie \\n[cov*rp-no-repeat-info] .rm cov*fn-div
> .     el \{\
> .             rn cov*fn-div fn@overflow-div
> .             nr fn@have-overflow 1
> .     \}
> .\}
> .als FS @FS
> .als FE @FE
> .CHECK-FOOTER-AND-KEEP
> .\" If anything was printed below where the footer line is normally
> .\" printed, then that's an overflow.
> .if -\\n[FM]/2+1v+\\n[cov*page-length]<\\n[nl] \
> .     @error cover sheet overflow
> .pl \\n[cov*page-length]u
> .cov*break-page
> .if !\\n[cov*rp-no-repeat-info] .cov*tl-au-print
> .rs
> .sp 1
> ..
> .de cov*draft-print
> .cov*tl-au-print
> .if d cov*ab-div \{\
> .     nf
> .     sp 2
> .     cov*ab-div
> .\}
> .sp 1
> ..
> .de cov*tl-au-print
> .par@reset
> .nf
> .rs
> .sp 3
> .ce \\n[.R]
> .if d cov*tl-div \{\
> .     cov*tl-div
> .     DEVTAG-EO-TL
> .\}
> .nr cov*i 1
> .nr cov*sp 1v
> .while \\n[cov*i]<=\\n[cov*n-au] \{\
> .     ie '\*(.T'html' .br
> .     el .sp \\n[cov*sp]u
> .     cov*au-div!\\n[cov*i]
> .     ie d cov*ai-div!\\n[cov*i] \{\
> .             sp .5v
> .             cov*ai-div!\\n[cov*i]
> .             nr cov*sp 1v
> .     \}
> .     el .nr cov*sp .5v
> .     nr cov*i +1
> .\}
> .ce 0
> ..
> .nr cov*fn-height 0
> .nr cov*in-fn 0
> .\" start of footnote on cover
> .de cov*FS
> .if \\n[cov*in-fn] \{\
> .     @error nested .FS
> .     FE
> .\}
> .nr cov*in-fn 1
> .ev fn
> .par@reset-env
> .da cov*fn-div
> .if !\\n[cov*fn-height] .ns
> .ie \\n[.$] .FP "\\$1" no
> .el .@LP
> ..
> .de @div-end!cov*fn-div
> .cov*FE
> ..
> .\" end of footnote on cover
> .de cov*FE
> .ie '\\n(.z'cov*fn-div' \{\
> .     br
> .     ev
> .     di
> .     nr cov*in-fn 0
> .     nr cov*fn-height +\\n[dn]
> .\}
> .el .@error .FE without matching .FS
> ..
> .\" ***************************
> .\" ******** module pg ********
> .\" ***************************
> .\" Page-level formatting.
> .\" > 0 if we have a footnote on the current page
> .nr pg@fn-flag 0
> .nr pg@colw 0
> .nr pg@fn-colw 0
> .nr HM 1i
> .nr FM 1i
> .ds LF
> .ds CF
> .ds RF
> .ds LH
> .ds CH -\En[%]-\"
> .ds RH
> .ds pg*OH '\E*[LH]'\E*[CH]'\E*[RH]'
> .ds pg*EH '\E*[LH]'\E*[CH]'\E*[RH]'
> .ds pg*OF '\E*[LF]'\E*[CF]'\E*[RF]'
> .ds pg*EF '\E*[LF]'\E*[CF]'\E*[RF]'
> .de OH
> .ds pg*\\$0 "\\$*
> ..
> .als EH OH
> .als OF OH
> .als EF OH
> .aln PN % \" Lesk 1978 documents PN.
> .de PT
> .\" To compare the page number to 1, we need it in Arabic format.
> .ds pg*saved-page-number-format \\g%\"
> .af % 0
> .nr pg*page-number-in-decimal \\n%
> .af % \\*[pg*saved-page-number-format]
> .fam \\*[pg@titles-font-family]
> .ie \\n[pg*page-number-in-decimal]=1 .if \\n[pg*P1] .tl \\*[pg*OH]
> .el \{\
> .     ie o .tl \\*[pg*OH]
> .     el .tl \\*[pg*EH]
> .\}
> .rm pg*saved-page-number-format
> ..
> .de BT
> .fam \\*[pg@titles-font-family]
> .ie o .tl \\*[pg*OF]
> .el .tl \\*[pg*EF]
> ..
> .nr pg*P1 0
> .de P1
> .nr pg*P1 1
> ..
> .\" Establish traps for the bottom of the text area and the footer line.
> .\" Various macros move them, but pg@top restores them at each new page.
> .wh -\n[FM]u pg@bottom
> .wh -\n[FM]u/2u-1v pg*footer
> .nr MINGW 2n
> .nr pg@ncols 1
> .de @MC
> .if !'\\n(.z'' .@error-recover .MC while diversion open
> .br
> .ie \\n[pg@ncols]>1 .pg@super-eject
> .el \{\
> .     \" flush out any floating keeps
> .     while \\n[kp@tail]>\\n[kp@head] \{\
> .             rs
> .             @break-page
> .     \}
> .\}
> .ie !\\n(.$ \{\
> .     nr pg*gutw \\n[MINGW]
> .     nr pg@colw \\n[LL]-\\n[pg*gutw]/2u
> .     nr pg@ncols 2
> .\}
> .el \{\
> .     nr pg@colw (n;\\$1)<?\\n[LL]
> .     ie \\n[.$]<2 .nr pg*gutw \\n[MINGW]
> .     el .nr pg*gutw (n;\\$2)
> .     nr pg@ncols \\n[LL]-\\n[pg@colw]/(\\n[pg@colw]+\\n[pg*gutw])+1
> .     ie \\n[pg@ncols]>1 \
> .             nr pg*gutw 
> \\n[LL]-(\\n[pg@ncols]*\\n[pg@colw])/(\\n[pg@ncols]-1)
> .     el .nr pg*gutw 0
> .\}
> .DEVTAG ".mc \\n[pg@ncols] \\n[pg@colw] \\n[pg*gutw]"
> .nr pg*col-num 0
> .nr pg@fn-colw \\n[pg@colw]*\\*[FR]
> .par@reset
> .ns
> .mk pg*col-top
> ..
> .de 2C
> .MC
> ..
> .de 1C
> .MC \\n[LL]u
> ..
> .de pg@top
> .\" invoked by '.wh 0' trap at the top of every page
> .\"
> .\" At short page lengths, footers may get truncated or recursion may
> .\" occur; ensure that the page length suffices to avoid these problems.
> .if (u;\\n[HM]+\\n[FM]+\n[.V]>=\\n[.p]) \{\
> .     @error insufficient page length; aborting\"
> .     pl \\n[nl]u
> .     ab
> .\}
> .ch pg*footer -\\n[FM]u/2u-1v
> .nr pg*col-num 0
> .nr pg@fn-bottom-margin 0
> .po \\n[PO]u
> .ev h
> .par@reset
> .sp (u;\\n[HM]/2)
> .PT
> .sp |\\n[HM]u
> .if d HD .HD
> .mk pg@header-bottom
> .ev
> .mk pg*col-top
> .pg*start-col
> ..
> .de pg*start-col
> .\" Handle footnote overflow before floating keeps, because the keep
> .\" might contain an embedded footnote.
> .fn@top-hook
> .kp@top-hook
> .tbl@top-hook
> .ns
> ..
> .de pg@cs-top
> .sp \\n[HM]u
> .\" move pg@bottom and pg*footer out of the way
> .ch pg@bottom \\n[.p]u*2u
> .ch pg*footer \\n[.p]u*2u
> .ns
> ..
> .de pg@bottom
> .tbl@bottom-hook
> .if \\n[pg@fn-flag] .fn@bottom-hook
> .nr pg*col-num +1
> .ie \\n[pg*col-num]<\\n[pg@ncols] .pg*end-col
> .el .pg*end-page
> ..
> .de pg*end-col
> 'sp |\\n[pg*col-top]u
> .po (u;\\n[PO]+(\\n[pg@colw]+\\n[pg*gutw]*\\n[pg*col-num]))
> .\"po +(u;\\n[pg@colw]+\\n[pg*gutw])
> .pg*start-col
> ..
> .de pg*end-page
> .po \\n[PO]u
> .\" Make sure we don't exit if there are still floats or footnotes
> .\" left-over.
> .ie \\n[kp@head]<\\n[kp@tail]:\\n[fn@have-overflow] \{\
> .     \" Switching environments ensures that we don't get an unnecessary
> .     \" blank line at the top of the page.
> .     ev ne
> '     @break-page
> .     ev
> .\}
> .el \{\
> .     \" If the text has ended and there are no more footnotes or
> .     \" keeps, exit.
> .     if \\n[pg@text-ended] .ex
> .     if r pg*next-number \{\
> .             pn \\n[pg*next-number]
> .             rr pg*next-number
> .             if d pg*next-format \{\
> .                     af % \\*[pg*next-format]
> .                     rm pg*next-format
> .             \}
> .     \}
> '     @break-page
> .\}
> ..
> .\" pg@begin number format
> .de pg@begin
> .ie \\n[.$]>0 \{\
> .     nr pg*next-number (;\\$1)
> .     ie \\n[.$]>1 .ds pg*next-format \\$2
> .     el .rm pg*next-format
> .\}
> .el .rr pg*next-number
> .pg@super-eject
> ..
> .\" print the footer line
> .de pg*footer
> .ev h
> .par@reset
> .BT
> .ev
> ..
> .\" flush out any keeps or footnotes
> .de pg@super-eject
> .br
> .if !'\\n(.z'' \{\
> .     ds @msg diversion open while ejecting page\"
> .     as @msg " (last file seen: \\*[@last-file-seen])\"
> .     @error-recover \\*[@msg]
> .     rm @msg
> .\}
> .\" Make sure we stay in the end macro while there is still footnote
> .\" overflow left, or floating keeps.
> .while \\n[kp@tail]>\\n[kp@head]:\\n[pg@fn-flag] \{\
> .     rs
> .     @break-page
> .\}
> .@break-page
> ..
> .nr pg@text-ended 0
> .de pg@end-text
> .br
> .nr pg@text-ended 1
> .pg@super-eject
> ..
> .em pg@end-text
> .\" ***************************
> .\" ******** module fn ********
> .\" ***************************
> .\" Footnotes.
> .nr fn@sep-dist 8p
> .ev fn
> .\" Round it vertically
> .vs \n[fn@sep-dist]u
> .nr fn@sep-dist \n[.v]
> .ev
> .nr fn*text-num 0 1
> .nr fn*note-num 0 1
> .nr fn*open 0
> .\" Handle initialition tasks deferred until par module is set up.
> .de fn@init
> .ie t .als fn@mark-start par@sup-start
> .el .ds fn@mark-start [
> .ie t .als fn@mark-end par@sup-end
> .el .ds fn@mark-end ]
> .ds * \E*[fn@mark-start]\En+[fn*text-num]\E*[fn@mark-end]
> ..
> .\" normal FS
> .\" FS-MARK is a user definable hook, which may be used to perform
> .\" any set-up actions, (e.g. planting an HREF link as the footnote
> .\" mark, in the document text); passed the same arguments as have
> .\" been passed to FS itself, unless redefined, it is a no-op.
> .de @FS
> .FS-MARK \\$@
> .ie \\n[.$] .fn*do-FS "\\$1" no
> .el \{\
> .     ie \\n[fn*text-num]>\\n[fn*note-num] .fn*do-FS \\n+[fn*note-num]
> .     el .fn*do-FS
> .\}
> ..
> .\" Default no-op fallback for FS-MARK
> .de FS-MARK
> ..
> .\" Second argument of 'no' means don't embellish the first argument.
> .de fn*do-FS
> .if \\n[fn*open] .@error-recover nested .FS
> .nr fn*open 1
> .if \\n[.u] \{\
> .     \" Ensure that the first line of the footnote is on the same page
> .     \" as the reference.  I think this is minimal.
> .     ev fn
> .     nr fn*need 1v
> .     ev
> .     ie \\n[pg@fn-flag] .nr fn*need +\\n[fn:PD]
> .     el .nr fn*need +\\n[fn@sep-dist]
> .     ne \\n[fn*need]u+\\n[.V]u>?0
> .\}
> .ev fn
> .par@reset-env
> .fn*start-div
> .par@reset
> .fam \\*[fn@font-family]
> .ie \\n[.$] .FP \\$@
> .el .@LP
> ..
> .de @FE
> .ie !\\n[fn*open] .@error .FE without .FS
> .el \{\
> .     nr fn*open 0
> .     br
> .     ev
> .     fn*end-div
> .\}
> ..
> .nr fn@have-overflow 0
> .\" called at the top of each column
> .de fn@top-hook
> .nr fn*max-width 0
> .nr fn*page-bottom-pos 0-\\n[FM]-\\n[pg@fn-bottom-margin]
> .ch pg@bottom \\n[fn*page-bottom-pos]u
> .if \\n[fn@have-overflow] \{\
> .     nr fn@have-overflow 0
> .     fn*start-div
> .     ev nf
> .     fn@overflow-div
> .     ev
> .     fn*end-div
> .\}
> ..
> .\" This is called at the bottom of the column if pg@fn-flag is set.
> .de fn@bottom-hook
> .nr pg@fn-flag 0
> .nr fn@have-overflow 0
> .nr fn@bottom-pos \\n[.p]-\\n[FM]-\\n[pg@fn-bottom-margin]+\\n[.v]
> .ev fn
> .nr fn@bottom-pos -\\n[.v]
> .ev
> .ie \\n[nl]+\\n[fn@sep-dist]+\n[.V]>\\n[fn@bottom-pos] \{\
> .     rn fn@div fn@overflow-div
> .     nr fn@have-overflow 1
> .\}
> .el \{\
> .     if \\n[pg@ncols]>1 \
> .             if \\n[fn*max-width]>\\n[pg@fn-colw] \
> .                     nr pg@fn-bottom-margin \\n[.p]-\\n[FM]-\\n[nl]+1v
> .     wh \\n[fn@bottom-pos]u fn*catch-overflow
> .     fn@print-sep
> .     ev nf
> .     fn@div
> .     rm fn@div
> .     ev
> .     if '\\n(.z'fn@overflow-div' \{\
> .             di
> .             nr fn@have-overflow \\n[dn]>0
> .     \}
> .     ch fn*catch-overflow
> .\}
> ..
> .de fn*catch-overflow
> .di fn@overflow-div
> ..
> .nr fn*embed-count 0
> .de @div-end!fn@div
> .br
> .if '\\n[.ev]'fn' .ev
> .fn*end-div
> .nr fn*open 0
> ..
> .als @div-end!fn*embed-div @div-end!fn@div
> .de fn*start-div
> .ie '\\n(.z'' \{\
> .     da fn@div
> .     if !\\n[pg@fn-flag] .ns
> .\}
> .el .di fn*embed-div
> ..
> .de fn*end-div
> .ie '\\n(.z'fn@div' \{\
> .     di
> .     nr fn*page-bottom-pos -\\n[dn]
> .     nr fn*max-width \\n[fn*max-width]>?\\n[dl]
> .     if !\\n[pg@fn-flag] .nr fn*page-bottom-pos -\\n[fn@sep-dist]
> .     nr pg@fn-flag 1
> .     nr fn*page-bottom-pos \\n[nl]-\\n[.p]+\n[.V]>?\\n[fn*page-bottom-pos]
> .     ch pg@bottom \\n[fn*page-bottom-pos]u
> .\}
> .el \{\
> .     ie '\\n(.z'fn*embed-div' \{\
> .     di
> .             rn fn*embed-div fn*embed-div!\\n[fn*embed-count]
> \!.           fn*embed-start \\n[fn*embed-count]
> .             rs
> '             sp (u;\\n[dn]+\\n[fn@sep-dist]+\\n[.V])
> \!.           fn*embed-end
> .             nr fn*embed-count +1
> .     \}
> .     el \{\
> .             ev fn
> .             @error-recover unclosed diversion within footnote
> .     \}
> .\}
> ..
> .de fn*embed-start
> .ie '\\n(.z'' \{\
> .     fn*start-div
> .     ev nf
> .     fn*embed-div!\\$1
> .     rm fn*embed-div!\\$1
> .     ev
> .     fn*end-div
> .     di fn*null
> .\}
> .el \{\
> \!.   fn*embed-start \\$1
> .     rs
> .\}
> ..
> .de fn*embed-end
> .ie '\\n(.z'fn*null' \{\
> .     di
> .     rm fn*null
> .\}
> .el \!.fn*embed-end
> ..
> .\" It's important that fn@print-sep use up exactly fn@sep-dist vertical
> .\" space.
> .de fn@print-sep
> .ev fn
> .in 0
> .vs \\n[fn@sep-dist]u
> \D'l 1i 0'
> .br
> .ev
> ..
> .\" ***************************
> .\" ******** module kp ********
> .\" ***************************
> .\" Keeps.
> .de KS
> .br
> .di kp@div
> ..
> .de KF
> .if !'\\n(.z'' .@error-recover .KF while diversion open
> .di kp@fdiv
> .ev k
> .if \\n[@is-initialized] \{\
> .     par@reset-env
> .     par@reset
> .\}
> ..
> .de KE
> .nr kp*did-closure-succeed 0
> .if '\\n(.z'kp@div'  .kp*end
> .if '\\n(.z'kp@fdiv' .kp*fend
> .if !\\n[kp*did-closure-succeed] .@error .KE without .KS or .KF
> .rr kp*did-closure-succeed
> ..
> .de @div-end!kp@div
> .kp*end
> ..
> .de @div-end!kp@fdiv
> .kp*fend
> ..
> .de kp*need
> .ie '\\n(.z'' .ds@need \\$1
> .el \!.kp*need \\$1
> ..
> .\" end non-floating keep
> .de kp*end
> .br
> .di
> .kp*need \\n[dn]
> .ev nf
> .kp@div
> .ev
> .rm kp@div
> .nr kp*did-closure-succeed 1
> ..
> .\" Floating keeps.
> .nr kp@head 0
> .nr kp@tail 0
> .\" end floating keep
> .de kp*fend
> .br
> .ev
> .di
> .ie \\n[.t]-(\\n[.k]>0*1v)>\\n[dn] \{\
> .     br
> .     ev nf
> .     kp@fdiv
> .     rm kp@fdiv
> .     ev
> .\}
> .el \{\
> .     rn kp@fdiv kp@div!\\n[kp@tail]
> .     nr kp*ht!\\n[kp@tail] 0\\n[dn]
> .     nr kp@tail +1
> .\}
> .nr kp*did-closure-succeed 1
> ..
> .\" top of page processing for KF
> .nr kp*doing-top 0
> .de kp@top-hook
> .if !\\n[kp*doing-top] \{\
> .     nr kp*doing-top 1
> .     kp*do-top
> .     nr kp*doing-top 0
> .\}
> ..
> .de kp*do-top
> .\" If the first keep won't fit, only force it out if we haven't had a
> .\" footnote and we're at the top of the page.
> .nr kp*force \\n[pg@fn-flag]=0&(\\n[nl]<=\\n[pg@header-bottom])
> .nr kp*fits 1
> .while \\n[kp@tail]>\\n[kp@head]&\\n[kp*fits] \{\
> .     ie \\n[.t]>\\n[kp*ht!\\n[kp@head]]:\\n[kp*force] \{\
> .             nr kp*force 0
> .             \" It's important to advance kp@head before bringing
> .             \" back the keep, so that if the last line of the
> .             \" last keep springs the bottom of page trap, a new
> .             \" page will not be started unnecessarily.
> .             rn kp@div!\\n[kp@head] kp*temp
> .             nr kp@head +1
> .             ev nf
> .             kp*temp
> .             ev
> .             rm kp*temp
> .     \}
> .     el .nr kp*fits 0
> .\}
> ..
> .\" ***************************
> .\" ******** module ds ********
> .\" ***************************
> .\" Displays and non-floating keeps.
> .de DE
> .ds*end!\\n[\\n[.ev]:ds-type]
> .nr \\n[.ev]:ds-type 0
> .ns
> ..
> .de ds@auto-end
> .if \\n[\\n[.ev]:ds-type] \{\
> .     @error automatically terminating display
> .     DE
> .\}
> ..
> .de @div-end!ds*div
> .ie \\n[\\n[.ev]:ds-type] .DE
> .el .ds*end!2
> ..
> .de ds*end!0
> .@error .DE without .DS, .ID, .CD, .LD, .RD, or .BD
> ..
> .de LD
> .br
> .nr \\n[.ev]:ds-type 1
> .par@reset
> .nf
> .sp \\n[DD]u
> ..
> .de ID
> .LD
> .ie \\n[.$] .in +(n;\\$1)
> .el .in +\\n[DI]u
> ..
> .de CD
> .LD
> .ce \\n[.R]
> ..
> .de RD
> .LD
> .rj \\n[.R]
> ..
> .de ds*common-end
> .par@reset
> .sp \\n[DD]u
> ..
> .als ds*end!1 ds*common-end
> .de BD
> .LD
> .nr \\n[.ev]:ds-type 2
> .di ds*div
> ..
> .de ds*end!2
> .br
> .ie '\\n(.z'ds*div' \{\
> .     di
> .     nf
> .     in (u;\\n[.l]-\\n[dl]/2>?0)
> .     ds*div
> .     rm ds*div
> .     ds*common-end
> .\}
> .el .@error-recover mismatched .DE
> ..
> .de DS
> .\" Break to ensure that we spring the top-of-page trap (at the start of
> .\" a document, this also ensures that important registers like `PS` are
> .\" set).  Also, even if the drawing position is elsewhere and invalid
> .\" nesting occurs, the user will still expect a break.
> .br
> .if '\\n(.z'ds*div' .@error-recover cannot begin display within display
> .nr ds*badarg 0
> .di ds*div
> .ie '\\$1'B' \{\
> .     LD
> .     nr \\n[.ev]:ds-type 4
> .\}
> .el \{\
> .     ie '\\$1'L' .LD
> .     el \{\
> .             ie '\\$1'C' .CD
> .             el \{\
> .                     ie '\\$1'R' .RD
> .                     el \{\
> .                             ie '\\$1'I' .ID \\$2
> .                             el \{\
> .                                     ie '\\$1'' .ID
> .                                     el .nr ds*badarg 1
> .                             \}
> .                     \}
> .             \}
> .     \}
> .     ie \\n[ds*badarg] \{\
> .             ds ds*msg unrecognized argument '\\$1' to .\\$0;\"
> .             as ds*msg " did you mean '.\\$0 I \\$1'?\"
> .             @error \\*[ds*msg]
> .             rm ds*msg
> .             di
> .     \}
> .     el .nr \\n[.ev]:ds-type 3
> .\}
> .rr ds*badarg
> ..
> .de ds@need
> .if '\\n(.z'' \{\
> .     while \\n[.t]<=(\\$1)&(\\n[nl]>\\n[pg@header-bottom]) \{\
> .             rs
> '             sp \\n[.t]u
> .     \}
> .\}
> ..
> .de ds*end!3
> .br
> .ie '\\n(.z'ds*div' \{\
> .     di
> .     ds@need \\n[dn]
> .     ev nf
> .     ds*div
> .     ev
> .     rm ds*div
> .     ds*common-end
> .\}
> .el .@error-recover mismatched .DE
> ..
> .de ds*end!4
> .ie '\\n(.z'ds*div' \{\
> .     br
> .     di
> .     nf
> .     in (u;\\n[.l]-\\n[dl]/2>?0)
> .     ds@need \\n[dn]
> .     ds*div
> .     rm ds*div
> .     ds*common-end
> .\}
> .el .@error-recover mismatched .DE
> ..
> .\" ****************************
> .\" ******** module par ********
> .\" ****************************
> .\" Paragraph-level formatting.
> .\" Load time initialization.
> .de par@load-init
> .\" PS and VS might have been set on the command line
> .if !rPS .nr PS 10
> .if !rLL .nr LL 6.5i
> .ll \\n[LL]u
> .\" don't set LT so that it can be defaulted from LL
> .ie rLT .lt \\n[LT]u
> .el .lt \\n[LL]u
> .ie (\\n[PS] >= 1000) \
> .     ps (\\n[PS]z / 1000u)
> .el \
> .     ps \\n[PS]
> .\" don't set VS so that it can be defaulted from PS
> .\" Set vertical spacing defaults to 120% of type size.
> .ie rVS \{\
> .     ie (\\n[VS] >= 1000) \
> .             par*vs "(\\n[VS]p / 1000u)"
> .     el \
> .             par*vs \\n[VS]
> .\}
> .el \{\
> .     ie (\\n[PS] >= 1000) \
> .             par*vs "((\\n[PS]p / 1000u) * 120u / 100u)"
> .     el \
> .             par*vs "(\\n[PS] * 120u / 100u)"
> .\}
> .if dFAM .fam \\*[FAM]
> .if !rHY .nr HY 6
> .hy \\n[HY]
> .TA
> .CHECK-FOOTER-AND-KEEP
> ..
> .de par*vs
> .\" If it's too big to be in points, treat it as units.
> .ie (p;\\$1)>=40p .vs (u;\\$1)
> .el .vs (p;\\$1)
> ..
> .de par@ab-indent
> .nr 0:li (u;\\n[LL]/12)
> .nr 0:ri \\n[0:li]
> ..
> .de par*env-init
> .aln \\n[.ev]:PS PS
> .aln \\n[.ev]:VS VS
> .aln \\n[.ev]:LL LL
> .aln \\n[.ev]:MCLL LL
> .aln \\n[.ev]:LT LT
> .aln \\n[.ev]:MCLT LT
> .aln \\n[.ev]:PI PI
> .aln \\n[.ev]:PD PD
> .ad \\n[par*adj]
> .par@reset-env
> ..
> .\" happens when the first page begins
> .de par@init
> .if !rLT .nr LT \\n[LL]
> .if !rFL .nr FL \\n[LL]*\\*[FR]
> .\" Set vertical spacing defaults to 120% of type size.
> .if !rVS \{\
> .     ie (\\n[PS] >= 1000) \
> .             nr VS (\\n[PS]p / 1000u * 120u / 100u)
> .     el \
> .             nr VS (\\n[PS] * 120u / 100u)
> .\}
> .if !rDI .nr DI .5i
> .if !rFPS \{\
> .     ie (\\n[PS] >= 1000) \
> .             nr FPS (\\n[PS] - 2000)
> .     el \
> .             nr FPS (\\n[PS] - 2)
> .\}
> .if !rFVS \{\
> .     ie (\\n[FPS] >= 1000) \
> .             nr FVS (\\n[FPS]p / 1000u * 120u / 100u)
> .     el \
> .             nr FVS (\\n[FPS] * 120u / 100u)
> .\}
> .\" don't change environment 0
> .ev h
> .ie (\\n[PS] >= 1000) \
> .     ps (\\n[PS]z / 1000u)
> .el \
> .     ps \\n[PS]
> .if !rQI .nr QI 5n
> .if !rPI .nr PI 5n
> .ie (\\n[VS] >= 1000) \
> .     par*vs "(\\n[VS]p / 1000u)"
> .el \
> .     par*vs \\n[VS]
> .if !rPD .nr PD .3v>?\n(.V
> .if !rDD .nr DD .5v>?\n(.V
> .if !rFI .nr FI 2n
> .if !rFPD .nr FPD \\n[PD]/2
> .ev
> .if !dFAM .ds FAM \\n[.fam]
> .ds pg@titles-font-family \\*[FAM]
> .ds fn@font-family \\*[FAM]
> .nr par*adj \\n[.j]
> .par*env-init
> .ev h
> .par*env-init
> .ev
> .ev fn
> .par*env-init
> .ev
> .ev k
> .par*env-init
> .ev
> .aln 0:MCLL pg@colw
> .aln 0:MCLT pg@colw
> .aln k:MCLL pg@colw
> .aln k:MCLT pg@colw
> .aln fn:PS FPS
> .aln fn:VS FVS
> .aln fn:LL FL
> .aln fn:LT FL
> .aln fn:PI FI
> .aln fn:PD FPD
> .aln fn:MCLL pg@fn-colw
> .aln fn:MCLT pg@fn-colw
> ..
> .de par@reset-env
> .nr \\n[.ev]:il 0
> .nr \\n[.ev]:li 0
> .nr \\n[.ev]:ri 0
> .nr \\n[.ev]:ai \\n[\\n[.ev]:PI]
> .nr \\n[.ev]:pli 0
> .nr \\n[.ev]:pri 0
> .nr \\n[.ev]:ds-type 0
> ..
> .\" par@reset
> .de par@reset
> .br
> .if \\n[s@devtag-needs-end-of-title] .DEVTAG-EO-TL
> .nr s@devtag-needs-end-of-title 0
> .if \\n[s@devtag-needs-end-of-heading] .DEVTAG-EO-H
> .nr s@devtag-needs-end-of-heading 0
> .ce 0
> .rj 0
> .ul 0
> .fi
> .ft 1
> .ie '\\n[.ev]'fn' .fam \\*[fn@font-family]
> .el .fam \\*[FAM]
> .ie (\\n[\\n[.ev]:PS] >= 1000) \
> .     ps (\\n[\\n[.ev]:PS]z / 1000u)
> .el \
> .     ps \\n[\\n[.ev]:PS]
> .ie (\\n[\\n[.ev]:VS] >= 1000) \
> .     par*vs "(\\n[\\n[.ev]:VS]p / 1000u)"
> .el \
> .     par*vs \\n[\\n[.ev]:VS]
> .ls 1
> .if !'\\$1'' .nr \\n[.ev]:pli (n;\\$1)
> .if !'\\$2'' .nr \\n[.ev]:pri (n;\\$2)
> .ie \\n[pg@ncols]>1 \{\
> .     ll (u;\\n[\\n[.ev]:MCLL]-\\n[\\n[.ev]:ri]-\\n[\\n[.ev]:pri])
> .     lt \\n[\\n[.ev]:MCLT]u
> .\}
> .el \{\
> .     ll (u;\\n[\\n[.ev]:LL]-\\n[\\n[.ev]:ri]-\\n[\\n[.ev]:pri])
> .     lt \\n[\\n[.ev]:LT]u
> .\}
> .in (u;\\n[\\n[.ev]:li]+\\n[\\n[.ev]:pli])
> .TA
> .hy \\n[HY]
> ..
> .\" This can be redefined by the user.
> .de TA
> .ta T 5n
> ..
> .\" \n[PORPHANS] sets number of initial lines of any paragraph,
> .\" which must be kept together, without any included page break.
> .\" Initialise to reproduce original behaviour; user may adjust it.
> .if !rPORPHANS .nr PORPHANS 1
> .
> .de par*start
> .ds@auto-end
> .par@reset \\$1 \\$2
> .sp \\n[\\n[.ev]:PD]u
> .ne \\n[PORPHANS]v+\\n(.Vu
> ..
> .de par@finish
> .nr \\n[.ev]:ai \\n[\\n[.ev]:PI]
> .nr \\n[.ev]:pli 0
> .nr \\n[.ev]:pri 0
> .par@reset 0 0
> ..
> .als @RT par@finish
> .\" normal LP
> .de @LP
> .par*start 0 0
> .nr \\n[.ev]:ai \\n[\\n[.ev]:PI]
> ..
> .de @PP
> .par*start 0 0
> .nr \\n[.ev]:ai \\n[\\n[.ev]:PI]
> .if !'\*(.T'html' .ti +\\n[\\n[.ev]:ai]u
> ..
> .de @QP
> .nr \\n[.ev]:ai \\n[\\n[.ev]:PI]
> .par*start \\n[QI]u \\n[QI]u
> ..
> .de @XP
> .par*start \\n[\\n[.ev]:PI]u 0
> .ti -\\n[\\n[.ev]:PI]u
> ..
> .de @IP
> .ie \\n[.$]>1 \{\
> .par*start \\$2 0
> .nr \\n[.ev]:ai \\n[\\n[.ev]:pli]
> .\}
> .el .par*start \\n[\\n[.ev]:ai]u 0
> .if !'\\$1'' \{\
> .     \" Divert the label so as to freeze any spaces.
> .     di par*label
> .     par*push-tag-env
> \&\\$1
> .     par*pop-tag-env
> .     di
> .     chop par*label
> .     ti -\\n[\\n[.ev]:ai]u
> .     ie \\n[dl]+1n<=\\n[\\n[.ev]:ai] \{\
> .             DEVTAG-COL 1
> \\*[par*label]\h'|\\n[\\n[.ev]:ai]u'\c
> .             DEVTAG-COL 2
> .       \}
> .     el \{\
> .             DEVTAG-COL 1
> \\*[par*label]
> .             DEVTAG-COL-NEXT 2
> .             br
> .     \}
> .     rm par*label
> .\}
> ..
> .\" We don't want margin characters to be attached when we divert
> .\" the tag.  Since there's no way to save and restore the current
> .\" margin character, we have to switch to a new environment, taking
> .\" what we need of the old environment with us.
> .de par*push-tag-env
> .nr par*saved-font \\n[.f]
> .nr par*saved-size \\n[.s]z
> .nr par*saved-ss \\n[.ss]
> .nr par*saved-sss \\n[.sss]
> .ds par*saved-fam \\n[.fam]
> .ev par
> .nf
> .TA
> .ft \\n[par*saved-font]
> .ps \\n[par*saved-size]u
> .ss \\n[par*saved-ss] \\n[par*saved-sss]
> .fam \\*[par*saved-fam]
> ..
> .de par*pop-tag-env
> .ev
> ..
> .de @RS
> .br
> .nr \\n[.ev]:li!\\n[\\n[.ev]:il] \\n[\\n[.ev]:li]
> .nr \\n[.ev]:ri!\\n[\\n[.ev]:il] \\n[\\n[.ev]:ri]
> .nr \\n[.ev]:ai!\\n[\\n[.ev]:il] \\n[\\n[.ev]:ai]
> .nr \\n[.ev]:pli!\\n[\\n[.ev]:il] \\n[\\n[.ev]:pli]
> .nr \\n[.ev]:pri!\\n[\\n[.ev]:il] \\n[\\n[.ev]:pri]
> .nr \\n[.ev]:il +1
> .nr \\n[.ev]:li +\\n[\\n[.ev]:ai]
> .nr \\n[.ev]:ai \\n[\\n[.ev]:PI]
> .par@reset
> ..
> .de @RE
> .br
> .ie \\n[\\n[.ev]:il] \{\
> .     nr \\n[.ev]:il -1
> .     nr \\n[.ev]:ai \\n[\\n[.ev]:ai!\\n[\\n[.ev]:il]]
> .     nr \\n[.ev]:li \\n[\\n[.ev]:li!\\n[\\n[.ev]:il]]
> .     nr \\n[.ev]:ri \\n[\\n[.ev]:ri!\\n[\\n[.ev]:il]]
> .     nr \\n[.ev]:pli \\n[\\n[.ev]:pli!\\n[\\n[.ev]:il]]
> .     nr \\n[.ev]:pri \\n[\\n[.ev]:pri!\\n[\\n[.ev]:il]]
> .\}
> .el .@error unbalanced .\\$0
> .ie '\\$0'QE' .par*start 0 0
> .el .par@reset
> ..
> .de @QS
> .br
> .nr \\n[.ev]:li!\\n[\\n[.ev]:il] \\n[\\n[.ev]:li]
> .nr \\n[.ev]:ri!\\n[\\n[.ev]:il] \\n[\\n[.ev]:ri]
> .nr \\n[.ev]:ai!\\n[\\n[.ev]:il] \\n[\\n[.ev]:ai]
> .nr \\n[.ev]:pli!\\n[\\n[.ev]:il] \\n[\\n[.ev]:pli]
> .nr \\n[.ev]:pri!\\n[\\n[.ev]:il] \\n[\\n[.ev]:pri]
> .nr \\n[.ev]:il +1
> .nr \\n[.ev]:li +\\n[QI]
> .nr \\n[.ev]:ri +\\n[QI]
> .nr \\n[.ev]:ai \\n[\\n[.ev]:PI]
> .par*start 0 0
> ..
> .als @QE @RE
> .\" start boxed text
> .de B1
> .br
> .HTML-IMAGE
> .di par*box-div
> .nr \\n[.ev]:li +1n
> .nr \\n[.ev]:ri +1n
> .nr par*box-in \\n[.in]
> .\" remember what 1n is, just in case the type size changes
> .nr par*box-n 1n
> .in +1n
> .ll -1n
> .lt -1n
> .ti \\n[par*box-in]u+1n
> ..
> .de @div-end!par*box-div
> .B2
> ..
> .\" end boxed text
> .\" Postpone the drawing of the box until we're in the top-level
> .\" diversion, in case there's a footnote inside the box.
> .de B2
> .ie '\\n(.z'par*box-div' \{\
> .     br
> .     \" In nroff mode, make room for the horizontal rules taking up
> .     \" 1v each.  (Why aren't we actually testing nroff mode? --GBR)
> .     if \n[.V]>.25m .sp
> .     di
> .     if \n[.V]>.25m .sp
> .     ds@need \\n[dn]
> .     par*box-mark-top
> .     ev nf
> .     par*box-div
> .     ev
> .     nr \\n[.ev]:ri -\\n[par*box-n]
> .     nr \\n[.ev]:li -\\n[par*box-n]
> .     in -\\n[par*box-n]u
> .     ll +\\n[par*box-n]u
> .     lt +\\n[par*box-n]u
> .     par*box-draw \\n[.i]u \\n[.l]u-(\\n[.H]u==1n*1n)
> .\}
> .el .@error .B2 without .B1
> .HTML-IMAGE-END
> ..
> .de par*box-mark-top
> .ie '\\n[.z]'' \{\
> .     rs
> .     mk par*box-top
> .\}
> .el \!.par*box-mark-top
> ..
> .de par*box-draw
> .ie '\\n[.z]'' \{\
> .     nr par*box-in \\n[.i]
> .     nr par*box-ll \\n[.l]
> .     nr par*box-vpt \\n[.vpt]
> .     nr par*box-ad \\n[.j]
> .     ad l
> .     vpt 0
> .     in \\$1
> .     ll \\$2
> \v'-1v+.25m'\
> \D'l (u;\\n[.l]-\\n[.i]) 0'\
> \D'l 0 |\\n[par*box-top]u'\
> \D'l -(u;\\n[.l]-\\n[.i]) 0'\
> \D'l 0 -|\\n[par*box-top]u'
> .     br
> .     sp -1
> .     in \\n[par*box-in]u
> .     ll \\n[par*box-ll]u
> .     vpt \\n[par*box-vpt]
> .     ad \\n[par*box-ad]
> .\}
> .el \!.par*box-draw \\$1 \\$2
> ..
> .\" \n[HORPHANS] sets how many lines of the following paragraph must be
> .\" kept together, with a preceding section heading.  Initialise it,
> .\" to reproduce original behaviour; user may change it.
> .if !rHORPHANS .nr HORPHANS 1
> .
> .\" \n[GROWPS] and \n[PSINCR] cause auto-increment of heading type size.
> .\" Initialise them, so they have no effect, unless explicitly set by
> .\" the user.
> .if !rGROWPS .nr GROWPS 0
> .if !rPSINCR .nr PSINCR 1p
> .
> .de SH-NO-TAG
> .par@finish
> .\" Keep the heading and the first few lines of the next paragraph
> .\" together.  (\n[HORPHANS] defines "first few" -- default = 1; user
> .\" may redefine it).
> .nr sh*minvs \\n[HORPHANS]v
> .if \\n[sh*psincr]<0 .nr sh*psincr 0
> .ie \\n(VS<1000 .par*vs \\n(VSp+\\n[sh*psincr]u
> .el .par*vs \\n(VSp/1000u+\\n[sh*psincr]u
> .ne 2v+\\n[sh*minvs]u+\\n[\\n[.ev]:PD]u+\\n(.Vu
> .\" Adjust type size for heading text, as specified by \n[GROWPS] and
> .\" \n[PSINCR].
> .ie \\n(PS<1000 .ps \\n(PS+\\n[sh*psincr]u
> .el .ps \\n(PSz/1000u+\\n[sh*psincr]u
> .sp 1
> .ft B
> ..
> .de @SH
> .\" AT&T ms implementation does not expect an argument, but groff ms
> .\" allows ".SH n" to make a heading's type size match ".NH n", for same
> .\" "n", when \n[GROWPS] and \n[PSINCR] are set.
> .  nr sh*psincr 0
> .  if 0\\$1>0 .nr sh*psincr (\\n[GROWPS]-0\\$1)*\\n[PSINCR]
> .  SH-NO-TAG
> .  DEVTAG-SH 1
> .  if '\*(.T'html' .nr s@devtag-needs-end-of-heading 1
> .if \\n[PDFFEAT]>0 \{\
> .  ds nh*bm  sh:bm\\n+[shct]
> .  if \\n[.$]>1 .ds nh*bm \\$2
> .  box @NHpdf
> .  dt .1v @NH-end
> .\}
> ..
> .\" TL, AU, and AI are aliased to these in cov*ab-init.
> .de par@TL
> .par@finish
> .sp 1
> .ft B
> .ps +2
> .vs +3p
> .ce \\n[.R]
> .DEVTAG-TL
> .nr s@devtag-needs-end-of-title 1
> ..
> .de par@AU
> .par@finish
> .sp 1
> .ft I
> .ce \\n[.R]
> ..
> .de par@AI
> .par@finish
> .sp .5
> .ce \\n[.R]
> ..
> .\" In paragraph macros.
> .de NL
> .if \En[.$] .@warning arguments to .NL ignored
> .ie (\\n[\\n[.ev]:PS] >= 1000) \
> .     ps (\\n[\\n[.ev]:PS]z / 1000u)
> .el \
> .     ps \\n[\\n[.ev]:PS]
> ..
> .de SM
> .if \En[.$] .@warning arguments to .SM ignored
> .ps -2
> ..
> .de LG
> .if \En[.$] .@warning arguments to .LG ignored
> .ps +2
> ..
> .\" par*define-font-macro macro font apply-italic-corrections
> .de par*define-font-macro
> .de \\$1
> .ds par*lic \" empty
> .ds par*ic \" empty
> .if \\n[.$]>2 \{\
> .     as par*lic \,\"
> .     as par*ic \/\"
> .\}
> .if \En[.$]>3 .@warning excess arguments to .\\$1 ignored
> .ie \En[.$] \{\
> .     nr par*prev-font \En[.f]
> \&\E$3\E*[par*lic]\f[\\$2]\E$1\f[\En[par*prev-font]]\E*[par*ic]\E$2
> .\}
> .el .ft \\$2
> \\..
> ..
> .par*define-font-macro R R
> .par*define-font-macro B B
> .par*define-font-macro I I yes
> .par*define-font-macro BI BI yes
> .ie n .par*define-font-macro CW R
> .el   .par*define-font-macro CW CR
> .\" underline a word
> .de UL
> .if \En[.$]>2 .@warning excess arguments to .UL ignored
> .     ie t .do nop \Z'\\$1'\v'.25m'\D'l \w'\\$1'u 0'\v'-.25m'\\$2
> .     el \(ul\\$1\(ul\\$2
> ..
> .\" box a word
> .de BX
> .if \En[.$]>1 .@warning excess arguments to .BX ignored
> .nr par*bxw \w'\\$1'
> .ie t \{\
> .nr par*bxw +.4m
> \Z'\v'.25m'\
> \D'l 0 -1m'\D'l \\n[par*bxw]u 0'\D'l 0 1m'\D'l -\\n[par*bxw]u 0''\
> \Z'\h'.2m'\\$1'\h'\\n[par*bxw]u'
> .\}
> .el \m[black]\M[white]\Z'\\$1'\h'\\n[par*bxw]u'\m[]\M[]
> ..
> .\" superscript
> .ds par@sup-start \v'-.9m\s'\En[.s]*7u/10u'+.7m'
> .als { par@sup-start
> .ds par@sup-end \v'-.7m\s0+.9m'
> .als } par@sup-end
> .\" subscript
> .ds par@sub-start \v'+.3m\s'\En[.s]*7u/10u'-.1m'
> .als < par@sub-start
> .ds par@sub-end \v'+.1m\s0-.3m'
> .als > par@sub-end
> .\" footnote paragraphs
> .fn@init
> .\" FR is the ratio of footnote (horizontal) length to the line length
> .ds FR 11/12
> .\" FF is the footnote format
> .nr FF 0
> .\" This can be redefined. It gets a second argument of 'no' if the
> .\" first argument was supplied by the user, rather than automatically.
> .de FP
> .br
> .if !d par*fp!\\n[FF] \{\
> .     @error unknown footnote format '\\n[FF]'
> .     nr FF 0
> .\}
> .ie '\\$2'no' .par*fp!\\n[FF]-no "\\$1"
> .el .par*fp!\\n[FF] "\\$1"
> ..
> .de par*fp!0
> .@PP
> \&\\*[fn@mark-start]\\$1\\*[fn@mark-end]\ \c
> ..
> .de par*fp!0-no
> .@PP
> \&\\$1\ \c
> ..
> .de par*fp!1
> .@PP
> \&\\$1.\ \c
> ..
> .de par*fp!1-no
> .@PP
> \&\\$1\ \c
> ..
> .de par*fp!2
> .@LP
> \&\\$1.\ \c
> ..
> .de par*fp!2-no
> .@LP
> \&\\$1\ \c
> ..
> .de par*fp!3
> .@IP "\\$1." (u;\\n[\\n[.ev]:PI]*2)
> ..
> .de par*fp!3-no
> .@IP "\\$1" (u;\\n[\\n[.ev]:PI]*2)
> ..
> .\" ***************************
> .\" ******** module nh ********
> .\" ***************************
> .\" Numbered headings.
> .\" nh*hl is the level of the last heading
> .nr nh*hl 0
> .\" SN-DOT and SN-NO-DOT represent the section number of
> .\" the current heading, with and without a terminating dot.
> .ds SN-DOT
> .ds SN-NO-DOT
> .\" SN-STYLE sets the statement numbering style used in headings
> .\" (either SN-DOT or SN-NO-DOT); for backward compatibility with
> .\" earlier ms versions, the default is SN-DOT
> .als SN-STYLE SN-DOT
> .\" Also for backward compatibility, let SN represent SN-DOT.
> .als SN SN-DOT
> .\" numbered heading
> .de @NH
> .ie '\\$1'S' \{\
> .     shift
> .     nr nh*hl 0
> .     while \\n[.$] \{\
> .             nr nh*hl +1
> .             nr H\\n[nh*hl] 0\\$1
> .             shift
> .     \}
> .     if !\\n[nh*hl] \{\
> .             nr H1 1
> .             nr nh*hl 1
> .             @error missing arguments to .NH S
> .     \}
> .\}
> .el \{\
> .     nr nh*ohl \\n[nh*hl]
> .     ie \\n[.$] \{\
> .             nr nh*hl 0\\$1
> .             ie \\n[nh*hl]<=0 \{\
> .                     nr nh*ohl 0
> .                     nr nh*hl 1
> .             \}
> .             el \{\
> .                     if \\n[nh*hl]-\\n[nh*ohl]>1 \{\
> .                             ds nh*msg .NH \\n[nh*ohl] followed by\"
> .                             as nh*msg " .NH \\n[nh*hl]\"
> .                             @warning \\*[nh*msg]
> .                             rm nh*msg
> .                     \}
> .             \}
> .     \}
> .     el .nr nh*hl 1
> .     while \\n[nh*hl]>\\n[nh*ohl] \{\
> .             nr nh*ohl +1
> .             nr H\\n[nh*ohl] 0
> .     \}
> .     nr H\\n[nh*hl] +1
> .\}
> .ds SN-NO-DOT \\n(H1
> .nr nh*i 1
> .while \\n[nh*i]<\\n[nh*hl] \{\
> .     nr nh*i +1
> .     as SN-NO-DOT .\\n[H\\n[nh*i]]
> .\}
> .ds SN-DOT \\*[SN-NO-DOT].
> .nr sh*psincr (\\n[GROWPS]-\\n[nh*hl])*\\n[PSINCR]
> .SH-NO-TAG
> .DEVTAG-NH "\\$1"
> .if '\*(.T'html' .nr s@devtag-needs-end-of-heading 1
> \\*[SN-STYLE]
> .if \\n[PDFFEAT]>0 \{\
> .  ds nh*bm  nh:bm\\n+[nhct]
> .  if \\n[.$]>1 .ds nh*bm \\$2
> .  box @NHpdf
> .  dt .1v @NH-end
> .\}
> ..
> .de @NH-end
> .  fl
> .  box
> .  chop @NHpdf
> \&\\*[@NHpdf]
> .  asciify @NHpdf
> .  pdfbookmark -T \\*[nh*bm] \\n[nh*hl] "\\*[@NHpdf]"
> .  rm @NHpdf
> ..
> .\" ****************************
> .\" ******** module toc ********
> .\" ****************************
> .\" Table of contents generation.
> .de @XS
> .da toc*div
> .ev h
> .ie \\n[.$] .XA "\\$1"
> .el .XA
> ..
> .de @div-end!toc*div
> .XE
> ..
> .de XA
> .ie '\\n(.z'toc*div' \{\
> .     if d toc*num .toc*end-entry
> .     ie \\n[.$] \{\
> .             ie '\\$1'no' .ds toc*num \" empty
> .             el .ds toc*num "\\$1
> .     \}
> .     el .ds toc*num \\n[%]\"
> .     br
> .     par@reset
> .     na
> .     ll -8n \" XXX: take TC-MARGIN into account?
> .     in (n;0\\$2)
> .       if \\n[PDFFEAT]>0 \{\
> .           pdfhref L -S -D \\*[nh*bm]
> .           sp -1v
> .       \}
> .\}
> .el .@error .XA without .XS
> ..
> .de XE
> .ie '\\n(.z'toc*div' \{\
> .       if \\n[PDFFEAT]>0 .nop \X'pdf: markend'\m[default]\c
> .     if d toc*num .toc*end-entry
> .     ev
> .     di
> .\}
> .el .@error .XE without .XS
> ..
> .\" Rudimentary integration of TOC generation with SH and NH;
> .\" (called by XH and XN respectively, to capture heading text
> .\" for reuse as TOC entry); may be redefined, to achieve more
> .\" sophisticated TOC layout effects.
> .\"
> .\" No-op initializers are called by XH and XN respectively,
> .\" before XH-UPDATE-TOC is called; if XH-UPDATE-TOC has been
> .\" redefined, then it may also be necessary to redefine either,
> .\" or both of these, to perform any initialization specific
> .\" to use after SH and NH respectively.
> .de XH-INIT de
> .de XN-INIT
> ..
> .de XH-UPDATE-TOC
> .\" .XH-UPDATE-TOC <outline-level> <text>
> .XS
> .in (\\$1u - 1u * 2n)
> .shift
> \&\\$*
> .XE
> ..
> .\" Rudimentary integration hook, to be called (nominally)
> .\" after SH, but acceptable in any body-text context
> .de XH de
> .\" .XH <outline-level> <text>
> .rn XH-REPLACEMENT XH
> .XH \\$@
> .de XH-REPLACEMENT
> .XH-INIT
> .XH-UPDATE-TOC \\$@
> .shift
> \&\\$*
> ..
> .\" Rudimentary integration hook, to be called after NH
> .de XN de
> .\" .XN <text>
> .ie \\n[nh*hl] .toc*xn-init \\$@
> .el \{\
> .     @error .XN is not allowed before .NH
> .     nop \&\\$*
> .\}
> .de toc*xn-init de
> .rn XN-REPLACEMENT XN
> .XN \\$@
> .rm \\$0
> .de XN-REPLACEMENT
> .XN-INIT
> .XH-UPDATE-TOC \\n[nh*hl] \\$@
> \&\\$*
> .if \\n[PDFFEAT]>0 .sp
> ..
> .de toc*end-entry
> .if !'\\*[toc*num]'' \\a\\t\\*[toc*num]
> .br
> .rm toc*num
> ..
> .de PX
> .1C
> .if !'\\$1'no' \{\
> .     ce 1
> .     ie (\\n[PS] >= 1000) \
> .             ps ((\\n[PS]z / 1000u) + 2z)
> .     el \
> .             ps \\n[PS]+2
> .     ft B
> \\*[TOC]
> .     ft
> .     ps
> .\}
> .nf
> .if !r TC-MARGIN .nr TC-MARGIN \w'000'
> .if !c \[TC-LEADER] .char \[TC-LEADER] .\h'1m'
> .ta (u;\\n[.l]-\\n[.i]-\\n[TC-MARGIN]) (u;\\n[.l]-\\n[.i])R
> .lc \[TC-LEADER]
> .sp 2
> .toc*div
> .par@reset
> ..
> .\" print the table of contents on page i
> .de TC
> .  if \\n[PDFFEAT]=1 .if \\n[gottitle*pdf] .pdfswitchtopage after title
> .P1
> .pg@begin 1 i
> .PX \\$1
> .  if \\n[PDFFEAT]=1 .pdfpagenumbering D . -0
> ..
> .\" ****************************
> .\" ******** module eqn ********
> .\" ****************************
> .\" Eqn support.
> .de EQ
> ..
> .de EN
> ..
> .de @EQ
> .if \\n[tbl@within-table] \
> .     @error .EQ is not allowed within a .TS/.TE table
> .br
> .nr eqn*type 0
> .ds eqn*num "\\$2
> .if '\\$1'L' .nr eqn*type 1
> .if '\\$1'I' .nr eqn*type 2
> .if '\\$1'C' .nr eqn*type 3
> .if !\\n[eqn*type] \{\
> .     ds eqn*msg .EQ: unrecognized alignment '\\$1';
> .     ie (\\n[.$] = 1) \{\
> .             if !'\\$1'' \{\
> .                     as eqn*msg " assuming it is an equation label
> .                     @warning \\*[eqn*msg]
> .                     ds eqn*num "\\$1
> .             \}
> .     \}
> .     el .if (\\n[.$] > 1) \{\
> .             if !'\\$1'' \{\
> .                     as eqn*msg " centering equation
> .                     @warning \\*[eqn*msg]
> .             \}
> .     \}
> .     rm eqn*msg
> .\}
> .di eqn*div
> .in 0
> .if '\*(.T'html' \{\
> .     nr eqn*ll \\n[.l]
> .     ll 1000n
> .\}
> .if \\n[eqn*type]=1 .EQN-HTML-IMAGE-LEFT
> .if \\n[eqn*type]=2 \{\
> .   if '\*(.T'html' .RS
> .EQN-HTML-IMAGE-INLINE
> .\}
> .if \\n[eqn*type]=3 .EQN-HTML-IMAGE
> .nf
> ..
> .de @div-end!eqn*div
> .@EN
> ..
> .\" Note that geqn mark and lineup work correctly in centered equations.
> .de @EN
> .ie !'\\n(.z'eqn*div' .@error-recover mismatched .EN
> .el \{\
> .     br
> .     di
> .     nr eqn*have-num 0
> .     if !'\\*[eqn*num]'' .nr eqn*have-num 1
> .     ie \\n[dl]:\\n[eqn*have-num] \{\
> .             sp \\n[DD]u
> .             ns
> .             par@reset
> .             ds eqn*tabs \\n[.tabs]
> .             nf
> .             ie \\n[dl] \{\
> .\"                   XXX: This really should not be necessary and
> .\"                   indicates that there is extra space creeping
> .\"                   into an equation when ps4html is enabled.
> .                     ie r ps4html .ds@need \\n[dn]u-1v+\n[.V]u+1i
> .                     el .ds@need \\n[dn]u-1v+\n[.V]u
> .                     chop eqn*div
> .                     ie \\n[eqn*type]=1 \{\
> .                             ta (u;\\n[.l]-\\n[.i])R
> \\*[eqn*div]\t\\*[eqn*num]
> .                     \}
> .                     el \{\
> .                             ie \\n[eqn*type]=2 .ta \\n[DI]u \
> (u;\\n[.l]-\\n[.i])R
> .                             el .ta (u;\\n[.l]-\\n[.i]/2)C \
> (u;\\n[.l]-\\n[.i])R
> \t\\*[eqn*div]\t\\*[eqn*num]
> .                     \}
> .             \}
> .             el \{\
> .                     ta (u;\\n[.l]-\\n[.i])R
> \t\\*[eqn*num]
> .             \}
> .\".          if !'\*(.T'html' .sp \\n[DD]u
> .             sp \\n[DD]u
> .             ns
> .             ta \\*[eqn*tabs]
> .     \}
> .     el \{\
> .\" must terminate empty equations in html and ps4html as they contain
> .\" the EQN-HTML-IMAGE-END suppression nodes
> .             if \\n[dl] .chop eqn*div
> .             if '\*(.T'html' \\*[eqn*div]
> .             if r ps4html    \\*[eqn*div]
> .     \}
> .       if !'\*(.T'html' .fi
> .     if \\n[eqn*type]=1 .EQN-HTML-IMAGE-END
> .     if \\n[eqn*type]=2 \{\
> .             EQN-HTML-IMAGE-END
> .             if '\*(.T'html' .RE
> .     \}
> .     if \\n[eqn*type]=3 .EQN-HTML-IMAGE-END
> .     if '\*(.T'html' \
> .             ll \\n[eqn*ll]u
> .\}
> ..
> .
> .\" ****************************
> .\" ******** module tbl ********
> .\" ****************************
> .\" Tbl support.
> .nr tbl@within-table 0
> .nr tbl*has-heading 0
> .nr tbl*was-tbl-failure-reported 0
> .de tbl*check-for-tbl
> .if !r TW .if !\\n[tbl*was-tbl-failure-reported] \{\
> .     ds tbl*err tbl preprocessor failed, or it or soelim was not
> .     as tbl*err " run; table(s) likely not rendered\"
> .     as tbl*err " (TE macro called with TW register undefined)\"
> .     @error \\*[tbl*err]
> .     rm tbl*err
> .     nr tbl*was-tbl-failure-reported 1
> .\}
> ..
> .\" This gets called if TS occurs before the first paragraph.
> .de TS
> .cov*ab-init
> .\" cov*ab-init, called by LP, aliases TS to @TS.
> \\*[TS]\\
> ..
> .de @TS
> .nr tbl@within-table 1
> .sp \\n[DD]u
> .if '\\$1'H' \{\
> .     ds tbl*stem .TS H table inside\"
> .     ie '\\n[.z]'kp@div' .@warning \\*[tbl*stem] .KS/.KE keep
> .     el .if '\\n[.z]'kp@fdiv' \
> .             @warning \\*[tbl*stem] .KF/.KE floating keep
> .     rm tbl*stem
> .     di tbl*heading-diversion
> .\}
> .if '\*(.T'html' \{\
> .     nr tbl*ll \\n[.l]
> .     ll 1000n
> .\}
> .HTML-IMAGE
> ..
> .de tbl@top-hook
> .if \\n[tbl*has-heading] \{\
> .     ie \\n[.t]-\\n[tbl*heading-height]-1v .tbl*print-heading
> .     el .sp \\n[.t]u
> .\}
> ..
> .de tbl*print-heading
> .ev nf
> .tbl*heading-diversion
> .ev
> .mk #T
> ..
> .de TH
> .ie '\\n[.z]'tbl*heading-diversion' \{\
> .     nr T. 0
> .     T#
> .     br
> .     di
> .     \" A table with repeating headings requires enough room for them
> .     \" and then at least one more vee for a row of data.
> .     ie \\n[dn]+1v>=(\\n[.p]-\\n[FM]-\\n[HM]) \{\
> .             ds tbl*err .TH repeating table heading(s) do not fit in
> .             as tbl*err " page area; formatting only once
> .             @error \\*[tbl*err]
> .             rm tbl*err
> .             ds@need \\n[dn]
> .             tbl*print-heading
> .     \}
> .     el \{\
> .             nr tbl*heading-height \\n[dn]
> .             ds@need \\n[dn]u+1v
> .             tbl*print-heading
> .             nr tbl*has-heading 1
> .     \}
> .\}
> .el .@error-recover .TH without .TS H
> ..
> .de @div-end!tbl*heading-diversion
> .TH
> .TE
> ..
> .de TE
> .tbl*check-for-tbl
> .if !r TW .return
> .ie '\\n(.z'tbl*heading-diversion' \
> .     @error-recover .TS H but no .TH before .TE
> .el \{\
> .     nr tbl*has-heading 0
> .     if !'\*(.T'html' \{\
> .             sp \\n[DD]u
> .             ns
> .     \}
> .\}
> .HTML-IMAGE-END
> .if '\*(.T'html' \
> .     ll \\n[tbl*ll]u
> .\" reset tabs
> .TA
> .nr tbl@within-table 0
> ..
> .de tbl@bottom-hook
> .if \\n[tbl*has-heading] \{\
> .     nr T. 1
> .     T#
> .\}
> ..
> .de T&
> ..
> .\" ****************************
> .\" ******** module pic ********
> .\" ****************************
> .\" Pic support.
> .\" This gets called if PS occurs before the first paragraph.
> .de PS
> .LP
> .\" cov*ab-init, called by LP, aliases PS to @PS.
> \\*[PS]\\
> ..
> .\" @PS height width
> .de @PS
> .br
> .sp \\n[DD]u
> .ie \\n[.$]<2 \{\
> .     ds pic*msg .PS: expected 2 arguments, got \\n[.$]\"
> .     as pic*msg ; not preprocessed with pic?\"
> .     @error \\*[pic*msg]
> .     rm pic*msg
> .\}
> .el \{\
> .     ds@need (u;\\$1)+1v
> .     in +(u;\\n[.l]-\\n[.i]-\\$2/2>?0)
> .\}
> .HTML-IMAGE
> ..
> .de PF
> .HTML-IMAGE-END
> .par@reset
> ..
> .de PE
> .PF
> .sp \\n[DD]u+.5m
> .ns
> ..
> .\" ****************************
> .\" ******** module ref ********
> .\" ****************************
> .\" Refer support.
> .mso refer-ms.tmac
> .\" ****************************
> .\" ******** module acc ********
> .\" ****************************
> .\" Accents and special characters.
> .ds Q \(lq
> .ds U \(rq
> .ds - \(em
> .\" Characters
> .\" Accents
> .de acc*over-def
> .ds \\$1 \Z'\v'(u;\w'x'*0+\En[rst]-\En[.cht])'\
> \h'(u;-\En[skw]+(-\En[.w]-\w'\\$2'/2)+\En[.csk])'\\$2'
> ..
> .de acc*under-def
> .ds \\$1 \Z'\v'\En[.cdp]u'\h'(u;-\En[.w]-\w'\\$2'/2)'\\$2'
> ..
> .de acc*slash-def
> .ds \\$1 \Z'\h'(u;-\En[.w]-\w'\\$2'/2)'\
> \v'(u;\En[.cdp]-\En[.cht]+\En[rst]+\En[rsb]/2)'\\$2'
> ..
> .de acc*prefix-def
> .ds \\$1 \Z'\h'(u;\w'x'-\w'\\$2'/2)'\\$2'
> ..
> .acc*prefix-def ' \'
> .acc*prefix-def ` \`
> .acc*prefix-def ^ ^
> .acc*prefix-def , \(ac
> .acc*prefix-def : \(ad
> .acc*prefix-def ~ ~
> .\" improved accent marks
> .de AM
> .acc*over-def ' \'
> .acc*over-def ` \`
> .acc*over-def ^ ^
> .acc*over-def ~ ~
> .acc*over-def : \(ad
> .acc*over-def v \(ah
> .acc*over-def _ \(a-
> .acc*over-def o \(ao
> .acc*under-def , \(ac
> .acc*under-def . \s[\En[.s]*8u/10u]\v'.2m'.\v'-.2m'\s0
> .acc*under-def hook \(ho
> .acc*slash-def / /
> .char \[hooko] o\E*[hook]
> .ds q \[hooko]
> .\" The idea of this definition is for the top of the 3 to be at the
> .\" x-height.
> .if !c\[yogh] .char \[yogh] \Z'\v'\w'x'*0-\En[rst]u'\s[\En[.s]*8u/10u]\
> \v'\w'3'*0+\En[rst]u'3\s0'\h'\w'\s[\En[.s]*8u/10u]3'u'
> .ds 3 \[yogh]
> .ds D- \(-D\"                 Icelandic uppercase eth
> .ds d- \(Sd\"                 Icelandic lowercase eth
> .ds Th \(TP\"                 Icelandic uppercase thorn
> .ds th \(Tp\"                 Icelandic lowercase thorn
> .ds 8 \(ss\"                  German double s
> .ds Ae \(AE\"                 AE ligature
> .ds ae \(ae\"                 ae ligature
> .ds Oe \(OE\"                 OE ligature
> .ds oe \(oe\"                 oe ligature
> .ds ? \(r?\"                  upside down ?
> .ds ! \(r!\"                  upside down !
> ..
> .de CHECK-FOOTER-AND-KEEP
> .if '\*(.T'html' \{\
> .   rm KF
> .   als KF KS
> .
> .   rm FS
> .   de FS
> .      sp
> .      HTML-NS <cite>
> \\..
> .   rm FE
> .   de FE
> .      HTML-NS </cite>
> .      sp
> \\..
> .\}
> ..
> .par@load-init
> .\" Local Variables:
> .\" mode: nroff
> .\" fill-column: 72
> .\" End:
> .\" vim: set filetype=groff textwidth=72:


> .\" Run: pdfmom --roff -ms -msboxes -dpapersz=a4 -tU -P-pa4  
> Groff-PDF-Features.ms > Groff-PDF-Features.pdf
> .nr PS 11
> .nr VS 13
> .nr PO 2c
> .nr LL 17c
> .nr PL 29.7c
> .nr HM 1.9c
> .nr FM .3c
> .nr QI 5n
> .nr DI \n[QI]
> .nr PDFOUTLINE.FOLDLEVEL 1
> .ds FR 1
> .nr TC-MARGIN \w'00' \" expect 2-digit page numbers at most
> .ie t .nr PI 3.5n
> .el   .nr PI 4n
> .ds date \*[MONTH\n[mo]] \n[year]\"
> .ND \*[date]
> .EH '%''\*[date]'
> .EF ''''
> .OH '\f[I]Groff\f[] PDF features''%'
> .OF ''''
> .de Cd
> .  ft CB
> ..
> .de CdX
> .  ft
> ..
> .de Qq
> .  nop \[lq]\\$1\[rq]\\$2
> ..
> .\" Define a macro for code literals; use bold and disable hyphenation.
> .de Lt
> .  ft CB
> .  nh
> .  nop \m[grey33]\s'-1p'\\$1\s'+1p'\m[]\c
> .  hy \\n[HY]
> .  ft
> .  nop \&\\$2
> ..
> .de PN
> .  ev Pn
> .  evc 0
> .  po .5c
> .  pdfnote \\$@
> .  fl
> .  po
> .  ev 0
> ..
> .ds = \f(CB\\$1\f(CR\\$4\f[CBI]\\$2\f(CR\\$3
> .RP no
> .TL
> .BI Groff
> PDF features
> .AU
> Deri James
> .AI
> [email protected]
> .AB no
> .PDFPIC gnu.eps
> .AE
> .NH 1 intro
> .XN Introduction
> .LP
> This is an attempt to draw together information gleaned from
> .Lt pdf.tmac ,
> .Lt gropdf(1) ,
> and the various full service macros which manage pdf features.
> .LP
> Previously groff contained
> .Lt pdfmark.tmac
> and
> .Lt spdf.tmac
> (to integrate pdf features into the
> .Lt ms
> macros). Since these have now been dropped from groff and are now supported by
> Keith Marshall at
> .pdfhref W -D https://savannah.nongnu.org/projects/groff-pdfmark -A . this 
> site
> .LP
> PDF features can either be controlled at a low level,
> using the macros defined in
> .Lt pdf.tmac
> or rely on these
> macros being integrated into a full-service macro
> package. Currently this is the state of play with regard
> to full-service:-
> .LP
> .TS
> allbox tab(^);
> Cb Cb Cb Cb Cb Cb
> Cf(CB) C C C C C.
> Macro^Meta Data^Bookmarks^Internal Link^External Link^Named Destination
> mom^\[OK]^\[OK]^\[OK]^\[OK]^\[OK]
> man^^\[OK]^\[OK]\m[red]\**\m[]^\[OK]^^
> ms^\[OK]^\[OK]^\[OK]^\[OK]^\[OK]
> me^^^^^
> mm^^^^^
> .TE
> .LP
> .FS
> In a man page collection the MR macro can be used to link to other entries
> in the collection, otherwise it is an external link.
> .FE
> .LP
> The macros outlined below cannot be mix'n'matched between full service
> macro sets, but the information given for
> .Lt pdf.tmac
> can be used in any document, particularly if a full macro set is missing
> a particular facility.
> .LP
> .NH 1 meta
> .XN Meta Data
> .NH 2 info
> .XN Document Info
> .LP
> A PDF document can contain meta data such as
> .Qq "Created Date" ,
> .Qq Author ,
> .Qq Title ,
> etc..
> .NH 3
> .XN pdf.tmac
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED cornsilk3 INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> To enter pdf meta data use:
> .QS
> .Lt ".pdfinfo /<label>"
> "\fItext\fP"
> .QE
> Where
> .Lt /<label>
> can be:-
> .LP
> .QS
> .Lt /Title ,
> .Lt /Author ,
> .Lt /Subject ,
> .Lt /Keywords
> .QE
> And
> .BI text
> can be multi-line if you use:-
> .QS
> .Lt \[rs]*[PDFLB]
> where you want the line-break.
> .QE
> These entries are stored in the pdf and can be seen if you run the
> command:-
> .QS
> .Lt pdfinfo
> \fIfilename\fP
> .QE
> Or are probably visible if you look at
> .Qq "Document Properties"
> in your favourite pdf viewer.
> .LP
> .BOXSTOP
> .NH 3
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> In
> .Lt mom
> you can use:-
> .QS
> .Lt .DOCTITLE
> .BI text
> .LP
> or more usually
> .LP
> .Lt .PDF_TITLE
> .BI text
> .QE
> The text given to:-
> .QS
> .Lt .AUTHOR
> .BI text
> .QE
> Will be added to the pdf meta data.
> .BOXSTOP
> .LP
> .NH 3
> .XN ms
> .BOXSTART SHADED honeydew OUTLINED green INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt ms
> .BOXSTOP
> .LP
> .Lt .TL
> and
> .Lt .AU
> Provide the Title and Author for the pdf.
> .BOXSTOP
> .LP
> .NH 2 notes
> .XN Annotation Notes
> .LP
> .PN -T "Deri James" Just an example
> These are the clickable/hoverable icons which appear in the document which
> can contain pop-up comments on the text. Only the
> .Lt pdf.tmac
> macros provide access to this facility.
> .LP
> .NH 3
> .XN pdf.tmac
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> .Lt .pdfnote
> [ -T
> .BI title
> ]
> .BI "text"
> .LP
> The popup note in the left margin was created with:-
> .QS
> .Lt \&.pdfnote \"
> -T "Deri James" Just an example
> .QE
> The colour of the icon is set by setting the register
> .Lt PDFNOTE.COLOUR
> (or
> .Lt PDFNOTE.COLOR )
> to three numbers each in the range 0.0 \[rA] 1.0 representing red, green,
> blue values, so:-
> .QS
> \&.ds
> .Lt PDFNOTE.COLOUR
> 1.0 1.0 0.0
> .QE
> Produces a yellow icon. The opacity of the icon is controlled by:-
> .QS
> \&.ds
> .Lt PDFNOTE.OPACITY
> 0.0 \[rA] 1.0
> .QE
> Where the default value is 0.6.
> .LP
> Again you can use
> .Lt \[rs]*[PDFLB]
> in the
> .BI text .
> .BOXSTOP
> .LP
> .NH 1 bookmarks
> .XN Bookmarks
> .LP
> For PDFs, bookmarks are the entries in the outline panel. Each bookmark
> has a hierarchical level, and, optionally, can be
> .Qq named
> so that they can be linked to from elsewhere in the document
> .XR naming ). (
> .LP
> Bookmarks in the panel can be open or closed, setting:-
> .QS
> \&.ds
> .Lt PDFOUTLINE.FOLDLEVEL
> 1
> .QE
> will
> .Qq close
> all bookmarks below the first level. The default is to open
> all bookmarks.
> .NH 2
> .XN pdf.tmac
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> .Lt pdf.tmac
> defines this macro for bookmarks:-
> .QS
> .Lt .pdfbookmark
> [\c
> .Lt -T
> .BI name ]
> .BI level
> .BI "descriptive text ..."
> .QE
> Where
> .Qq level
> is the mandatory nesting level for this
> .Qq bookmark .
> All the
> .Qq "descriptive text"
> form the entry in the outline panel. If the
> .Lt -T
> flag is given this becomes a
> .Qq "named destination"
> which can be linked to from elsewhere in the document
> .ds here naming
> .XR naming ). (
> .ds here after
> .BOXSTOP
> .ds here ne
> .ne 5
> .ds here
> .NH 2
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> mom uses the
> .Lt .HEADING
> macro to populate the outline panel:-
> .QS
> .Lt .HEADING
> .BI level
> [\c
> .Lt NAMED
> .BI name ]
> .BI "descriptive text ..."
> .QE
> .Qq level ,
> .Qq name
> and
> .Qq "descriptive text"
> have the same meanings as above.
> .BOXSTOP
> .LP
> .NH 2
> .XN ms
> .BOXSTART SHADED honeydew OUTLINED green INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt ms
> .BOXSTOP
> .LP
> There are two separate ways of providing outline bookmarks in
> .Lt ms .
> First there is the traditional heading:-
> .QS
> .Lt .NH
> .BI level
> [\c
> .BI name \m[red]\**\m[]]
> .QE
> .FS
> If the
> .Qq level
> is
> .Qq S
> (a Berkeley extension) you cannot have a
> .Qq named
> bookmark.
> .FE
> This is then followed by input which is used as
> .Qq "descriptive text" .
> .LP
> Although there could be several lines of input text, it is the
> accumulated output line which is used as
> .Qq "descriptive text" .
> .LP
> .pdfhref M -N hdexample ms Heading Example
> So this input:
> .ID
> .ft CB
> \&.NH 1 using
> Using
> \&.BI groff
> with the
> \&.BI ms
> Macro Package
> \&.XS
> Using
> \&.BI groff
> with the
> \&.BI ms
> Macro Package
> \&.XE
> .ft
> .DE
> Would create a named (\c
> .Qq using )
> level 1 bookmark
> .Qq "Using \fIgroff\fP with the \fIms\fP Macro Package" ,
> and also create a
> .Lt TOC
> entry.
> Note if the input text results in multiple output lines it is
> only the first output line which is used as the bookmark text.
> .LP
> .pdfhref M -N XN Using .XN
> The second method, which combined the
> .Qq heading
> with populating a
> .Lt TOC ,
> is the
> .Lt .XN
> command which
> .B must
> immediately follow the
> .Lt .NH
> command:-
> .QS
> .Lt .XN
> .BI "descriptive text"
> .QE
> Previously the
> .Lt .NH
> and following heading text had to be followed with
> .Lt .XS/.XE
> commands with the same text to separately populate the
> .Lt TOC .
> .LP
> So the equivalent code to the above using
> .Lt .XN
> is:-
> .ID
> .ft CB
> \&.NH 1 using
> \&.XN Using \[rs]fIgroff\[rs]fP with the \[rs]fIms\[rs]fP Macro Package
> .ft
> .DE
> In both cases the bookmark is named
> .Qq using ,
> and clicking on the overview panel
> or the hotspot link in the
> .Lt TOC
> will jump to the correct part of the document.
> You can also use the
> .XR XR "" "" ".XR command"
> to jump to the section named
> .Qq using :-
> .QS
> \&.XR using ). (
> .QE
> to enter this hotlink
> .Qq "(see: Using \fIgroff\fP with the \fIms\fP Macro Package)" .
> .BOXSTOP
> .bp
> .NH 2
> .XN man
> .LP
> .BOXSTART SHADED linen OUTLINED maroon INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt man
> .BOXSTOP
> .LP
> The
> .QS
> .Lt .TH
> .BI identifier
> .BI section
> .QE
> macro creates (via first call to
> .Lt .PT )
> a named heading with name id of
> .Qq identifier(section) .
> It is normally given a level 1 bookmark, which can be controlled by setting
> .Qq an*bookmark-base-level
> on the command line to increase the level,
> this can be used in man page collections where the pages
> are split into separate sections.
> .LP
> The
> .Lt .SH
> and
> .Lt .SS
> macros create unnamed bookmarks at the appropriate level.
> .BOXSTOP
> .LP
> .NH 1 naming
> .XN Named Destination
> .LP
> In order to insert hotspot links which jump to other parts of the
> document, the destination must be
> .Qq named .
> In the section on
> .XR bookmarks "" "" bookmarks
> it showed how they can be
> .Qq named .
> .LP
> If you want to jump to a place in the document which has not got a bookmark,
> you can name any particular place in the document, for example, a table or a
> figure to which you may want to refer. See description of
> .XR pdfhrefM . "" ".pdfhref M"
> .NH 2
> .XN pdf.tmac
> .LP
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> .pdfhref M -N pdfhrefM
> This macro will plant a named destination without inserting a bookmark.
> .QS
> .Lt ".pdfhref M"
> [\c
> .Lt -N
> .BI name ]
> [\c
> .Lt -E ]
> .BI "descriptive text" ] [
> .QE
> If no
> .BI name
> is specified, the first word of
> .Qq "descriptive text"
> is used as the
> .Qq name ,
> which means that if the
> .Lt -N
> flag is not used then there must be at least one word in the text.
> .LP
> The
> .Qq "descriptive text" ,
> if any, is saved as the
> .Qq value
> of the destination, and can be used when specifying a hotspot link
> .XR hotspot ). (
> .LP
> If the
> .Lt -E
> flag is used, the text is output to the document as well.
> .BOXSTOP
> .LP
> .NH 2
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> The mom macro:-
> .QS
> .Lt .PDF_TARGET
> .BI name
> [\c
> .BI "descriptive text" ]
> .QE
> You can now use
> .BI name
> as the destination of any hotspot link
> .XR hotspot ). (
> .BOXSTOP
> .LP
> .bp
> .NH 1 hotspot
> .XN Internal Hotspot Links
> .LP
> An internal link is to a
> .Qq "named destination"
> within the document,
> .XR external "" "" external
> are links using a URI.
> .LP
> A hotspot link is a clickable piece of text which will cause the viewer
> to jump to a
> .XR naming . "" *
> The colour of the hotspot is controlled by setting the register
> .Lt PDFHREF.COLOUR
> (or
> .Lt PDFHREF.COLOR )
> to three numbers each in the range 0.0 \[rA] 1.0 representing red, green,
> blue values, so:-
> .QS
> \&.ds
> .Lt PDFHREF.COLOUR
> 0.00 0.35 0.60
> .QE
> Uses a turquoise text, which can be referenced as
> .QS
> .Lt \[rs]m[\[rs]*[PDFHREF.TEXT.COLOUR]] .
> .QE
> .ne 5
> Hotspots can have a border drawn around them, which is controlled by setting
> the register
> .Lt PDFHREF.BORDER
> to an array of 3 numbers which represent \[en] horizontal corner radius,
> vertical corner radius, and border width. A zero radius gives square (not
> rounded) corners. The default is:-
> .QS
> \&.ds
> .Lt PDFHREF.BORDER
> 0 0 0
> .QE
> Which produces no border around the hotspot.
> .NH 2
> .XN pdf.tmac
> .LP
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> To plant a hotspot link:-
> .QS
> .Lt ".pdfhref L " [\c
> .Lt -D
> .BI name ]
> [\c
> .Lt -P
> .BI prefix-text ]
> [\c
> .Lt -A
> .BI affixed-text ]
> [\c
> .Lt -- ]
> [\c
> .BI "hotlink text" ]
> .QE
> The target for the link is given by
> .BI name
> which should be a destination you have named
> .XR naming ). (
> .LP
> .pdfhref M -N prefix -- Prefix and Affix
> The
> .BI prefix-text
> and
> .BI affixed-text
> are placed around the
> .BI "hotlink text" .
> .LP
> If you used a
> .BI "descriptive text"
> when naming a destination, you can retrieve the value with this code:-
> .ID
> .ft CB
> \&.pdf:lookup \c
> .BI name
> \&.ie !'\[rs]*[pdf:lookup-result]'' \\
> \&. ds desc-txt \&\[rs]*[pdf:lookup-value]
> \&.el \\
> \&. ds desc-txt Unknown
> .ft
> .DE
> .LP
> .pdfhref M -N expandochars expando char
> This technique is used by some of the full service macros to provide the
> .Qq expando
> characters
> .Qq *
> and
> .Qq + \&.
> When they are the final character in
> .Qq "hotlink text"
> the expando is replaced by
> .BI desc-text ,
> surrounded by double quotes if the expando character is
> .Qq + .
> .BOXSTOP
> .LP
> .NH 2
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> mom uses this macro:-
> .QS
> .Lt .PDF_LINK
> .BI name
> [\c
> .Lt PREFIX
> .BI text ]
> [\c
> .Lt SUFFIX
> .BI text ]
> .BI "hotlink text"
> .QE
> If the
> .BI "hotlink text"
> is terminated with an
> .XR expandochars "" "" *
> it is replaced by the value of the
> .BI "descriptive text"
> when the target
> .BI name
> was created.
> .BOXSTOP
> .LP
> .NH 2 hotspotms
> .XN ms
> .BOXSTART SHADED honeydew OUTLINED green INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt ms
> .BOXSTOP
> .LP
> .pdfhref M XR
> An addition to the ms macro set is the
> .Lt .XR
> command, originally found in some versions of Keith Marshall's
> .Lt spdf.tmac ,
> our version now uses
> .XR expandochars "" "" expandos
> .QS
> .Lt .XR
> .BI name
> [\c
> .BI post
> [\c
> .BI pre
> [\c
> .BI "hotlink text" "] ] ]"
> .QE
> If
> .BI "hotlink text"
> is missing, the contents of
> .Lt \[rs]*[spdf:txt_default]
> is used instead. This defaults to
> .Qq "see:\ +" " "
> so, with the use of the terminating expando, the code
> .QS
> .Lt .XR
> intro ), (
> .QE
> would add a link
> .XR intro ), (
> if the first section of this document was:-
> .ID
> \&.NH 1 intro
> \&.XN Introduction
> .DE
> .BOXSTOP
> .LP
> .NH 2
> .XN man
> .LP
> .BOXSTART SHADED linen OUTLINED maroon INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt man
> .BOXSTOP
> .LP
> The
> .QS
> .Lt .MR
> .BI name
> .BI section
> .QE
> macro normally creates an external link
> .XR external ), (
> suitable for linking to
> another man page, but if the other man page it is calling is part
> of the same man page collection, it creates an internal link within
> the document.
> .BOXSTOP
> .LP
> .NH 1 external
> .XN External Hotspot Links
> .LP
> External links are often links to resources on the internet such as website
> URLs.
> .NH 2
> .XN pdf.tmac
> .LP
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> The macro to link to internet resources is:-
> .QS
> .Lt ".pdfhref W " [\c
> .Lt -D
> .BI URI ]
> [\c
> .Lt -P
> .BI prefix-text ]
> [\c
> .Lt -A
> .BI affixed-text ]
> [\c
> .Lt -- ]
> [\c
> .BI "hotlink text" ]
> .QE
> If a
> .BI URI
> is not specified the
> .BI "hotlink text"
> must be a valid
> .BI URI
> itself, and the complete
> .BI URI
> will be visible in the document.
> If a
> .BI URI
> is given by specifying
> .Lt -D
> .BI URI
> then only the
> .BI "hotlink text"
> will be visible, but clicking it will launch the
> .BI URI .
> .LP
> The
> .Lt -P
> and
> .Lt -A
> flags operate in the same way as in
> .Lt ".pdfhref L "
> .XR prefix ). (
> .BOXSTOP
> .LP
> .NH 2
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> External links are supported by:-
> .QS
> .Lt .PDF_WWW_LINK
> .BI URI
> [\c
> .Lt PREFIX
> .BI text ]
> [\c
> .Lt SUFFIX
> .BI text ]
> .BI "hotlink text"
> .QE
> If no
> .BI "hotlink text"
> is given, the
> .BI URI
> is used as the
> .BI "hotlink text" .
> .XR expandochars "" "" Expandos
> can be used as the last character of
> .BI "hotlink text"
> and will insert the
> .BI URI .
> .BOXSTOP
> .ne 8v
> .LP
> .NH 2
> .XN man
> .LP
> .BOXSTART SHADED linen OUTLINED maroon INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt man
> .BOXSTOP
> .LP
> The macro
> .QS
> .Lt .MR
> .BI name
> .BI section
> .QE
> Generates an appropriate external
> .BI URI
> such as:-
> .QS
> man:/groff(1)
> .QE
> except if the link is to another man page in the same collection.
> .BOXSTOP
> .LP
> .ne 5
> .NH 1 toc
> .XN Table of Contents
> .LP
> Some full service macro sets offer macros for building a Table of Contents,
> with PDF features these TOCs have clickable entries.
> .LP
> .NH 2
> .XN pdf.tmac
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> .Lt pdf.tmac
> has no specific macros to build Table of Contents, but it does have two macros
> which can insert a TOC into the appropriate position in the document:-
> .QS
> .Lt .pdfpagename
> .BI name
> .QE
> This assigns a name to the current page being rendered which can then be used 
> in
> the second macro:-
> .QS
> .Lt .pdfswitchtopage
> [\c
> .BI when ]
> .BI name
> .QE
> Where
> .BI name
> is a previously named page, and the optional
> .BI when
> can be
> .Qq before
> or
> .Qq after ,
> default is
> .Qq before .
> Alternatively the positions
> .Qq top
> and
> .Qq bottom
> can be used instead of a named page.
> This macro should be used before a new page is started.
> .BOXSTOP
> .LP
> .NH 2
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> The macro
> .QS
> .Lt .AUTO_RELOCATE_TOC
> .QE
> (must come before
> .Lt .START )
> will position the TOC appropriately in the document.
> .LP
> Pease see the
> .pdfhref W -D http://schaffter.ca/mom/momdoc/tables-of-contents.html#top -- 
> mom documentation
> for details of TOC generation, by default the TOC entries are clickable.
> .BOXSTOP
> .LP
> .NH 2
> .XN ms
> .BOXSTART SHADED honeydew OUTLINED green INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt ms
> .BOXSTOP
> .LP
> The traditional way of populating a TOC was to use the
> .Lt .XS/.XA/.XE
> macros.
> .LP
> Groff 1.23.0 introduced the
> .Lt .XN
> macro which combined producing headings
> .XR hdexample ) (
> with adding a TOC entry.
> .LP
> In either case the entry will be clickable.
> .LP
> If the document has an
> .Lt .RP
> entry, so a title page is produced, the TOC will be repositioned after the
> Title Page.
> .BOXSTOP
> .LP
> .ne 7v
> .NH 1 slide
> .XN Slideshow Presentations
> .LP
> The groff pdf driver understands how to create pdfs to be used for slideshow
> presentations. An alternative is to use the postscript driver and gpresent
> which is downloadable
> .pdfhref W -D 
> https://ftp.openbsd.org/pub/OpenBSD/distfiles/gpresent-2.5.tar.gz here
> because the original
> .pdfhref W -D https://www.bob.diertens.org/corner/useful/gpresent/ website
> is having difficulty downloading files larger than 32kb.
> .LP
> groff documents written to work with
> .Lt present.tmac
> .I may
> work with -Tpdf with minor alteration (and no need to run
> .Lt presentps
> and
> .Lt ps2pdf ).
> .LP
> .NH 2
> .XN pdf.tmac
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> There are just two macros which control the slide presentation:-
> .QS
> .Lt .pdftransition
> .I feature
> .I mode
> .I duration
> .I dimension
> .I motion
> .I direction
> .I scale
> .I bool
> .QE
> These parameters are explained in the
> .pdfhref W -D man:/gropdf(1)#transition gropdf(1)
> man page, many of them are not required!
> .LP
> There are two
> .Qq events
> which trigger a transition: when a slide is first drawn
> (\c
> .I feature
> =
> .Lt SLIDE )
> and when a new element is added to the slide
> .I feature "" (
> =
> .Lt BLOCK ).
> Both can have a different transition
> .I mode ,
> such as
> .Lt "\[dq]Split | Blinds | Box | Wipe | Dissolve | Glitter | R\[dq]"
> all of which are explained (and some more!) in the man page.
> .LP
> Whenever you want the presentation to pause (because you have added a
> new element to the slide) use the macro:-
> .QS
> .Lt .pdfpause
> .QE
> and the new element will appear using the current transition set for
> .Lt BLOCK .
> .BOXSTOP
> .LP
> .NH 2
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> mom has full integration with pdf presentation mode, the documentation
> is
> .pdfhref W -D http://schaffter.ca/mom/momdoc/docprocessing.html#slides -A . 
> here
> .LP
> In addition the mom example documents include
> .Lt slide-demo.mom
> and its resultant pdf, so should be included in the examples/mom directory
> in the groff documentation.
> .BOXSTOP
> .LP
> .NH 2
> .XN ms
> .BOXSTART SHADED honeydew OUTLINED green INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt ms
> .BOXSTOP
> .LP
> An example of using
> .Lt ms
> to create a slideshow is
> .pdfhref W -D http://chuzzlewit.co.uk/ms-slides.tgz -A . here
> .BOXSTOP
> .bp
> .NH 1 boxes
> .XN Boxes and PDF paper colour
> .LP
> PDF viewers usually show contents on a white background, the actual
> background in the PDF is really transparent (so it appears white),
> however it is possible to specify a colour for the page background.
> .LP
> In addition it is possible to specify framed coloured boxes on the page
> to contain the running contents. These boxes will flow onto following
> pages.
> .LP
> .NH 2
> .XN pdf.tmac
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt pdf.tmac
> .BOXSTOP
> .LP
> The relevent macros are:-
> .QS
> .Lt .pdfbackground
> .I cmd
> .I left
> .I top
> .I right
> .I bottom
> .I weight
> .br
> .Lt ".pdfbackground off"
> .br
> .Lt ".pdfbackground footnote"
> .I bottom
> .QE
> which produce a background rectangle on the page. The meanings of the
> parameters are explained in the
> .pdfhref W -D man:/gropdf(1)#boxes gropdf(1)
> man page.
> .BOXSTOP
> .LP
> .NH 2
> .XN ms
> .BOXSTART SHADED honeydew OUTLINED green INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt ms
> .BOXSTOP
> .LP
> If you include the macro file
> .Lt -msboxes
> in the
> .I groff
> command, you will have access to these macros:-
> .QS
> .Lt .BOXSTART
> .Lt SHADED
> .I colour
> .Lt OUTLINED
> .I colour
> .Lt INDENT
> .I size
> .Lt WEIGHT
> .I size
> .QE
> begins a box, where the argument after
> .Lt SHADED
> gives the fill colour and that after
> .Lt OUTLINED
> the border colour. Omit the former to get a borderless filled box and the
> latter for a border with no fill. The specified
> .Lt WEIGHT
> (line thickness) is used if the box is
> .Lt OUTLINED .
> .LP
> .Lt INDENT
> precedes a value that leaves a gap between the border and the contents
> inside the box.
> .LP
> Each colour must be a defined groff colour name, and each size a valid
> .I groff
> numeric expression. The keyword/value pairs can be specified in any order.
> .LP
> Boxes can be stacked, so you can start a box within another box; usually
> the later boxes would be smaller than the containing box, but this is
> not enforced. When using
> .Lt BOXSTART ,
> the left position is the current indent minus the
> .Lt INDENT
> in the command, and the right position is the left position
> (calculated above) plus the current line length and twice the indent.
> .QS
> .Lt BOXSTOP
> .QE
> takes no parameters. It closes the most recently started box at the
> current vertical position after adding its
> .Lt INDENT
> spacing.
> .BOXSTOP
> .LP
> .NH 2
> .XN mom
> .BOXSTART SHADED lavenderblush OUTLINED mediumblue INDENT 2n WEIGHT 1p
> .BOXSTART SHADED lightgray INDENT 2p
> .ce 1
> .Lt mom
> .BOXSTOP
> .LP
> mom has comprehensive control of boxes see the
> .pdfhref W -D http://schaffter.ca/mom/momdoc/images.html#box-intro \
>    -A . mom documentation
> .BOXSTOP
> .bp
> .NH 1 developer
> .XN For macro developers
> .LP
> If you want to add PDF features to your own macro packages here is some
> information which may be helpful.
> .BOXSTART SHADED cornsilk OUTLINED brown INDENT 2n WEIGHT 1p
> .sp -1v
> .NH 2 restart
> .XN Stop/Start hotlink
> .QS
> .Lt .pdfmarksuspend/.pdfmarkresume
> .QE
> If there is a piece of
> .Qq "hotlink text"
> which appears right at the bottom of the page and continues across to the
> start of the next page these macros can stop/restart the hotlink.
> Typically they appear at the start of a page footer macro (to prevent the
> footer text becoming part of the hotlink), and at the end of a page
> header macro (to restart the hotlinking from the previous page).
> .LP
> .NH 2 pdfroff
> .XN Forward References
> .LP
> Since groff is a single pass interpreter when it finds an
> .XR hotspot "" "" *
> it cannot plant a link if it has not
> .Qq seen
> the
> .XR naming "" "" *
> to which it links, and the link will appear as
> \m[\*[PDFHREF.TEXT.COLOUR]]Unknown\m[]
> resisting all mouse clicks - the same happens if the
> .I destination
> of the link is mis-typed and does not exist. The forward link problem can be
> solved: instead of:-
> .QS
> .Lt "groff -Tpdf ..."
> .QE
> use:-
> .QS
> .Lt "pdfmom --roff ..."
> .QE
> The command was originally developed for satisfying forward references in
> .I mom
> files (hence the name), but with the
> .I --roff
> flag it drops its reliance on
> .I mom
> and you can place any macro package on the command line.
> .NH 2 S
> .XN Marking Hotlinks
> .LP
> The
> .QS
> .Lt .pdfhref
> .I command
> .QE
> general purpose macro, supports these commands:-
> .QS
> .IP \*[= O]
> (Outline) This is the command which
> .XR bookmarks "" "" \fB.pdfbookmark\fP
> calls.
> .IP \*[= M]
> (Mark) Setup a
> .XR naming . "" +
> .IP \*[= L]
> (Local) Mark an
> .XR hotspot . "" +
> .IP \*[= W]
> (WWW URI) Form an
> .XR external . "" +
> .QE
> .LP
> These
> .Qq low-level
> calls all expect
> .Qq "descriptive text"
> or
> .Qq "hotlink text"
> to be passed as part of the macro call. Sometimes this is not convenient,
> consider the man macro pair
> .Lt .MT/.ME
> where
> .ID
> .ft CB
> \&.MT jh@\[rs]:axis\[rs]:.se
> J\[rs][o ad]rgen H\[rs][a ad]gg
> \&.ME
> .ft
> .DE
> The mail-to
> .I URI
> is on the
> .Lt .MT
> line, but the text to use for a
> .I hotlink
> is everything up to the following
> .Lt .ME .
> .LP
> The
> .Lt .pdfhref
> macro allows an
> .Lt -S
> flag, not documented above, which just turns on
> .I hotlinking
> text. It is then up to the macro author to arrange for it to be turned off
> at the appropriate point. In this example it should be
> .Qq "turned off"
> in the
> .Lt .ME
> macro.
> .LP
> The code to turn off the
> .Qq hotlinking
> and restore the text colour to what it was before the
> .Qq hotlinking
> started is:-
> .ID
> .ft CB
> \[rs]X'pdf: markend'\[rs]m[\[rs]*[pdf:curcol]]
> .ft
> .DE
> .BOXSTOP
> .TC


-- 
---
Larry McVoy           Retired to fishing          http://www.mcvoy.com/lm/boat

  • ... Deri via discussion of the GNU roff typesetting system and related software
    • ... Larry McVoy
    • ... Morten Bo Johansen
      • ... Deri via discussion of the GNU roff typesetting system and related software

Reply via email to