groff: update vendor branch to v1.20.1
[dragonfly.git] / contrib / groff / contrib / pdfmark / pdfroff.sh
1 #! /bin/sh
2 # ------------------------------------------------------------------------------
3 #
4 # Function: Format PDF Output from groff Markup
5 #
6 # Copyright (C) 2005, 2006, 2009 Free Software Foundation, Inc.
7 # Written by Keith Marshall (keith.d.marshall@ntlworld.com)
8
9 # This file is part of groff.
10
11 # groff is free software; you can redistribute it and/or modify it under
12 # the terms of the GNU General Public License as published by the Free
13 # Software Foundation, either version 3 of the License, or
14 # (at your option) any later version.
15
16 # groff is distributed in the hope that it will be useful, but WITHOUT ANY
17 # WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
19 # for more details.
20
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #
24 # ------------------------------------------------------------------------------
25 #
26 # Set up an identifier for the NULL device.
27 # In most cases "/dev/null" will be correct, but some shells on
28 # MS-DOS/MS-Windows systems may require us to use "NUL".
29 #
30   NULLDEV="/dev/null"
31   test -c $NULLDEV || NULLDEV="NUL"
32 #
33 # Set up the command name to use in diagnostic messages.
34 # (We can't assume we have 'basename', so use the full path if required.
35 #  Also use the 'exec 2>...' workaround for a bug in Cygwin's 'ash').
36 #
37   CMD=`exec 2>$NULLDEV; basename $0` || CMD=$0
38 #
39 # To ensure that prerequisite helper programs are available, and are
40 # executable, a [fairly] portable method of detecting such programs is
41 # provided by function `searchpath'.
42 #
43   searchpath(){
44   #
45   # Usage:  searchpath progname path
46   #
47     IFS=${PATH_SEPARATOR-":"} prog=':'
48     for dir in $2
49     do
50       for ext in '' '.exe'
51       #
52       # try `progname' with all well known extensions
53       # (e.g. Win32 may require `progname.exe')
54       #
55       do
56         try="$dir/$1$ext"
57         test -f "$try" && test -x "$try" && prog="$try" && break
58       done
59       test "$prog" = ":" || break
60     done
61     echo "$prog"
62   }
63 # @PATH_SEARCH_SETUP@
64 #
65 # If the system maps '/bin/sh' to some 'zsh' implementation,
66 # then we may need this hack, adapted from autoconf code.
67 #
68   test x${ZSH_VERSION+"set"} = x"set" && NULLCMD=":" \
69     && (emulate sh) >$NULLDEV 2>&1 && emulate sh
70 #
71 # We need both 'grep' and 'sed' programs, to parse script options,
72 # and we also need 'cat', to display help and some error messages,
73 # so ensure they are all installed, before we continue.
74 #
75   CAT=`searchpath cat "$PATH"`
76   GREP=`searchpath grep "$PATH"`
77   SED=`searchpath sed "$PATH"`
78 #
79 # Another fundamental requirement is the 'groff' program itself;
80 # we MUST use a 'groff' program located in 'GROFF_BIN_DIR', if this
81 # is specified; if not, we will search 'GROFF_BIN_PATH', only falling
82 # back to a 'PATH' search, if neither of these is specified.
83 #
84   if test -n "$GROFF_BIN_DIR"
85   then
86     GPATH=GROFF_BIN_DIR
87     GROFF=`searchpath groff "$GROFF_BIN_DIR"`
88 #
89   elif test -n "$GROFF_BIN_PATH"
90   then
91     GPATH=GROFF_BIN_PATH
92     GROFF=`searchpath groff "$GROFF_BIN_PATH"`
93 #
94   else
95     GPATH=PATH
96     GROFF=`searchpath groff "$PATH"`
97   fi
98 #
99 # If one or more of these is missing, diagnose and bail out.
100 #
101   NO='' NOPROG="$CMD: installation problem: cannot find program"
102   test "$CAT" = ":" && echo >&2 "$NOPROG 'cat' in PATH" && NO="$NO 'cat'"
103   test "$GREP" = ":" && echo >&2 "$NOPROG 'grep' in PATH" && NO="$NO 'grep'"
104   test "$GROFF" = ":" && echo >&2 "$NOPROG 'groff' in $GPATH" && NO="$NO 'groff'"
105   test "$SED" = ":" && echo >&2 "$NOPROG 'sed' in PATH" && NO="$NO 'sed'"
106   if test -n "$NO"
107   then
108     set $NO
109     test $# -gt 1 && NO="s" IS="are" || NO='' IS="is"
110     while test $# -gt 0
111     do
112       test $# -gt 2 && NO="$NO $1,"
113       test $# -eq 2 && NO="$NO $1 and" && shift
114       test $# -lt 2 && NO="$NO $1"
115       shift
116     done
117     $CAT >&2 <<-ETX
118
119         *** FATAL INSTALLATION ERROR ***
120
121         The program$NO $IS required by '$CMD',
122         but cannot be found; '$CMD' is unable to continue.
123
124         ETX
125     exit 1
126   fi
127 #
128 # Identify the postprocessor command, for writing PDF output.
129 # (May be forced, by defining PDFROFF_POSTPROCESSOR_COMMAND in the environment;
130 #  if this is not set, leave blank to use the built in default).
131 #
132   if test -n "${PDFROFF_POSTPROCESSOR_COMMAND}"
133   then
134     GROFF_GHOSTSCRIPT_INTERPRETER=`set command ${PDFROFF_POSTPROCESSOR_COMMAND};
135       echo $2`
136   fi
137 #
138 # Set up temporary/intermediate file locations.
139 #
140   WRKFILE=${GROFF_TMPDIR=${TMPDIR-${TMP-${TEMP-"."}}}}/pdf$$.tmp
141 #
142   REFCOPY=${GROFF_TMPDIR}/pdf$$.cmp
143   REFFILE=${GROFF_TMPDIR}/pdf$$.ref
144 #
145   CS_DATA=""
146   TC_DATA=${GROFF_TMPDIR}/pdf$$.tc
147   BD_DATA=${GROFF_TMPDIR}/pdf$$.ps
148 #
149 # Set a trap, to delete temporary files on exit.
150 # (FIXME: may want to include other signals, in released version).
151 #
152   trap "rm -f ${GROFF_TMPDIR}/pdf$$.*" 0
153 #
154 # Initialise 'groff' format control settings,
155 # to discriminate table of contents and document body formatting passes.
156 #
157   TOC_FORMAT="-rPHASE=1"
158   BODY_FORMAT="-rPHASE=2"
159 #
160   LONGOPTS="
161     help        reference-dictionary    no-reference-dictionary
162     stylesheet  pdf-output              no-pdf-output
163     version     report-progress         no-toc-relocation
164     emit-ps     keep-temporary-files    no-kill-null-pages
165     "
166 # Parse the command line, to identify 'pdfroff' specific options.
167 # Collect all other parameters into new argument and file lists,
168 # to be passed on to 'groff', enforcing the '-Tps' option.
169 #
170   DIFF="" STREAM="" INPUT_FILES=""
171   SHOW_VERSION="" GROFF_STYLE="$GROFF -Tps"
172   while test $# -gt 0
173   do
174     case "$1" in
175 #
176 #     Long options must be processed locally ...
177 #
178       --*)
179 #
180 #          First identify, matching any abbreviation to its full form.
181 #
182            MATCH="" OPTNAME=`IFS==; set dummy $1; echo $2`
183            for OPT in $LONGOPTS
184            do
185              MATCH="$MATCH"`echo --$OPT | $GREP "^$OPTNAME"`
186            done
187 #
188 #          For options in the form --option=value
189 #          capture any specified value into $OPTARG.
190 #
191            OPTARG=`echo $1 | $SED -n s?"^${OPTNAME}="??p`
192 #
193 #          Perform case specific processing for matched option ...
194 #
195            case "$MATCH" in
196
197              --help)
198                $CAT <<-ETX
199                 Usage: $CMD [-option ...] [--long-option ...] [file ...]
200
201                 Options:
202                   -h
203                   --help
204                         Display this usage summary, and exit.
205
206                   -v
207                   --version
208                         Display a version identification message and exit.
209
210                   --report-progress
211                         Enable console messages, indicating the progress of the
212                         PDF document formatting process.
213
214                   --emit-ps
215                         Emit PostScript output instead of PDF; this may be useful
216                         when the ultimate PDF output is to be generated by a more
217                         specialised postprocessor, (e.g. gpresent), rather than
218                         the default GhostScript PDF writer.
219
220                   --pdf-output=name
221                         Write the PDF, (or PostScript), output stream to file
222                         'name'; if this option is unspecified, standard output
223                         is used for PDF, (or PostScript), output.
224
225                   --no-pdf-output
226                         Suppress the generation of PDF, (or PostScript), output
227                         entirely; use this with the --reference-dictionary option,
228                         if processing a document stream to produce only a
229                         reference dictionary.
230
231                   --no-reference-dictionary
232                         Suppress the generation of a '$CMD' reference dictionary
233                         for the PDF document.  Normally '$CMD' will create a
234                         reference dictionary, at the start of document processing;
235                         this option can accelerate processing, if it is known in
236                         advance, that no reference dictionary is required.
237
238                   --reference-dictionary=name
239                         Save the document reference dictionary in file 'name'.
240                         If 'name' already exists, when processing commences, it
241                         will be used as the base case, from which the updated
242                         dictionary will be derived.  If this option is not used,
243                         then the reference dictionary, created during the normal
244                         execution of '$CMD', will be deleted on completion of
245                         document processing.
246
247                   --stylesheet=name
248                         Use the file 'name' as a 'groff' style sheet, to control
249                         the appearance of the document's front cover section.  If
250                         this option is not specified, then no special formatting
251                         is applied, to create a front cover section.
252
253                   --no-toc-relocation
254                         Suppress the multiple pass 'groff' processing, which is
255                         normally required to position the table of contents at the
256                         start of a PDF document.
257
258                   --no-kill-null-pages
259                         Suppress the 'null page' elimination filter, which is used
260                         to remove the excess blank pages produced by the collation
261                         algorithm used for 'toc-relocation'.
262
263                   --keep-temporary-files
264                         Suppress the normal clean up of temporary files, which is
265                         scheduled when 'pdfroff' completes.
266
267                 ETX
268                exit 0
269                ;;
270
271              --version)
272                GROFF_STYLE="$GROFF_STYLE \"$1\""
273                SHOW_VERSION="GNU pdfroff (groff) version @VERSION@"
274                ;;
275
276              --report-progress)
277                SHOW_PROGRESS=echo
278                ;;
279
280              --keep-temporary-files)
281                trap "" 0
282                ;;
283
284              --emit-ps)
285                PDFROFF_POSTPROCESSOR_COMMAND="$CAT"
286                ;;
287
288              --pdf-output)
289                PDF_OUTPUT="$OPTARG"
290                ;;
291
292              --no-pdf-output)
293                PDF_OUTPUT="$NULLDEV"
294                ;;
295
296              --reference-dictionary)
297                REFFILE="$OPTARG"
298                ;;
299
300              --no-reference-dictionary)
301                AWK=":" DIFF=":" REFFILE="$NULLDEV" REFCOPY="$NULLDEV"
302                ;;
303
304              --stylesheet)
305                STYLESHEET="$OPTARG" CS_DATA=${GROFF_TMPDIR}/pdf$$.cs
306                ;;
307
308              --no-toc-relocation)
309                TC_DATA="" TOC_FORMAT="" BODY_FORMAT=""
310                ;;
311
312              --no-kill-null-pages)
313                PDFROFF_COLLATE="$CAT" PDFROFF_KILL_NULL_PAGES=""
314                ;;
315 #
316 #          any other non-null match must have matched more than one defined case,
317 #          so report the ambiguity, and bail out.
318 #
319              --*)
320                echo >&2 "$CMD: ambiguous abbreviation in option '$1'"
321                exit 1
322                ;;
323 #
324 #          while no match at all simply represents an undefined case.
325 #
326              *)
327                echo >&2 "$CMD: unknown option '$1'"
328                exit 1
329                ;;
330            esac
331            ;;
332 #
333 #     A solitary hyphen, as an argument, means "stream STDIN through groff",
334 #     while the "-i" option means "append STDIN stream to specified input files",
335 #     so set up a mechanism to achieve this, for ALL 'groff' passes.
336 #
337       - | -i*)
338            STREAM="$CAT ${GROFF_TMPDIR}/pdf$$.in |"
339            test "$1" = "-" && INPUT_FILES="$INPUT_FILES $1" \
340              || GROFF_STYLE="$GROFF_STYLE $1" 
341            ;;
342 #
343 #     Those standard options which expect an argument, but are specified with
344 #     an intervening space, between flag and argument, must be reparsed, so we
345 #     can trap invalid use of '-T dev', or missing input files.
346 #
347       -[dfFILmMnoPrTwW])
348            OPTNAME="$1"
349            shift; set reparse "$OPTNAME$@"
350            ;;
351 #
352 #     Among standard options, '-Tdev' is treated as a special case.
353 #     '-Tps' is automatically enforced, so if specified, is silently ignored.
354 #
355       -Tps) ;;
356 #
357 #     No other '-Tdev' option is permitted.
358 #
359       -T*) echo >&2 "$CMD: option '$1' is incompatible with PDF output"
360            exit 1
361            ;;
362 #
363 #     '-h' and '-v' options redirect to their equivalent long forms ...
364 #
365       -h*) set redirect --help
366            ;;
367 #
368       -v*) shift; set redirect --version "$@"
369            ;;
370 #
371 #     All other standard options are simply passed through to 'groff',
372 #     with no validation beforehand.
373 #
374       -*)  GROFF_STYLE="$GROFF_STYLE \"$1\""
375            ;;
376 #
377 #     All non-option arguments are considered as possible input file names,
378 #     and are passed on to 'groff', unaltered.
379 #
380       *)   INPUT_FILES="$INPUT_FILES \"$1\""
381            ;;
382     esac
383     shift
384   done
385 #
386 # If the '-v' or '--version' option was specified,
387 # then we simply emulate the behaviour of 'groff', with this option,
388 # and quit.
389 #
390   if test -n "$SHOW_VERSION"
391   then
392     echo >&2 "$SHOW_VERSION"
393     echo >&2; eval $GROFF_STYLE $INPUT_FILES
394     exit $?
395   fi
396 #
397 # Establish how to invoke 'echo', suppressing the terminating newline.
398 # (Adapted from 'autoconf' code, as found in 'configure' scripts).
399 #
400   case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
401     *c*,*-n*)  n=''   c=''   ;;
402     *c*)       n='-n' c=''   ;;
403     *)         n=''   c='\c' ;;
404   esac
405 #
406 # If STDIN is specified among the input files,
407 # or if no input files are specified, then we need to capture STDIN,
408 # so we can replay it into each 'groff' processing pass.
409 #
410   test -z "$INPUT_FILES" && STREAM="$CAT ${GROFF_TMPDIR}/pdf$$.in |"
411   test -n "$STREAM" && $CAT > ${GROFF_TMPDIR}/pdf$$.in
412 #
413 # Unless reference resolution is explicitly suppressed,
414 # we initiate it by touching the cross reference dictionary file,
415 # and initialise the comparator, to kickstart the reference resolver loop.
416 #
417   SAY=":"
418   if test -z "$DIFF"
419   then
420     >> $REFFILE
421     echo kickstart > $REFCOPY
422     test x${SHOW_PROGRESS+"set"} = x"set" && SAY=echo
423 #
424 #   In order to correctly resolve 'pdfmark' references,
425 #   we need to have both the 'awk' and 'diff' programs available.
426 #
427     NO=''
428     if test -n "$GROFF_AWK_INTERPRETER"
429     then
430       AWK="$GROFF_AWK_INTERPRETER"
431       test -f "$AWK" && test -x "$AWK" || AWK=":"
432     else
433       for prog in @GROFF_AWK_INTERPRETERS@
434       do
435         AWK=`searchpath $prog "$PATH"`
436         test "$AWK" = ":" || break
437       done
438     fi
439     DIFF=`searchpath diff "$PATH"`
440     test "$AWK" = ":" && echo >&2 "$NOPROG 'awk' in PATH" && NO="$NO 'awk'"
441     test "$DIFF" = ":" && echo >&2 "$NOPROG 'diff' in PATH" && NO="$NO 'diff'"
442     if test -n "$NO"
443     then
444       set $NO
445       SAY=":" AWK=":" DIFF=":"
446       test $# -gt 1 && NO="s $1 and $2 are" || NO=" $1 is"
447       $CAT >&2 <<-ETX
448
449         *** WARNING ***
450
451         The program$NO required, but cannot be found;
452         consequently, '$CMD' is unable to resolve 'pdfmark' references.
453
454         Document processing will continue, but no 'pdfmark' reference dictionary
455         will be compiled; if any 'pdfmark' reference appears in the resulting PDF
456         document, the formatting may not be correct.
457
458         ETX
459     fi
460   fi
461 #
462 # Run the multi-pass 'pdfmark' reference resolver loop ...
463 #
464   $SAY >&2 $n Resolving references ..$c
465   until $DIFF $REFCOPY $REFFILE 1>$NULLDEV 2>&1
466   do
467 #
468 #   until all references are resolved, to yield consistent values
469 #   in each of two consecutive passes, or until it seems that no consistent
470 #   resolution is achievable.
471 #
472     $SAY >&2 $n .$c
473     PASS_INDICATOR="${PASS_INDICATOR}."
474     if test "$PASS_INDICATOR" = "...."
475     then
476 #
477 #     More than three passes required indicates a probable inconsistency
478 #     in the source document; diagnose, and bail out.
479 #
480       $SAY >&2 " failed"
481       $CAT >&2 <<-ETX
482         $CMD: unable to resolve references consistently after three passes
483         $CMD: the source document may exhibit instability about the reference(s) ...
484         ETX
485 #
486 #     Report the unresolved references, as a diff between the two pass files,
487 #     preferring 'unified' or 'context' diffs, when available
488 #
489       DIFFOPT=''
490       $DIFF -c0 $NULLDEV $NULLDEV 1>$NULLDEV 2>&1 && DIFFOPT='-c0'
491       $DIFF -u0 $NULLDEV $NULLDEV 1>$NULLDEV 2>&1 && DIFFOPT='-u0'
492       $DIFF >&2 $DIFFOPT $REFCOPY $REFFILE
493       exit 1
494     fi
495 #
496 #   Replace the comparison file copy from any previous pass,
497 #   with the most recently updated copy of the reference dictionary.
498 #   (Some versions of 'mv' may not support overwriting of an existing file,
499 #    so remove the old comparison file first).
500 #
501     rm -f $REFCOPY
502     mv $REFFILE $REFCOPY
503 #
504 #   Run 'groff' and 'awk', to identify reference marks in the document source,
505 #   filtering them into the reference dictionary; discard incomplete 'groff' output
506 #   at this stage.
507 #
508     eval $STREAM $GROFF_STYLE -Z 1>$NULLDEV 2>$WRKFILE $REFCOPY $INPUT_FILES
509     $AWK '/^gropdf-info:href/ {$1 = ".pdfhref D -N"; print}' $WRKFILE > $REFFILE
510   done
511   $SAY >&2 " done"
512 #
513 # To get to here ...
514 # We MUST have resolved all 'pdfmark' references, such that the content of the
515 # updated reference dictionary file EXACTLY matches the last saved copy.
516 #
517 # If PDF output has been suppressed, then there is nothing more to do.
518 #
519   test "$PDF_OUTPUT" = "$NULLDEV" && exit 0
520 #
521 # We are now ready to start preparing the intermediate PostScript files,
522 # from which the PDF output will be compiled -- but before proceding further ...
523 # let's make sure we have a GhostScript interpreter to convert them!
524 #
525   if test -n "$GROFF_GHOSTSCRIPT_INTERPRETER"
526   then
527     GS="$GROFF_GHOSTSCRIPT_INTERPRETER"
528     test -f "$GS" && test -x "$GS" || GS=":"
529   else
530     for prog in @GROFF_GHOSTSCRIPT_INTERPRETERS@
531     do
532       GS=`searchpath $prog "$PATH"`
533       test "$GS" = ":" || break
534     done
535   fi
536 #
537 # If we could not find a GhostScript interpreter, then we can do no more.
538 #
539   if test "$GS" = ":"
540   then
541     echo >&2 "$CMD: installation problem: cannot find GhostScript interpreter"
542     $CAT >&2 <<-ETX
543
544         *** FATAL INSTALLATION ERROR ***
545
546         '$CMD' requires a GhostScript interpreter to convert PostScript to PDF.
547         Since you do not appear to have one installed, '$CMD' connot continue.
548
549         ETX
550     exit 1
551   fi
552 #
553 # We now extend the local copy of the reference dictionary file,
554 # to create a full 'pdfmark' reference map for the document ...
555 #
556   $AWK '/^grohtml-info/ {print ".pdfhref Z", $2, $3, $4}' $WRKFILE >> $REFCOPY
557 #
558 # Re-enable progress reporting, if necessary ...
559 # (Missing 'awk' or 'diff' may have disabled it, to avoid display
560 #  of spurious messages associated with reference resolution).
561 #
562   test x${SHOW_PROGRESS+"set"} = x"set" && SAY=echo
563 #
564 # If a document cover style sheet is specified ...
565 # then we run a special formatting pass, to create a cover section file.
566 #
567   if test -n "$STYLESHEET"
568   then
569     DOT='^\.[   ]*'
570     CS_MACRO=${CS_MACRO-"CS"} CE_MACRO=${CE_MACRO-"CE"}
571     $SAY >&2 $n "Formatting document ... front cover section ..$c"
572     CS_FILTER="$STREAM $SED -n '/${DOT}${CS_MACRO}/,/${DOT}${CE_MACRO}/p'"
573     eval $CS_FILTER $INPUT_FILES | eval $GROFF_STYLE $STYLESHEET - > $CS_DATA
574     $SAY >&2 ". done"
575   fi
576 #
577 # If table of contents relocation is to be performed (it is, by default),
578 # then we run an extra 'groff' pass, to format a TOC intermediate file.
579 #
580   if test -n "$TC_DATA"
581   then
582     $SAY >&2 $n "Formatting document ... table of contents ..$c"
583     eval $STREAM $GROFF_STYLE $TOC_FORMAT $REFCOPY $INPUT_FILES > $TC_DATA
584     $SAY >&2 ". done"
585   fi
586 #
587 # In all cases, a final 'groff' pass is required, to format the document body.
588 #
589   $SAY >&2 $n "Formatting document ... body section ..$c"
590   eval $STREAM $GROFF_STYLE $BODY_FORMAT $REFCOPY $INPUT_FILES > $BD_DATA
591   $SAY >&2 ". done"
592 #
593 # Finally ...
594 # Invoke GhostScript as a PDF writer, to bind all of the generated
595 # PostScript intermediate files into a single PDF output file.
596 #
597   $SAY >&2 $n "Writing PDF output ..$c"
598   if test -z "$PDFROFF_POSTPROCESSOR_COMMAND"
599   then
600     PDFROFF_POSTPROCESSOR_COMMAND="$GS -dQUIET -dBATCH -dNOPAUSE
601       -sDEVICE=pdfwrite -sOutputFile="${PDF_OUTPUT-"-"}
602
603   elif test -n "$PDF_OUTPUT"
604   then
605     exec > $PDF_OUTPUT
606   fi
607 #
608 # (This 'sed' script is a hack, to eliminate redundant blank pages).
609 #
610   ${PDFROFF_COLLATE-"$SED"} ${PDFROFF_KILL_NULL_PAGES-'
611       /%%Page:/{
612         N
613         /%%BeginPageSetup/b again
614       }
615       b
616     :again
617       /%%EndPageSetup/b finish
618       /%%Page:/{
619         N
620         b again
621       }
622       b
623     :finish
624       N
625       /^%%Page:.*\n0 Cg EP$/d
626     '} $TC_DATA $BD_DATA | $PDFROFF_POSTPROCESSOR_COMMAND $CS_DATA -
627   $SAY >&2 ". done"
628 #
629 # ------------------------------------------------------------------------------
630 # $RCSfile: pdfroff.sh,v $ $Revision: 1.15 $: end of file