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:

Attachment: Groff-PDF-Features.pdf
Description: Adobe PDF document

.\" 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
  • ... 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