Plain TeX and different hyperlink styles in dvi and pdf

You get the same result with the hypertex driver of the LaTeX package hyperref. The hyperTeX specials define some very basic HTML markup, e.g.:

s:[1/1]:: html:<a name="page.1">
s:[1/1]:: html:</a>
s:[1/1]:: html:<a name="Doc-Start">
s:[1/1]:: html:</a>
s:[1/1]:: html:<a name="section*.1">
s:[1/1]:: html:</a>
s:[1/1]:: html:<a href="#section.1">
s:[1/1]:: html:</a>
s:[1/1]:: html:<a name="section.1">
s:[1/1]:: html:</a>
s:[1/1]:: html:<a href="#section.1">
s:[1/1]:: html:</a>
s:[1/1]:: html:<a href="http://www.example.org/">
s:[1/1]:: html:</a>

This is the output of dvii, the TeX source file:

\documentclass{article}
\usepackage[hypertex]{hyperref}
\begin{document}
\tableofcontents
\section{Hello World}
\label{sec:hello}
See section \ref{sec:hello}.
\url{http://www.example.org/}
\end{document}

The hyperTeX specials do not provide configuration options and do not know about PDF. Thus the link outcome is in control of the DVI program:

  • xdvi underlines in blue.
  • dvips -z uses blue boxes.

Configuration of xdvi

There are three options to configure the link outcome, from the manual page of `xdvi`:
-linkcolor
      (.linkColor)  Color  used  for unvisited hyperlinks
      (`Blue2' by default). Hyperlinks are unvisited  be­
      fore  you  click on them, or after the DVI file has
      been reloaded.  The value should be either a  valid
      X color name (such as DarkGoldenrod4) or a hexadec­
      imal color string (such as #8b6508).Seealso -visit­
      edlinkcolor and -linkstyle.

-linkstyle
      (.LinkStyle)  Determines  the style in which hyper­
      links are  displayed.  Possible  values  and  their
      meanings are:

       0       No highlighting of links
       1       Underline links with link color
       2       No underlining, color text with link color
       3       Underline and display text colored with
               link color

      The  values for link color are specified by the op­
      tions/resources  -linkcolor  and  -visitedlinkcolor
      (which see).

-visitedlinkcolor
      (.visitedLinkColor) Color used for  visited  hyper­
      links  (`Purple4'  by  default).  Hyperlinks become
      visited once you click on them. As  for  linkColor,
      the  value should be either a valid X color name or
      a hexadecimal color string.

Configuration of dvips

The border and color are hardcoded, from hps.c:

p->color[0] = 0;
p->color[1] = 0; /* Blue links */
p->color[2] = 1;
p->border[0] = 1; /* horizontal corner radius */
p->border[1] = 1; /* vertical corner radius */
p->border[2] = 1; /* box line width */
p->border[3] = 3; /* dash on size */
p->border[4] = 3;  /* dash off size */

As result dvips -z writes the links of the example above as:

(#section.1) [[134 682 210 694] [1 1 1 [3 3]] [0 0 1]] pdfm 
(#section.1) [[185 628 190 640] [1 1 1 [3 3]] [0 0 1]] pdfm
[[197 628 318 640] [1 1 1 [3 3]] [0 0 1]] (http://www.example.com/) pdfm 

To get different colors, either the definition of pdfm needs to be changed in hps.pro or the redefinition can be done at later time by manipulating the arguments for pdfm and replacing the arrays for the border and the color.

Or in short, dvips -z cannot be configured without hacking internals.


This is my solution to the problems of (1) using pdfmark in plain TeX to produce the hyperlinks in PDF outputs, and (2) converting the blue rectangles to underlines.

http://linux.topology.org/pdfmark.html

It's too long to summarize here. But roughly speaking, the answer to (1) was to write some TeX macros to do the job. The answer to (2) was to edit the file "hps.pro" in the current working directory to use the underlines.


PS. Here's my answer to part (1).
How to use PDF hyperlinks in plain TeX.

When I say "plain TeX", I mean completely plain TeX with no 3rd party macro libraries or external scripts or anything like that, except for what I write myself. The basic references for my plain TeX macros are http://arxiv.org/hypertex/ (the section marked "How it is all done internally") and https://www.tug.org/applications/hyperref/ftp/doc/manual.html (the section marked "introduction").

First I had to get the HTML special commands into the DVI file by writing them into my own \chapter, \section and various subsection macros. These are far too tedious and irrelevant to write out in full. But the following code snippet gives the basic idea.

% This \PreHatch macro to prefix a text string with a hatch character.
{\catcode`\^=6 \catcode`\#=12 \gdef\PreHatch^1{#^1}}

% Anchor points for cross-reference hyperlinks.
\def\LinkNameText#1#2{%
 \special{html:<a name="#1">}#2\special{html:</a>}}
\def\LinkNamePRE#1{\special{html:<a name="#1">}}
\def\LinkNamePOST{\special{html:</a>}}
\def\LinkName#1{\LinkNameText{#1}{}}

% Cross-reference hyperlinks to defined anchor points.
\def\LinkHrefText#1#2{%
 \special{html:<a href="\PreHatch{#1}">}#2\special{html:</a>}}

% Pre-text and post-text macros.
\def\LinkHrefPRE#1{\special{html:<a href="\PreHatch{#1}">}}
\def\LinkHrefPOST{\special{html:</a>}}

% External hyperlinks.
\def\LinkHrefExtText#1#2{%
 \special{html:<a href="#1">}#2\special{html:</a>}}
\def\LinkHrefExt#1{\LinkHrefExtText{#1}{#1}}
\def\LinkHrefExtTT#1{\LinkHrefExtText{#1}{{\tt#1}}}

That's the actual code which I use for my documents. These macros may be invoked as in this example.

\def\BKidxhyperSECT##1{\LinkHrefText{section.##1}{##1}}

That inserts the text parameter (an integer number of a section) into an A/HREF span. If I need to first output the A/HREF link info, then some text, and then after that add the </a>, then I invoke the LinkHrefPRE and LinkHrefPOST macros.

The above description is the relatively easy part, just getting completely plain TeX to create PDF hyperlinks. The only tricky thing is altering the \catcode to allow the hash character to be inserted in the a/href parameter.

I'll write up part (2) about how to get the blue link-boxes replaced by underlines in the next answer.


Part (2).
How to change pdfmark blue rectangle hyperlinks to underlines.

By running the strace command for the linux dvips -z command, I discovered that dvips -z used a global file /usr/lib/texmf/dvips/base/hps.pro by default to prepend to the output Postscript file to make the hyperlinks get used by ps2pdf. However, dvips -z also looked in the current working directory for a file hps.pro first. So all I had to do was copy the global hps.pro file to the local working directory and hack it to change the blue box for hyperlinks to a blue underline.

First I added a new Postscript dictionary /bsdict just below where the original hps.pro file creates a dictionary called /actiondict.

/actiondict 2 dict dup/Subtype/URI put def
/bsdict 4 dict dup /S /U put dup /W 1 put def

The first line above was in the file already. The second line is what I added.

Second, I added the text /BS bsdict in the places where indicated in the following text.

[....]
/Color exch /BS bsdict oldstyle{/LNK}{/Subtype/Link/ANN}ifelse gsave initmat
[....]
aload pop/Rect 4 1 roll/Border 3 1 roll/Color exch/BS bsdict/LNK gsave initmat
[....]

The added text must be exactly where indicated (unless you know exactly how to program Postscript so that you know how to put it somewhere else). The above commands have the effect of adding the desired parameters for creating blue underlines. I think that BS means box style, /S means "style", /U means underline, and very importantly, '/W' means "width" of line. If this is not set, then the default is zero, which is invisible.


PS. Just for the record, here is the modified version of the hps.pro file which I have been using successfully for the last year. I'm fairly sure there are no copyright issues with this.

%!
/HPSdict 20 dict dup begin/braindeaddistill 50 def/rfch{dup length 1 sub
1 exch getinterval}bind def/splituri{dup(#)search{exch pop}{()exch}
ifelse dup(file:)anchorsearch{pop exch pop 3 -1 roll pop false}{pop 3 -1
roll exch pop true}ifelse}bind def/lookuptarget{exch rfch dup
/TargetAnchors where{pop TargetAnchors dup 3 -1 roll known{exch get true
}{pop(target unknown:)print == false}ifelse}{pop pop
(target dictionary unknown\012)print false}ifelse}bind def/savecount 0
def/stackstopped{count counttomark sub/savecount exch store stopped
count savecount sub 1 sub dup 0 gt{{exch pop}repeat}{pop}ifelse}bind def
/tempstring 128 string def/targetvalidate{1 index dup length 127 gt exch
tempstring cvs dup(/)search{pop pop pop exch pop true exch}{pop}ifelse
token{pop length 0 ne}{true}ifelse or not}bind def/targetdump-hook where
{pop}{/targetdump-hook{dup mark exch gsave initmat setmatrix{{mark/Dest
4 2 roll targetvalidate{aload pop exch pop/Page 3 1 roll/View exch[exch
/FitH exch]/DEST pdfmark}{cleartomark}ifelse}forall}stackstopped pop
grestore}bind def}ifelse/baseurl{mark exch 1 dict dup 3 -1 roll/Base
exch put/URI exch/DOCVIEW{pdfmark}stackstopped pop}bind def
/externalhack systemdict/PDF known def/oldstyle true def/initmat matrix
currentmatrix def
/actiondict 2 dict dup/Subtype/URI put def
/bsdict 4 dict dup /S /U put dup /W 1 put def
/weblinkhandler{dup 3 1 roll mark 4 1 roll/Title 4 1 roll splituri 3 -1
roll dup length 0 gt{cvn/Dest exch 4 2 roll}{pop}ifelse{externalhack{
/HTTPFile exch}{actiondict dup 3 -1 roll/URI exch put/Action exch}
ifelse}{externalhack{/HTTPFile exch}{/File exch/Action/GoToR}ifelse}
ifelse counttomark 2 sub -1 roll aload pop/Rect 4 1 roll/Border 3 1 roll
/Color exch /BS bsdict oldstyle{/LNK}{/Subtype/Link/ANN}ifelse gsave initmat
setmatrix{pdfmark}stackstopped grestore}bind def/externalhandler where{
pop}{/externalhandler{2 copy{weblinkhandler}exec{/externalhack
externalhack not store 2 copy{weblinkhandler}exec{/externalhack
externalhack not store/oldstyle false store 2 copy{weblinkhandler}exec{
(WARNING: external refs disabled\012)print/externalhandler{pop pop}bind
store externalhandler}{pop pop}ifelse}{pop pop/externalhack externalhack
not store}ifelse}{pop pop/externalhandler{weblinkhandler pop}bind store}
ifelse}bind def}ifelse/pdfmnew{dup type/stringtype eq{externalhandler}{
exch dup rfch exch 3 -1 roll lookuptarget{mark 4 1 roll/Title 4 1 roll
aload pop exch pop/Page 3 1 roll/View exch[exch/FitH exch]5 -1 roll
aload pop/Rect 4 1 roll/Border 3 1 roll/Color exch/BS bsdict/LNK gsave initmat
setmatrix pdfmark grestore}{pop pop}ifelse}ifelse}bind def/pdfmold{dup
type/stringtype eq{externalhandler}{exch dup rfch exch 3 -1 roll
lookuptarget{mark 4 1 roll/Title 4 1 roll aload pop exch pop/Page 3 1
roll/View exch[exch/FitH exch]5 -1 roll aload pop pop 0 3 getinterval
/Rect 3 1 roll/Border exch/LNK gsave initmat setmatrix pdfmark grestore}
{pop pop}ifelse}ifelse}bind def/pdfm where{pop}{/pdfm
/currentdistillerparams where{pop currentdistillerparams dup
/CoreDistVersion known{/CoreDistVersion get}{0}ifelse dup
braindeaddistill le{(WARNING: switching to old pdfm because version =)
print ==/pdfmold}{pop/pdfmnew}ifelse load}{/pdfmark where{pop{dup type
/stringtype eq{externalhandler}{2 copy mark 3 1 roll{pdfmnew}
stackstopped{2 copy mark 3 1 roll{pdfmold}stackstopped{
(WARNING: pdfm disabled\012)print/pdfm{pop pop}store}{
(WARNING: new pdfm failed, switching to old pdfm\012)print/pdfm/pdfmold
load store}ifelse}{/pdfm/pdfmnew load store}ifelse pop pop}ifelse}}{{
pop pop}}ifelse}ifelse bind def}ifelse end def

If this file is put in the local directory when dvips -z is run, then it will be used instead of the global hps.pro file. It works on dvips version 5.98 anyway.