.\" -*- nroff -*-
.ig

pdfmark.tmac

Copyright (C) 2004
  Free Software Foundation, Inc.
     Written by Keith Marshall (keith.d.marshall@ntlworld.com)

This file is part of groff.

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 2, 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 groff; see the file COPYING.  If not, write to the Free Software
Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA.

Author's Note
=============

While I have written this macro package from scratch, much of my
inspiration has come from discussion on the groff mailing list
(mailto:groff@gnu.org).  I am particularly indebted to:

   Kees Zeelenberg, for an earlier macro package he posted,
   a study of which helped me to get started.

   Carlos J. G. Duarte and Werner Lemberg, whose discussion
   on computation of the bounding boxes for link "hot-spots"
   forms the basis of such computations in this package.
..
.if !\n(.g .ab These pdfmark macros require groff.
.\"
.\" Check if we have already been loaded -- do not reload
.if d pdfmark .nx
.\"
.\" ======================================================================
.\" Module PDFMARK: Insert Arbitrary PDFMARK Code in the PostScript Stream
.\" ======================================================================
.\"
.\" PDFMARK output may be disabled, by zeroing the PDFOPMODE register,
.\" ( which mimics a more generic OPMODE, if it is defined ).
.\"
.if rOPMODE .aln PDFOPMODE OPMODE
.\"
.\" but if OPMODE wasn't defined,
.\" then make the default PDFMARK mode ENABLED.
.\"
.if !rPDFOPMODE .nr PDFOPMODE 1
.\"
.\" The "pdfmark" macro is responsible for emitting the appropriate
.\" PostScript code.
.\"
.de pdfmark
.\" ----------------------------------------------------------------
.\" Usage:
.\"   .pdfmark  text of pdfmark instruction
.\" Macro supplies the required opening "[" and closing "pdfmark"
.\" operator; DO NOT include them in the instruction text!
.\" ----------------------------------------------------------------
.\"
.if \\n[PDFOPMODE] \X'ps:exec [\\$* pdfmark'\c
..
.\"
.\" Some supporting macros defer actual pdfmark output until an
.\" appropriate time for it to be written; the "pdfsync" macro
.\" provides a mechanism for flushing such deferred output;
.\" it should be called from an end macro, and at any other time
.\" when it may be deemed necessary to flush pdfmark context.
.\"
.de pdfsync
.\" ----------------------------------------------------------------
.\" Usage:
.\"   .pdfsync buffer ...
.\" Arguments indicate which "buffer(s)" to flush:
.\"   O -> bookmark (outline) cache
.\"   M -> document metadata diversion
.\" If no argument, flush ALL buffers
.\" ----------------------------------------------------------------
.\"
.ie \\n(.$ \{\
.   while \\n(.$ \{\
.      if '\\$1'O' .pdf:bm.sync 1
.      if '\\$1'M' \{\
.         if dpdf:metadata .pdf:metadata
.         rm pdf:metadata
.         \}
.      shift
.      \}
.   \}
.el .pdfsync O M
..
.\"
.\" some helper functions ...
.\"
.\" "pdf:warn" and "pdf:error" write diagnostic messages to stderr
.\"
.de pdf:warn
.\" ----------------------------------------------------------
.\" Usage:
.\"   .pdf:warn text of message
.\" ----------------------------------------------------------
.\"
.tm \\n(.F:\\n(.c: macro warning: \\$*
..
.de pdf:error
.\" ----------------------------------------------------------
.\" Usage:
.\"   .pdf:error text of message
.\" ----------------------------------------------------------
.\"
.tm \\n(.F:\\n(.c: macro error: \\$*
..
.\" "pdf:pop", assisted by "pdf*pop", allows us to retrieve register,
.\" or string values, from a string masquerading as a data queue,
.\" or as a stack.
.\"
.de pdf:pop
.\" ----------------------------------------------------------------
.\" Usage:
.\"   .pdf:pop <type> <to-name> <from-name>
.\"   $1 = nr for numeric register, ds for string
.\"   $2 = name of register or string to be assigned
.\"   $3 = name of string, from which data is to be retrieved
.\" ----------------------------------------------------------------
.\"
.pdf*pop \\$* \\*[\\$3]
..
.de pdf*pop
.ds pdf:stack \\$3
.\\$1 \\$2 \\$4
.shift 4
.ie \\n(.$ .ds \\*[pdf:stack] \\$*
.el .rm \\*[pdf:stack]
.rm pdf:stack
..
.\"
.\"
.\" ===========================================================
.\" Module PDFINFO: Insert MetaData Entries into a PDF Document
.\" ===========================================================
.\"
.\" N.B.
.\"   Output from the macros in this module is deferred, until
.\"   subsequent invocation of .pdfsync, or .pdfexit
.\"
.\" ."pdfinfo" provides a general purpose form of metadata entry ...
.\" it allows arbitrary text to be associated with any specified
.\" metadata field name.
.\"
.de pdfinfo
.\" -------------------------------------------------------------------
.\" Usage:
.\"   .pdfinfo /FieldName field content ...
.\" Examples:
.\"   .pdfinfo /Title   A PDF Document
.\"   .pdfinfo /Author  Keith Marshall
.\" -------------------------------------------------------------------
.\"
.ds pdf:meta.field \\$1
.shift
.da pdf:metadata
\!.pdfmark \\*[pdf:meta.field] (\\$*) /DOCINFO
.di
.rm pdf:meta.field
..
.\"
.\" Macro "pdfview" defines a special form of metadata entry ...
.\" it uses the /DOCVIEW pdfmark, to specify the initial (default) view,
.\" when the document is opened.
.\"
.de pdfview
.\" -------------------------------------------------------------------
.\" Usage:
.\"   .pdfview view parameters ...
.\" Examples:
.\"   .pdfview /PageMode /UseOutlines
.\"   .pdfview /Page 2 /View [/FitH \n(.p u]
.\" -------------------------------------------------------------------
.\"
.da pdf:metadata
\!.pdfmark \\$* /DOCVIEW
.di
..
.\"
.\"
.\" =====================================================================
.\" Module PDFNOTE: Insert "Sticky Note" Style Comments in a PDF Document
.\" =====================================================================
.\"
.\" "PDFNOTE.WIDTH" and "PDFNOTE.HEIGHT" set the preferred size for
.\" display of the "sticky note" pane, when opened.  Acrobat Reader
.\" seems not to honour these -- perhaps GhostScript doesn't encode
.\" them correctly!  Anyway, let's set some suitable default values,
.\" in case the user has a set up which does work as advertised.
.\"
.nr PDFNOTE.WIDTH  3.5i
.nr PDFNOTE.HEIGHT 2.0i
.\"
.\" "pdf:bbox" defines the expression used to set the size and location
.\" of the bounding rectangle for display of notes and link "hot-spots".
.\" This is defined, such that a note is placed at troff's current text
.\" position on the current page, with its displayed image size defined
.\" by the "PDFNOTE.WIDTH" and "PDFNOTE.HEIGHT" registers, while the
.\" bounds for a link "hot-spot" are matched to the text region which
.\" defines the "hot-spot".
.\"
.ds pdf:bbox \\n[pdf:llx] u \\n[pdf:lly] u \\n[pdf:urx] u \\n[pdf:ury] u
.\"
.\" Getting line breaks into the text of a PDFNOTE is tricky -- we need
.\" to get a "\n" into the PostScript stream, but three levels of "\" are
.\" swallowed, when we invoke "pdfnote".  The following definition of "PDFLB",
.\" (for LineBreak), is rather ugly, but does allow us to use
.\"
.\"    .pdfnote  Some text.\*[PDFLB]Some more text, on a new line.
.\"
.ds PDFLB \\\\\\\\\\\\\\\\n
.\"
.de pdfnote
.\" ----------------------------------------------------------------------
.\" Usage:
.\"   .pdfnote [-T "Text for Title"] Text of note ...
.\" ----------------------------------------------------------------------
.\"
.if \\n[PDFOPMODE] \{\
.\"
.\" First, compute the bounding rectangle,
.\" for this PDFNOTE instance
.\"
.   mk pdf:ury
.   nr pdf:llx \\n(.k+\\n(.o+\\n[.in]
.   nr pdf:lly \\n[pdf:ury]-\\n[PDFNOTE.HEIGHT]
.   nr pdf:urx \\n[pdf:llx]+\\n[PDFNOTE.WIDTH]
.   ds pdf:note.instance /Rect [\\*[pdf:bbox]]
.\"
.\" Parse any specified (recognisable) PDFNOTE options
.\"
.   while dpdf:note\\$1 \{\
.      pdf:note\\$1 \\$@
.      shift \\n[pdf:note.argc]
.      \}
.\"
.\" Emit the note, and clean up
.\"
.   pdfmark \\*[pdf:note.instance] /Contents (\\$*) /ANN
.   rm pdf:note.instance
.   rr pdf:note.argc
.   \}
..
.de pdf:note-T
.nr pdf:note.argc 2
.as pdf:note.instance " /Title (\\$2)
..
.\"
.\"
.\" =====================================================================
.\" Module PDFBOOKMARK: Add an Outline Reference in the PDF Bookmark Pane
.\" =====================================================================
.\"
.\" "PDFBOOKMARK.VIEW" controls how the document will be displayed,
.\" when the user selects a bookmark.  This default setting will fit
.\" the page width to the viewing window, with the bookmarked entry
.\" located at the top of the viewable area.
.\"
.ds PDFBOOKMARK.VIEW /FitH \\n[PDFPAGE.Y] u
.\"
.\" "PDFOUTLINE.FOLDLEVEL" controls how the document outline will be
.\" displayed.  It is a number, defining the maximum heading level
.\" which will be visible, without outline expansion by the user, in
.\" the initial view of the document outline.  Assuming that no sane
.\" document will ever extend to 10,000 levels of nested headings,
.\" this initial default value causes outlines to be fully expanded.
.\"
.nr PDFOUTLINE.FOLDLEVEL 10000
.\"
.\" The actual job of creating an outline reference
.\" is performed by the "pdfbookmark" macro.
.\"
.de pdfbookmark
.\" ------------------------------------------------------------------
.\" Usage:
.\"   .pdfbookmark [-T tag] level "Text of Outline Entry"
.\"
.\"   $1 = nesting level for bookmark (1 is top level)
.\"   $2 = text for bookmark, (in PDF viewer bookmarks list)
.\"   $3 = suffix for PDF internal bookmark name (optional)
.\" ------------------------------------------------------------------
.\"
.if \\n[PDFOPMODE] \{\
.\"
.\" Make the bookmark name "untagged" by default,
.\" then parse any specified options, to set a "tag", if required
.\"
.   ds pdf:href-T
.   while dpdf:href.opt\\$1 \{\
.      pdf:href.opt\\$1 \\$@
.      shift \\n[pdf:href.argc]
.      \}
.   rr pdf:href.argc
.\"
.\" If we found "--" to mark the end of the options, discard it
.\"
.   if '\\$1'--' .shift
.\"
.\" Synchronise the bookmark cache
.\" to the requested bookmark nesting level
.\"
.   pdf:bm.sync \\$1
.   shift
.\"
.\" Increment the bookmark serialisation index
.\" in order to generate a uniquely serialised bookmark name,
.\" ( which we return in the string "PDFBOOKMARK.NAME" ),
.\" and insert this bookmark into the cache
.\"
.   pdf:href.sety
.   nr pdf:bm.nr +1
.   ds PDFBOOKMARK.NAME pdf:bm\\n[pdf:bm.nr]\\*[pdf:href-T]
.   ds pdf:bm\\n[pdf:bm.nr] /Dest /\\*[PDFBOOKMARK.NAME]
.   pdfmark \\*[pdf:bm\\n[pdf:bm.nr]] /View [\\*[PDFBOOKMARK.VIEW]] /DEST
.   as pdf:bm\\n[pdf:bm.nr] " /Title (\\$*)
.   pdf:href.options.clear
.   rr PDFPAGE.Y
.   \}
..
.\"
.\" Macro "pdf:bm.sync" is called for each bookmark created,
.\" to establish a cache entry at the appropriate nesting level.
.\" It will flush ALL previous cache content, when called to
.\" add a new bookmark at level 1, or if simply called at
.\" level 1, without adding any bookmark.
.\"
.de pdf:bm.sync
.\" ------------------------------------------------------------------
.\" Usage:
.\"   .pdf:bm.sync  level
.\"   $1 = nesting level of current bookmark, or 1 to flush cache
.\" ------------------------------------------------------------------
.\"
.\" First validate the bookmark nesting level
.\" adjusting it if required
.\"
.if \\$1>\\n[pdf:bm.nl] .nr pdf:bm.nl +1
.ie \\$1>\\n[pdf:bm.nl] \{\
.   pdf:warn adjusted level \\$1 bookmark; should be <= \\n[pdf:bm.nl]
.   \}
.el .nr pdf:bm.nl \\$1
.if \\n[pdf:bm.nl]<1 \{\
.   pdf:warn bad arg (\\$1) in \\$0 \\$1; \\$0 1 forced
.   nr pdf:bm.nl 1
.   \}
.\"
.\" If reverting from a higher to a lower nesting level,
.\" cyclicly adjust cache counts for each pending higher level
.\"
.if \\n[pdf:bm.lc]>=\\n[pdf:bm.nl] \{\
.   nr pdf:bm.lc +1
.   if !rpdf:bm.c\\n[pdf:bm.lc].c .nr pdf:bm.c\\n[pdf:bm.lc].c 0
.   while \\n[pdf:bm.lc]>\\n[pdf:bm.nl] \{\
.      as pdf:bm.c\\n[pdf:bm.lc] " \\n[pdf:bm.c\\n[pdf:bm.lc].c]
.      rr pdf:bm.c\\n[pdf:bm.lc].c
.      nr pdf:bm.lc -1
.      \}
.   \}
.\"
.\" Update the cache level,
.\" flushing when we are at level 1
.\"
.nr pdf:bm.lc \\n[pdf:bm.nl]
.ie \\n[pdf:bm.nl]=1 \{\
.   while \\n[pdf:bm.ic]<\\n[pdf:bm.nr] .pdf:bm.emit 0
.   rr pdf:bm.rc
.   \}
.el .nr pdf:bm.c\\n[pdf:bm.nl].c +1
..
.\" Macro "pdf:bm.emit" is called, when the cache is at level 1.
.\" This flushes ALL pending bookmarks from the cache, i.e. the
.\" preceding level 1 bookmark, and any nested dependents,
.\" which it may have.
.\"
.de pdf:bm.emit
.\" ------------------------------------------------------------------
.\" Usage:
.\"   .pdf:bm.emit  flag
.\"   $1 = reference counting flag, used to control recursion
.\" ------------------------------------------------------------------
.\"
.\" First check for nested dependents,
.\" and append the "dependent count" to the bookmark, as required.
.\"
.nr pdf:bm.ic +1
.nr pdf:bm.lc +1
.pdf:pop nr pdf:bm.rc pdf:bm.c\\n[pdf:bm.lc]
.if \\n[pdf:bm.rc] \{\
.   ds pdf:bm.fold
.   if \\n[pdf:bm.lc]>\\n[PDFOUTLINE.FOLDLEVEL] .ds pdf:bm.fold -
.   as pdf:bm\\n[pdf:bm.ic] " /Count \\*[pdf:bm.fold]\\n[pdf:bm.rc]
.   rm pdf:bm.fold
.   \}
.pdfmark \\*[pdf:bm\\n[pdf:bm.ic]] /OUT
.rm pdf:bm\\n[pdf:bm.ic]
.\"
.\" For ALL dependents, if any,
.\" recursively flush out any higher level dependents,
.\" which they themselves may have
.\"
.while \\n[pdf:bm.rc] \{\
.   nr pdf:bm.rc -1
.   pdf:bm.emit \\n[pdf:bm.rc]
.   \}
.\"
.\" Finally,
.\" unwind the recursive call stack, until we return to the top level.
.\"
.nr pdf:bm.rc \\$1
.nr pdf:bm.lc -1
..
.nr pdf:bm.nr 0
.nr pdf:bm.nl 1
.nr pdf:bm.lc 0
.nr pdf:bm.ic 0
.\"
.\"
.\" =============================================================
.\" Module PDFHREF: Create Hypertext References in a PDF Document
.\" =============================================================
.\"
.\" "PDFHREF.VIEW" controls how the document will be displayed,
.\" when the user follows a link to a named reference.
.\"
.ds PDFHREF.VIEW     /FitH \\n[PDFPAGE.Y] u
.\"
.\" This default setting will fit the page width to the viewing
.\" window, with the bookmarked entry located close to the top
.\" of the viewable area.  "PDFHREF.VIEW.LEADING" controls the
.\" actual distance below the top of the viewing window, where
.\" the reference will be positioned; 5 points is a reasonable
.\" default offset.
.\"
.nr PDFHREF.VIEW.LEADING  5.0p
.\"
.\" Yuk!!!
.\" PDF view co-ordinates are mapped from the bottom left corner,
.\" of the page, whereas page printing co-ordinates are mapped
.\" conventionally, from top left.
.\"
.\" Macro "pdf:href.sety" transforms the vertical position of the
.\" last printed baseline, from the printing co-ordinate domain to
.\" the PDF view domain.
.\"
.de pdf:href.sety
.\" ----------------------------------------------------------------
.\" Usage:
.\"   .pdf:href.sety
.\" ----------------------------------------------------------------
.\"
.\" This computation yields the vertical view co-ordinate
.\" in groff's basic units; don't forget to append grops' "u"
.\" conversion operator, when writing the pdfmark!
.\"
.nr PDFPAGE.Y \\n(.p-\\n(nl+\\n[PDFHREF.VIEW.LEADING]
..
.\" When we create a link "hot-spot" ...
.\" "PDFHREF.LEADING" sets the distance above the top of the glyph
.\" bounding boxes, in each line of link text, over which the link
.\" hot-spot will extend, while "PDFHREF.HEIGHT" sets the hot-spot
.\" height, PER LINE of text occupied by the reference.
.\"
.\" Since most fonts specify some leading space within the bounding
.\" boxes of their glyphs, a better appearance may be achieved when
.\" NEGATIVE leading is specified for link hot-spots;  indeed, when
.\" the default 10pt Times font is used, -1.0 point seems to be a
.\" reasonable default value for "PDFHREF.LEADING" -- it may be
.\" changed, if desired.
.\"
.\" "PDFHREF.HEIGHT" is initially set as one vertical spacing unit;
.\" note that it is defined as a string, so it will adapt to changes
.\" in the vertical spacing.  Changing it is NOT RECOMMENDED.
.\"
.nr PDFHREF.LEADING -1.0p
.ds PDFHREF.HEIGHT   1.0v
.\"
.\" PDF readers generally place a rectangular border around link
.\" "hot-spots".  Within text, this looks rather ugly, so we set
.\" "PDFHREF.BORDER" to suppress it -- the three zeroes represent
.\" the border parameters in the "/Border [0 0 0]" PDFMARK string,
.\" and may be changed to any valid form, as defined in Adobe's
.\" PDFMARK Reference Manual.
.\"
.ds PDFHREF.BORDER   0 0 0
.\"
.\" "PDFHREF.COLOUR" (note British spelling) defines the colour to
.\" be used for display of link "hot-spots".  This will apply both
.\" to borders, if used, and, by default to text; however, actual
.\" text colour is set by "PDFHREF.TEXT.COLOUR", which may be reset
.\" independently of "PDFHREF.COLOUR", to achieve contrasting text
.\" and border colours.
.\"
.\" "PDFHREF.COLOUR" must be set to a sequence of three values,
.\" each in the range 0.0 .. 1.0, representing the red, green, and
.\" blue components of the colour specification in the RGB colour
.\" domain, which is shared by "groff" and the PDF readers.
.\"
.ds PDFHREF.COLOUR   0.35 0.00 0.60
.defcolor pdf:href.colour rgb \*[PDFHREF.COLOUR]
.\"
.\" "PDFHREF.TEXT.COLOUR", on the other hand, is simply defined
.\" using any "groff" colour name -- this default maps it to the
.\" same colour value as "PDFHREF.COLOUR".
.\"
.ds PDFHREF.TEXT.COLOUR  pdf:href.colour
.\"
.\" Accommodate users who prefer the American spelling, COLOR, to
.\" the British spelling, COLOUR.
.\"
.als PDFHREF.COLOR       PDFHREF.COLOUR
.als PDFHREF.TEXT.COLOR  PDFHREF.TEXT.COLOUR
.\"
.\" All PDF "Hypertext" reference capabilities are accessed
.\" through the "pdfhref" macro
.\"
.de pdfhref
.\" -----------------------------------------------------------------
.\" Usage:
.\"   .pdfhref <subcommand [options ...] [parameters ...]> ...
.\" -----------------------------------------------------------------
.\"
.if \\n[PDFOPMODE] \{\
.\"
.\" Loop over all subcommands specified in the argument list
.\"
.   while \\n(.$ \{\
.   \"
.   \" Initially, assume each subcommand will complete successfully
.   \"
.      nr pdf:href.ok 1
.   \"
.   \" Initialise -E and -X flags in the OFF state
.   \"
.      nr pdf:href-E 0
.      nr pdf:href-X 0
.   \"
.   \" Handle the case where subcommand is specified as "-class",
.   \" setting up appropriate macro aliases for subcommand handlers.
.   \"
.      if dpdf*href\\$1       .als pdf*href      pdf*href\\$1
.      if dpdf*href\\$1.link  .als pdf*href.link pdf*href\\$1.link
.      if dpdf*href\\$1.file  .als pdf*href.file pdf*href\\$1.file
.   \"
.   \" Repeat macro alias setup
.   \" for the case where the subcommand is specified as "class",
.   \" (without a leading hyphen)
.   \"
.      if dpdf*href-\\$1      .als pdf*href      pdf*href-\\$1
.      if dpdf*href-\\$1.link .als pdf*href.link pdf*href-\\$1.link
.      if dpdf*href-\\$1.file .als pdf*href.file pdf*href-\\$1.file
.   \"
.   \" Process one subcommand ...
.   \"
.      ie dpdf*href \{\
.      \"
.      \" Subcommand "class" is recognised ...
.      \" discard the "class" code from the argument list,
.      \" set the initial argument count to swallow all arguments,
.      \" and invoke the selected subcommand handler.
.      \"
.         shift
.         nr pdf:argc \\n(.$
.         pdf*href \\$@
.      \"
.      \" When done,
.      \" discard all arguments actually consumed by the handler,
.      \" before proceeding to the next subcommand (if any).
.      \"
.         shift \\n[pdf:argc]
.      \}
.      el \{\
.      \"
.      \" Subcommand "class" is not recognised ...
.      \" issue a warning, and discard the entire argument list,
.      \" so aborting this "pdfhref" invocation
.      \"
.         pdf:warn \\$0: undefined reference class '\\$1' ignored
.         shift \\n(.$
.         \}
.   \"
.   \" Clean up temporary reference data,
.   \" to ensure it doesn't propagate to any future reference
.   \"
.      rm pdf*href pdf:href.link pdf:href.files
.      rr pdf:href-E pdf:href-X
.      pdf:href.options.clear
.      \}
.   rr pdf:href.ok
.   \}
..
.\"
.\" Macros "pdf:href.flag" and "pdf:href.option"
.\" provide a generic mechanism for switching on flag type options,
.\" and for decoding options with arguments, respectively
.\"
.de pdf:href.flag
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
.nr pdf:href\\$1 1
.nr pdf:href.argc 1
..
.de pdf:href.option
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
.ds pdf:href\\$1 \\$2
.nr pdf:href.argc 2
..
.\"
.\" Valid PDFHREF options are simply declared
.\" by aliasing option handlers to "pdf:href.option",
.\" or to "pdf:href.flag", as appropriate
.\"
.als pdf:href.opt-A pdf:href.option   \" affixed text
.als pdf:href.opt-D pdf:href.option   \" destination name
.als pdf:href.opt-E pdf:href.flag     \" echo link descriptor
.als pdf:href.opt-F pdf:href.option   \" remote file specifier
.als pdf:href.opt-N pdf:href.option   \" reference name
.als pdf:href.opt-P pdf:href.option   \" prefixed text
.als pdf:href.opt-T pdf:href.option   \" bookmark "tag"
.als pdf:href.opt-X pdf:href.flag     \" cross reference
.\"
.\" For references to another document file
.\" we also need to support OS dependent file name specifiers
.\"
.als pdf:href.opt-DF pdf:href.option  \" /DOSFile specifier
.als pdf:href.opt-MF pdf:href.option  \" /MacFile specifier
.als pdf:href.opt-UF pdf:href.option  \" /UnixFile specifier
.als pdf:href.opt-WF pdf:href.option  \" /WinFile specifier
.\"
.\" Macro "pdf:href.options.clear" ensures that ALL option
.\" argument strings are deleted, after "pdfhref" has completed
.\" all processing which depends on them
.\"
.de pdf:href.options.clear
.\" -----------------------------------------------------------------
.\" Usage:
.\"   .pdf:href.options.clear [option ...]
.\" -----------------------------------------------------------------
.\"
.\" When an option list is specified ...
.\"
.ie \\n(.$ \{\
.   \"
.   \" then loop through the list,
.   \" deleting each specified option argument string in turn
.   \"
.   while \\n(.$ \{\
.      if dpdf:href-\\$1 .rm pdf:href-\\$1
.      shift
.      \}
.   \}
.\"
.\" ... but when no list is specified,
.\" then recurse, to clear all known option argument strings
.\"
.el .pdf:href.options.clear A D F N P T DF MF UF WF
..
.\"
.\" "PDFHREF.INFO" establishes the content of the cross reference
.\" data record, which is exported via the "stderr" stream, when a
.\" cross reference anchor is created using a "pdfhref" macro request
.\" of the form
.\"
.\"    .pdfhref M -N name -X text ...
.\"
.\"    .ds PDFHREF.INFO \\*[PDFHREF.NAME] reference data ...
.\"
.ds PDFHREF.INFO page \\n% \\$*
.\"
.\" Macro "pdf*href-M" is the handler invoked by "pdfhref", when
.\" called with the "M" reference class specifier, to create a
.\" named cross reference mark, and to emit a cross reference
.\" data record, as specified by "PDFHREF.INFO".
.\"
.de pdf*href-M
.\" -----------------------------------------------------------------
.\" Usage:
.\"   .pdfhref M [-X] [-N name | -D name] [-E] descriptive text ...
.\" -----------------------------------------------------------------
.\"
.\" Initially, declare the -D and -N string options as empty,
.\" so we avoid warning messages when we try to use them, and find
.\" that they are undefined.
.\"
.ds pdf:href-D
.ds pdf:href-N
.\"
.\" Parse, interpret, and strip any specified options from the
.\" argument list.  (Note that only options with a declared handler
.\" will be processed; there is no provision for detecting invalid
.\" options -- anything which is not recognised is assumed to start
.\" the "descriptive text" component of the argument list).
.\"
.while dpdf:href.opt\\$1 \{\
.   pdf:href.opt\\$1 \\$@
.   shift \\n[pdf:href.argc]
.   \}
.\"
.\" If we found "--", to mark the end of the options,
.\" then we should discard it.
.\"
.if '\\$1'--' .shift
.\"
.\" All PDF reference markers MUST be named. The name may have been
.\" supplied using the "-N Name" option, (or the "-D Name" option);
.\" if not, deduce it from the first "word" in the "descriptive text",
.\" if any, and set the marker -- if we still can't identify the name
.\" for the destination, then this marker will not be created.
.\"
.pdf*href.set \\*[pdf:href-N] \\*[pdf:href-D] \\$1
.\"
.\" If we specified a cross reference, with the "-X" option, and the
.\" reference mark has been sucessfully created, then we now need to
.\" write the cross reference info to the STDERR stream
.\"
.if \\n[pdf:href-X] .pdf*href.export \\*[PDFHREF.INFO]
.\"
.\" Irrespective of whether this marker is created, or not,
.\" the descriptive text will be copied to the groff output stream,
.\" provided the "-E" option was specified
.\"
.if \\n[pdf:href-E] \&\\$*
..
.\"
.de pdf*href.set
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
.pdf*href.map.init
.ie \\n(.$ \{\
.   \"
.   \" a marker name has been supplied ...
.   \" if we are formatting for immediate output,
.   \" emit PDFMARK code to establish the associated view
.   \"
.   ie '\\n(.z'' \{\
.      pdf:href.sety
.      pdfmark /Dest /\\$1 /View [\\*[PDFHREF.VIEW]] /DEST
.      ds PDFHREF.NAME \\$1
.      rr PDFPAGE.Y
.      \}
.   \"
.   \" but, when formatting a diversion ...
.   \" delay output of the PDFMARK code, until the diversion
.   \" is eventually written out
.   \"
.   el \!.\\$0 \\$@
.   \"
.   \" check if we also need to emit cross reference data
.   \" (caller will do this if "pdf:href-X" is set, but it is
.   \"  not necessary, when "pdf:href.map" already exists)
.   \"
.   if dpdf:href.map .nr pdf:href-X 0
.   \}
.el \{\
.   \" marker is unnamed ...
.   \" issue error message; do not emit reference data
.   \"
.   pdf:warn pdfhref destination marker must be named
.   nr pdf:href-X 0
.   \}
..
.de pdf*href.export
.\"
.\" Called ONLY by "pdf*href-M",
.\" this macro ensures that the emission of exported reference data
.\" is synchronised with the placement of the reference mark,
.\" especially when the mark is defined within a diversion.
.\"
.ie '\\n(.z'' .tm gropdf-info:href \\*[PDFHREF.NAME] \\$*
.el \!.\\$0 \\$@
..
.\"
.\" Macro "pdf*href-D" is invoked when "pdfhref" is called
.\" with the "D" reference class specifier; it provides a
.\" standardised mechanism for interpreting reference data
.\" exported by the "M" reference class, and may be used
.\" to directly define external reference data, without the
.\" use of "M" reference class designators in the source
.\" document.
.\"
.de pdf*href-D
.ds pdf:href-N
.\"
.\" Parse, interpret, and strip any specified options from the
.\" argument list.  (Note that only options with a declared handler
.\" will be processed; there is no provision for detecting invalid
.\" options -- anything which is not recognised is assumed to start
.\" the "descriptive text" component of the argument list).
.\"
.while dpdf:href.opt\\$1 \{\
.   pdf:href.opt\\$1 \\$@
.   shift \\n[pdf:href.argc]
.   \}
.\"
.\" If we found "--", to mark the end of the options,
.\" then we should discard it.
.\"
.if '\\$1'--' .shift
.\"
.ie '\\*[pdf:href-N]'' \{\
.   pdf:warn pdfhref defined reference requires a name
.   \}
.el \{\
.   ds pdf:href(\\*[pdf:href-N]).info \\$*
.   \}
..
.\"
.\" Macro "pdf*href-F" is invoked when "pdfhref" is called
.\" with the "F" reference class specifier; it allows the user
.\" to provide an alternative interpreter macro, which will be
.\" called when a "PDFHREF.INFO" record is retrieved to define
.\" the text of a cross reference link "hot spot".
.\"
.de pdf*href-F
.\" ----------------------------------------------------------------
.\" Usage:
.\"   .pdfhref F [macro-name]
.\" ----------------------------------------------------------------
.\"
.\" Set macro specified by "macro-name" as the format interpreter
.\" for parsing "PDFHREF.INFO" records; if "macro-name" is omitted,
.\" or is specified as the reserved name "default", then use the
.\" default format parser, "pdf*href.format", defined below.
.\"
.if '\\$1'default' .shift \\n(.$
.ie \\n(.$ .als pdf*href.format \\$1
.el .als pdf*href.format pdf*href.default
.nr pdf:argc 1
..
.\" The default reference formatting macro is defined below.
.\" It parses the "PDFHREF.INFO" record specific to each reference,
.\" recognising the keywords "file", "page" and "section", when they
.\" appear in initial key/value pairs, replacing the key/value pair
.\" with "PDFHREF.FILEREF", "PDFHREF.PAGEREF" or "PDFHREF.SECTREF"
.\" respectively; any additional data in the "PDFHREF.INFO" record
.\" is enclosed in typographic double quotes, and the parsed record
.\" is appended to "PDFHREF.PREFIX", to be returned as the formatted
.\" reference text.
.\"
.\" Default definitions for the reference strings "PDFHREF.PREFIX",
.\" "PDFHREF.FILEREF", "PDFHREF.PAGEREF" and "PDFHREF.SECTREF" are
.\" provided, in the English language.  Users may substitute any
.\" desired alternative definitions, for example, when formatting
.\" documents in other languages.  In each case, "\\$1" may be used
.\" in the substitution, to represent the "value" component of the
.\" respective key/value pair specified in the "PDFHREF.INFO" record.
.\"
.ds PDFHREF.PREFIX   see
.ds PDFHREF.PAGEREF  page \\$1,
.ds PDFHREF.SECTREF  section \\$1,
.ds PDFHREF.FILEREF  \\$1
.\"
.de pdf*href.format
.\" -----------------------------------------------------------------
.\" Usage: (to be called ONLY by "pdfhref")
.\"   .pdf*href.format cross reference data ...
.\" -----------------------------------------------------------------
.\"
.\" This macro is responsible for defining the strings "PDFHREF.TEXT"
.\" and "PDFHREF.DESC", which are used by the "pdfhref" macro, as the
.\" basis for generating the text content of a link "hot spot"; (any
.\" user specified alternate formatter MUST do likewise).
.\"
.\" Note that "PDFHREF.TEXT" defines the overall format for the "link
.\" text", while "PDFHREF.DESC" is the descriptive component thereof.
.\"
.\" This default implementation, subject to user customisation of the
.\" "internationalisation" strings defined above, formats "hot spots"
.\" of the style
.\"
.\"    see page N, section S, "descriptive text ..."
.\"
.ds PDFHREF.TEXT \\*[PDFHREF.PREFIX]
.while d\\$0.\\$1 \{\
.   \\$0.\\$1 "\\$2"
.   shift 2
.   \}
.\"
.\" Retrieve the descriptive text from the cross reference data,
.\" ONLY IF no overriding description has been set by the calling
.\" "pdfhref" macro invocation.
.\"
.if \\n(.$ .if !dPDFHREF.DESC .ds PDFHREF.DESC \\$*
.\"
.\" Augment "PDFHREF.TEXT" so the descriptive text will be included
.\" in the text of the formatted reference
.\"
.if dPDFHREF.DESC .as PDFHREF.TEXT " \(lq\\\\*[PDFHREF.DESC]\(rq
.\"
.\" Finally, suppress any leading spaces,
.\" which may have been included in the PDFHREF.TEXT definition.
.\"
.ds PDFHREF.TEXT \\*[PDFHREF.TEXT]
..
.de pdf*href.format.file
.\" ----------------------------------------------------------------------
.\" Include a file identifier in a formatted reference.
.\" This is invoked ONLY by "pdf*href.format", and ONLY IF the
.\" reference data includes an initial file identifier tuple.
.\" ----------------------------------------------------------------------
.\"
.as PDFHREF.TEXT " \\*[PDFHREF.FILEREF]
..
.de pdf*href.format.page
.\" ----------------------------------------------------------------------
.\" Include a page number in a formatted reference.
.\" This is invoked ONLY by "pdf*href.format", and ONLY IF the
.\" reference data includes an initial page number tuple.
.\" ----------------------------------------------------------------------
.\"
.as PDFHREF.TEXT " \\*[PDFHREF.PAGEREF]
..
.de pdf*href.format.section
.\" ----------------------------------------------------------------------
.\" Include a section number in a formatted reference.
.\" This is invoked ONLY by "pdf*href.format", and ONLY IF the
.\" reference data includes an initial section number tuple.
.\" ----------------------------------------------------------------------
.\"
.as PDFHREF.TEXT " \\*[PDFHREF.SECTREF]
..
.\"
.\" Make "pdf*href.format" the default cross reference formatter
.\"
.als pdf*href.default pdf*href.format
.\"
.\"
.\" Macro "pdf*href" provides a generic mechanism for placing link
.\" "hot-spots" in a PDF document.  ALL "pdfhref" class macros which
.\" create "hot-spots" are aliased to this macro; each must also have
.\" an appropriately aliased definition for "pdf*href.template".
.\"
.de pdf*href
.\" ------------------------------------------------------------------
.\" Usage:
.\"   .pdf*href class [options ...] [link text ...]
.\" ------------------------------------------------------------------
.\"
.\" First, we initialise an empty string, which will be affixed to
.\" the end of the "link text".  (This is needed to cancel the effect
.\" of a "\c" escape, which is placed at the end of the "link text"
.\" to support the "-A" option -- any text supplied by the user, when
.\" the "-A" option is specified, will replace this empty string).
.\"
.ds pdf:href-A
.\"
.\" Now we interpret, and remove any specified options from the
.\" argument list.  (Note that only options with a declared handler
.\" will be processed;  there is no provision for detecting invalid
.\" options -- anything which is not recognised is assumed to start
.\" the "link text" component of the argument list).
.\"
.while dpdf:href.opt\\$1 \{\
.   pdf:href.opt\\$1 \\$@
.   shift \\n[pdf:href.argc]
.   \}
.\"
.\" If we found "--", to mark the end of the options, then we should
.\" discard it.
.\"
.if '\\$1'--' .shift
.\"
.\" All PDF link classes REQUIRE a named destination.  This may have
.\" been supplied using the "-D Name" option, but, if not, deduce it
.\" from the first "word" in the "link text", if any -- if we still
.\" can't identify the destination, then set "pdf:href.ok" to zero,
.\" so this link will not be created.
.\"
.if !dpdf:href-D .pdf:href.option -D \\$1
.if '\\*[pdf:href-D]'' \{\
.   pdf:error pdfhref has no destination
.   nr pdf:href.ok 0
.   \}
.\"
.\" Some PDF link classes support a "/File (FilePathName)" argument.
.\"
.if dpdf*href.file \{\
.   \"
.   \" When this is supported, it may be specified by supplying
.   \" the "-F FileName" option, which is captured in "pdf:href-F".
.   \"
.   if dpdf:href-F \{\
.      \"
.      \" the /File key is present, so set up the link specification
.      \" to establish the reference to the specified file
.      \"
.      als pdf*href.link pdf*href.file
.      ds pdf:href.files /File (\\*[pdf:href-F])
.      \"
.      \" in addition to the /File key,
.      \" there may also be platform dependent alternate file names
.      \"
.      if dpdf:href-DF .as pdf:href.files " /DOSFile (\\*[pdf:href-DF])
.      if dpdf:href-MF .as pdf:href.files " /MacFile (\\*[pdf:href-MF])
.      if dpdf:href-UF .as pdf:href.files " /UnixFile (\\*[pdf:href-UF])
.      if dpdf:href-WF .as pdf:href.files " /WinFile (\\*[pdf:href-WF])
.      \}
.   \" In some cases, the "/File" key is REQUIRED.
.   \" We will know it is missing, if "pdf*href.link" is not defined.
.   \"
.   if !dpdf*href.link \{\
.   \"
.   \" When a REQUIRED "/File" key specification is not supplied,
.   \" then complain, and set "pdf:href.ok" to abort the creation
.   \" of the current reference.
.   \"
.      pdf:error pdfhref: required -F specification omitted
.      nr pdf:href.ok 0
.      \}
.   \" Now, we have no further use for "pdf*href.file".
.   \"
.   rm pdf*href.file
.   \}
.\"
.\" Now, initialise a string, defining the PDFMARK code sequence
.\" to create the reference, using the appropriate type indicators.
.\"
.ds pdf:href.link /Subtype /Link \\*[pdf*href.link]
.\"
.\" And now, we have no further use for "pdf*href.link".
.\"
.rm pdf*href.link
.\"
.\" If the user specified any "link prefix" text, (using the "-P text"
.\" option), then emit it BEFORE processing the "link text" itself.
.\"
.if dpdf:href-P \&\\*[pdf:href-P]\c
.ie \\n[pdf:href.ok] \{\
.   \"
.   \" This link is VALID (so far as we can determine) ...
.   \" Modify the "link text" argument specification, as required,
.   \" to include any pre-formatted cross reference information
.   \"
.   ie \\n(.$ \{\
.      \"
.      \" One or more "link text" argument(s) are present,
.      \" so, set the link description from the argument(s) ...
.      \"
.      ds PDFHREF.DESC \\\\$*
.      ie \\n[pdf:href-X] \{\
.         \"
.         \" ... and, when the "-X" flag is set,
.         \" also include formatted location information,
.         \" derived from the cross reference record.
.         \"
.         pdf*href.format \\*[pdf:href(\\*[pdf:href-D]).info]
.         \}
.      el \{\
.         \" ... but, when the "-X" flag is NOT set,
.         \" use only the argument(s) as the entire content
.         \" of the "link text"
.         \"
.         rn PDFHREF.DESC PDFHREF.TEXT
.         \}
.      \}
.   el \{\
.      \" No "link text" arguments are present,
.      \" so, format the cross reference record to define
.      \" the content of the "link text".
.      \"
.      pdf*href.format \\*[pdf:href(\\*[pdf:href-D]).info]
.      \}
.   \" Apply border and colour specifications to the PDFMARK string
.   \" definition, as required.
.   \"
.   if dPDFHREF.BORDER .as pdf:href.link " /Border [\\*[PDFHREF.BORDER]]
.   if dPDFHREF.COLOUR .as pdf:href.link " /Color  [\\*[PDFHREF.COLOUR]]
.   \"
.   \" Emit the "link text", in its appropriate colour, marking the
.   \" limits of its bounding box(es), as the before and after output
.   \" text positions.
.   \"
.   pdf*href.mark.begin "\\*[pdf:href.link]"
.   if dPDFHREF.COLOUR .defcolor pdf:href.colour rgb \\*[PDFHREF.COLOUR]
.   nop \&\m[\\*[PDFHREF.TEXT.COLOUR]]\\*[PDFHREF.TEXT]\m[]\c
.   pdf*href.mark.end
.   \"
.   \" Clean up the temporary registers and strings, used to
.   \" compute the "hot-spot" bounds, and format the reference,
.   \"
.   rm PDFHREF.DESC PDFHREF.TEXT
.   \}
.\"
.\" But when we identify an INVALID link ...
.\" We simply emit the "link text", with no colour change, no border,
.\" and no associated "hot-spot".
.\"
.el \&\\$*\c
.\"
.\" And then, if the user specified any affixed text, (using the
.\" "-A text" option), we tack it on at the end.
.\"
.nop \&\\*[pdf:href-A]
..
.de pdf*href.map.init
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
.\"
.if dpdf:href.map-1 \{\
.   \"
.   \" We have a reference map, but we haven't started to parse it yet.
.   \" This must be the first map reference in pass 2, so we need to
.   \" "kick-start" the parsing process, by loading the first indexed
.   \" sub-map into the global map.
.   \"
.   rn pdf:href.map-1 pdf:href.map
.   als pdf:href.map.internal pdf:href.map
.   nr pdf:href.map.index 1 1
.   \}
.als pdf*href.map.init pdf*href.mark.idle
..
.\"
.\" "pdf*href-Z" is used to add link co-ordinate entries to the
.\" "pdf:href.map".  Primarily, it is used by the "pdfroff" formatter,
.\" to pass link co-ordinate data from one "groff" formatting pass to
.\" the next, and is not generally useful to the end user.
.\"
.de pdf*href-Z
.\" ----------------------------------------------------------------------
.\" Usage:
.\"   .pdfhref Z page-index x-displacement y-displacement
.\" Where:
.\"   page-index      is the reference mark's page number
.\"   x-displacement  is its offset from the left edge of the page
.\"   y-displacement  is its offset from the top edge of the page
.\" ( both displacement values are expressed in basic groff units, )
.\" ( and measured perpendicular to their respective page edges.   )
.\" ----------------------------------------------------------------------
.\"
.ie \\n(.$=3 .ds pdf:href.map-\\n+[pdf*href-Z.index] \\$*
.el .pdf:error pdfhref Z operator expects exactly three arguments
..
.\" Initialise the auto-incrementing "pdf*href-Z.index" register,
.\" to ensure that sub-map numbering starts at 1.
.\"
.nr pdf*href-Z.index 0 1
.\"
.de pdf*href.map.read
.\" ----------------------------------------------------------------------
.\" Usage: (internal use only):
.\"   .pdf*href.map.read co-ordinate name list ...
.\" ----------------------------------------------------------------------
.\"
.\" Reads values from "pdf:href.map" to each named register, in turn
.\" Reading to "null" discards the corresponding value in "pdf:href.map"
.\"
.while \\n(.$ \{\
.   \"
.   \" Loop over all registers named in the argument list,
.   \" assigning values from "pdf:href.map" to each in turn.
.   \"
.   pdf:pop nr pdf:\\$1 pdf:href.map.internal
.   if !dpdf:href.map.internal \{\
.      \"
.      \" We ran out of map references in the current sub-map,
.      \" so move on to the next indexed sub-map, if any.
.      \"
.      if dpdf:href.map-\\n+[pdf:href.map.index] \{\
.         rn pdf:href.map-\\n[pdf:href.map.index] pdf:href.map
.         als pdf:href.map.internal pdf:href.map
.         \}
.      \}
.   \"
.   \" Proceed to the next named co-ordinate, (if any), specified
.   \" in the argument list.
.   \"
.   shift
.   \}
.\"
.\" Discard any assignments to a register named "null"
.\"
.rr pdf:null
..
.de pdf*href.mark.begin
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
.pdf*href.map.init
.ie dpdf:href.map \{\
.   \"
.   \" Once we have established a document reference map,
.   \" then this, and all subsequent calls to "pdf*href.mark.begin",
.   \" may be redirected to the reference mark resolver, and the
.   \" "pdf*href.mark.end" macro has nothing further to do.
.   \"
.   pdf*href.mark.resolve \\$@
.   rn pdf*href.mark.resolve pdf*href.mark.begin
.   als pdf*href.mark.end pdf*href.mark.idle
.   \}
.el \{\
.   \" Since we don't yet have a document reference map, the
.   \" reference mark resolver will not work, in this pass of the
.   \" formatter;  this, and all subsequent calls to "pdf*href.mark.begin",
.   \" may be redirected to "pdf*href.mark.end", which is responsible
.   \" for emitting the reference mark data to be incorporated into
.   \" the reference map in a subsequent formatting pass.
.   \"
.   pdf*href.mark.end
.   als pdf*href.mark.begin pdf*href.mark.end
.   \}
..
.de pdf*href.mark.resolve
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
.ie '\\n(.z'' \{\
.   ds pdf:href.link \\$1
.   nr pdf:urx \\n(.o+\\n(.l
.   pdf*href.map.read spg llx ury epg urx.end lly.end
.   ie \\n[pdf:spg]=\\n[pdf:epg] \{\
.      \"
.      \" This link is entirely contained on a single page ...
.      \" emit the text, which defines the content of the link region,
.      \" then make it active.
.      \"
.      pdf*href.mark.emit 1 \\n[pdf:urx.end]
.      if \\n[pdf:lly]<\\n[pdf:lly.end] \{\
.         \"
.         \" This link spans multiple output lines; we must save its
.         \" original end co-ordinates, then define a new intermediate
.         \" end point, to create a PDFMARK "hot-spot" extending from
.         \" the start of the link to the end if its first line.
.         \"
.         nr pdf:ury +1v
.         nr pdf:llx \\n(.o+\\n[.in]
.         nr pdf:lly \\n[pdf:lly.end]-\\*[PDFHREF.HEIGHT]
.         if \\n[pdf:ury]<\\n[pdf:lly] \{\
.            nr pdf:lly +\\*[PDFHREF.HEIGHT]-1v
.            pdf*href.mark.emit 2
.            nr pdf:ury \\n[pdf:lly.end]-\\*[PDFHREF.HEIGHT]
.            \}
.         pdf*href.mark.emit 0 \\n[pdf:urx.end]
.         \}
.      pdf*href.mark.flush
.      \}
.   el \{\
.      \" This link is split across a page break, so ...
.      \" We must mark the "hot-spot" region on the current page,
.      \" BEFORE we emit the link text, as we will have moved off
.      \" this page, by the time the text has been output.
.      \"
.      \" First step: define the region from the start of the link,
.      \" to the end of its first line.
.      \"
.      pdf*href.mark.emit 1 \\n[pdf:urx]
.      \"
.      \" All additional regions MUST align with the left margin.
.      \"
.      nr pdf:llx \\n(.o+\\n[.in]
.      \"
.      \" If the current page can accomodate more than the current line,
.      \" then it will include a second active region for this link; this
.      \" will extend from just below the current line to the end of page
.      \" trap, if any, or the bottom of the page otherwise, and occupy
.      \" the full width of the page, between the margins.
.      \"
.      nr pdf:ury +1v
.      pdf*href.mark.emit 3
.      \"
.      \" We now need a page transition trap, to map the active link
.      \" region(s), which overflow on to the following page(s); (the
.      \" handler for this trap MUST have been previously installed).
.      \"
.      ie dpdf*href.mark.hook \{\
.         \"
.         \" The page transition trap handler has been installed,
.         \" so we may activate both it, and also the appropriate
.         \" termination handler, to deactivate it when done.
.         \"
.         als pdf*href.mark.hook pdf*href.mark.trap
.         \"
.         \" Now we set up "pdf:epg" to count the number of page breaks
.         \" which this link will span, and emit the link text, leaving
.         \" the page trap macro to map active regions on intervening
.         \" pages, which are included in the link.
.         \"
.         nr pdf:epg -\\n[pdf:spg] 1
.         \}
.      el \{\
.         \" There was no handler initialised for the page trap,
.         \" so we are unable to map the active regions for this link;
.         \" we may discard the remaining map data for this link,
.         \" and issue a diagnostic.
.         \"
.         pdf:error pdfhref: link dissociated at page break (trap not initialised)
.         if dPDFHREF.BROKEN.COLOR \{\
.            \"
.            \" The user may opt to have such broken links highlighted.
.            \" We use "PDFHREF.BROKEN.COLOUR" to specify this requirement,
.            \" but the user may prefer the American spelling, so we will
.            \" handle both as equivalent.
.            \"
.            als PDFHREF.BROKEN.COLOUR PDFHREF.BROKEN.COLOR
.            \}
.         if dPDFHREF.BROKEN.COLOUR \{\
.            if dPDFHREF.COLOUR .als PDFHREF.COLOUR PDFHREF.BROKEN.COLOUR
.            \}
.         \}
.      \}
.   \}
.el \!.\\$0 \\$@
..
.\"
.\" Macro "pdf*href.mark.emit" is called only by "pdf*href".  It is
.\" responsible for emitting the PDFMARK code, to establish the
.\" "hot-spot" region associated with a document or resource link.
.\"
.de pdf*href.mark.emit
.\" ----------------------------------------------------------------------
.\" Usage:
.\"   .pdf*href.mark.emit <action> [<end-urx>]
.\"     <action> == 0 --> normal operation -- link height = 1 line
.\"     <action> == 1 --> start of link -- add leading above text
.\"     <action> == 2 --> overtall link -- set intermediate baseline
.\"     <action> == 3 --> split link -- break at bottom of page
.\" ----------------------------------------------------------------------
.\"
.if \\$1=1 \{\
.   \"
.   \" Initialising a new link region ...
.   \" Some different versions of "groff" disagree about the vertical
.   \" displacement of "opminy", as emitted by "\O1|\h'-\w"|"u'\O2\c",
.   \" relative to the current text baseline.  Therefore, recompute
.   \" the link displacement, independently of "opminy".
.   \"
.   mk pdf:ury.base
.   while \\n[pdf:ury.base]<\\n[pdf:ury] .nr pdf:ury.base +1v
.   nr pdf:ury.base -1m+\\n[PDFHREF.LEADING]
.   \"
.   \" adjust the end-point vertical displacement by the same offset,
.   \" and then relocate the link starting point to its new displacement,
.   \" as established by this base line relative computation.
.   \" 
.   nr pdf:lly.end +\\n[pdf:ury.base]-\\n[pdf:ury]+\\*[PDFHREF.HEIGHT]
.   rnn pdf:ury.base pdf:ury
.   \}
.if \\$1<2 \{\
.   \"
.   \" Link segment fits on a single line ...
.   \" Set its height and end-point horizontal displacement accordingly.
.   \"
.   nr pdf:lly \\n[pdf:ury]+\\*[PDFHREF.HEIGHT]
.   if \\n[pdf:lly]>=\\n[pdf:lly.end] .nr pdf:urx \\$2
.   \}
.ie \\$1=3 \{\
.   \"
.   \" Link segment extends beyond the next page break ...
.   \" Recompute truncated height, to just fit portion on current page,
.   \" recursing to emit it, and leaving page trap mechanism to place
.   \" continuation region(s) on following page(s).
.   \"
.   nr pdf:lly (\\n[.t]u-\\n[.V]u)/1v
.   if \\n[pdf:lly]>0 \{\
.      nr pdf:lly \\n[pdf:ury]+\\n[pdf:lly]v-1v+\\*[PDFHREF.HEIGHT]
.      pdf*href.mark.emit 2
.      \}
.   \}
.el \{\
.   \" Link region size and placement has been fully specified ...
.   \" Emit it.
.   \"
.   pdfmark \\*[pdf:href.link] /Rect [\\*[pdf:bbox]] /ANN
.   \}
..
.\"
.\" When "pdf*href" emits a link for which the "hot-spot" spans a
.\" page break, then we need to provide a "hook" in to the page break
.\" trap, so we can map the "hot-spot" regions which are to be placed
.\" on either side of the page break.
.\"
.\" Macro "pdf*href.mark.idle" is a dummy macro, which provide this
.\" "hook" for normal page breaks, where there is no link "hot-spot"
.\" crossing the break.
.\"
.de pdf*href.mark.idle
.\" ----------------------------------------------------------------------
.\" Usage:
.\"   Called only as an internal hook, by a page trap macro.
.\"   Expects no arguments, and does nothing.
.\" ----------------------------------------------------------------------
..
.\"
.\" Macro "pdf*href.mark.trap" is the active "hook", which is substituted
.\" for "pdf*href,mark.idle" at those page breaks which are crossed by
.\" a link "hot-spot".
.\"
.de pdf*href.mark.trap
.\" ----------------------------------------------------------------------
.\" Usage:
.\"   Called only as an internal hook, by a page trap macro.
.\"   Expects no arguments.  Maps residual link "hot-spot" regions,
.\"   which spill beyond any page break.  Not to be invoked directly
.\"   by the user, nor by any user supplied macro.
.\" ----------------------------------------------------------------------
.\"
.mk pdf:ury
.nr pdf:ury +1v-1m-\\n[PDFHREF.LEADING]
.ie \\n-[pdf:epg] \{\
.   \"
.   \" The link "hot-spot" extends across more than one page break,
.   \" so, for each page which is completely contained within the
.   \" extent of the link, simply mark the entire text area on the
.   \" page as a "hot-spot".
.   \"
.   pdf*href.mark.emit 3
.   \}
.el \{\
.   \" The link "hot-spot" ends on the page which immediately follows
.   \" the current page transition, so we may now finalise this link.
.   \"
.   nr pdf:lly \\n[pdf:ury]+\\*[PDFHREF.HEIGHT]
.   if \\n[pdf:lly.end]>\\n[pdf:lly] \{\
.      \"
.      \" The "hot-spot" extends beyond the first line of text,
.      \" on its final page; compute and emit "hot-spot" region to cover
.      \" the full with of the text area, including all but the last
.      \" line of the link text.
.      \"
.      while \\n[pdf:lly.end]>\\n[pdf:lly] .nr pdf:lly +1v
.      nr pdf:lly -1v
.      pdf*href.mark.emit 2
.      \"
.      \" Now, adjust the vertical "hot-spot" mapping reference,
.      \" to identify the correct position for the the last line of
.      \" text, over which the "hot-spot" extends.
.      \"
.      nr pdf:ury \\n[pdf:lly.end]-\\*[PDFHREF.HEIGHT]
.      \}
.   \"
.   \" We now have exactly one final line of text, over which we must
.   \" emit a "hot-spot" region;  map it, terminate page trap processing
.   \" for this "hot-spot", and clean up the "hot-spot" mapping context.
.   \"
.   pdf*href.mark.emit 0 \\n[pdf:urx.end]
.   als pdf*href.mark.hook pdf*href.mark.idle
.   pdf*href.mark.flush
.   \}
..
.de pdf*href.mark.flush
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
.rr pdf:spg pdf:epg
.rr pdf:llx pdf:lly pdf:urx pdf:ury
.if dPDFHREF.COLOR .als PDFHREF.COLOUR PDFHREF.COLOR
.rr pdf:urx.end pdf:lly.end
..
.de pdf*href.mark.end
.\" ----------------------------------------------------------------------
.\" ----------------------------------------------------------------------
\O1|\h'-\w"|"u'\O2\c
..
.\" Macro "pdf*href-I" is used for one time initialisation of special
.\" "pdfhref" features; (currently, only the above page trap hook is
.\" supported, but it is implemented with one level of indirection, to
.\" accommodate possible future expansion).
.
.de pdf*href-I
.\" ----------------------------------------------------------------------
.\" Usage:
.\"   .pdfhref I -<option> <optarg> [-<option> <optarg>] ...
.\" ----------------------------------------------------------------------
.\"
.\" Loop over all arguments, in pairs ...
.
.while \\n(.$ \{\
.   \"
.   \" handing them off to their respective initialisers,
.   \" when suitable initialisers exist, or complaining otherwise.
.   \"
.   ie dpdf*href\\$1.init .pdf*href\\$1.init \\$2
.   el .pdf*error pdfhref:init: unknown feature '\\$1'
.   shift 2
.   \}
..
.\" Before we can use the page break "hook", we need to initialise it
.\" as an addendum to a regular page break trap. To ensure that we don't
.\" compromise the user's page trap setup, we leave the onus for this
.\" initialisation with the user, but we provide the "pdf*href-PT.init"
.\" macro, (invoked by ".pdfhref I -PT <macro-name>"), to implement a
.\" suitable initialisation action.
.
.de pdf*href-PT.init
.\" ----------------------------------------------------------------------
.\" Usage:
.\"   .pdfhref I -PT <macro-name>
.\"     <macro-name> == name of user's page break trap macro
.\" ----------------------------------------------------------------------
.\"
.\" Initially, map the page break hook to its default, do nothing helper.
.
.als pdf*href.mark.hook pdf*href.mark.idle
.ie !\\n(.$ \{\
.   \"
.   \" Don't have enough arguments to specify a page trap macro name,
.   \" so simply plant "pdf*href.mark.hook" as a top of page trap.
.   \"
.   wh 0 pdf*href.mark.hook
.   \}
.el \{\
.   \" Page trap macro name is specified in "\\$1" ...
.   \"
.   ie d\\$1 \{\
.      \"
.      \" When this page trap macro already exists, then we simply
.      \" append a call to "pdf*href.mark.hook" to it.
.      \"
.      am \\$1 pdf*href.mark.idle
.         pdf*href.mark.hook
.         pdf*href.mark.idle
.      \}
.   el \{\
.      \" However, when the specified page trap macro does not yet
.      \" exist, then we create it, and plant it as a top of page
.      \" trap.
.      \"
.      de \\$1 pdf*href.mark.idle
.         pdf*href.mark.hook
.         pdf*href.mark.idle
.      wh 0 \\$1
.      \}
.   \}
..
.
.\" "pdf*href-L" is the generic handler for creating references to
.\" named destinations in PDF documents.  It supports both local
.\" references, to locations within the same document, through its
.\" "pdf*href-L.link" attribute, and also references to locations
.\" in any other PDF document, through "pdf*href-L.file".
.\"
.als pdf*href-L      pdf*href
.ds  pdf*href-L.link /Dest /\\\\*[pdf:href-D]
.ds  pdf*href-L.file /Action /GoToR \\\\*[pdf:href.files] \\*[pdf*href-L.link]
.\"
.\" "pdf*href-O" is the "official" handler for creating PDF
.\" document outlines.  It is simply an alias to "pdfbookmark",
.\" which may also be invoked directly, if preferred.  Neither
.\" a "pdf*href-O.link" nor a "pdf*href-O.file" attribute is
.\" required.
.\"
.als pdf*href-O      pdfbookmark
.\"
.\" "pdf*href-W" is the generic handler for creating references to
.\" web resources, (or any resource specified by a uniform resource
.\" identifier).  Such resource links are fully specified by the
.\" "pdf*href-W.link" attribute.
.\"
.als pdf*href-W      pdf*href
.ds  pdf*href-W.link /Action << /Subtype /URI /URI (\\\\*[pdf:href-D]) >>
.\"
.\" pdfmark.tmac: end of file / vim: ft=groff
