2 # ------------------------------------------------------------------------------
4 # Function: Format PDF Output from groff Markup
6 # Copyright (C) 2005-2014 Free Software Foundation, Inc.
7 # Written by Keith Marshall (keith.d.marshall@ntlworld.com)
9 # This file is part of groff.
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.
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
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/>.
24 # ------------------------------------------------------------------------------
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".
31 test -c $NULLDEV || NULLDEV="NUL"
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').
37 CMD=`exec 2>$NULLDEV; basename $0` || CMD=$0
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'.
45 # Usage: searchpath progname path
47 IFS=${PATH_SEPARATOR-":"} prog=':'
52 # try `progname' with all well known extensions
53 # (e.g. Win32 may require `progname.exe')
57 test -f "$try" && test -x "$try" && prog="$try" && break
59 test "$prog" = ":" || break
65 # If the system maps '/bin/sh' to some 'zsh' implementation,
66 # then we may need this hack, adapted from autoconf code.
68 test x${ZSH_VERSION+"set"} = x"set" && NULLCMD=":" \
69 && (emulate sh) >$NULLDEV 2>&1 && emulate sh
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.
75 CAT=`searchpath cat "$PATH"`
76 GREP=`searchpath grep "$PATH"`
77 SED=`searchpath sed "$PATH"`
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.
84 if test -n "$GROFF_BIN_DIR"
87 GROFF=`searchpath groff "$GROFF_BIN_DIR"`
89 elif test -n "$GROFF_BIN_PATH"
92 GROFF=`searchpath groff "$GROFF_BIN_PATH"`
96 GROFF=`searchpath groff "$PATH"`
99 # If one or more of these is missing, diagnose and bail out.
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'"
109 test $# -gt 1 && NO="s" IS="are" || NO='' IS="is"
112 test $# -gt 2 && NO="$NO $1,"
113 test $# -eq 2 && NO="$NO $1 and" && shift
114 test $# -lt 2 && NO="$NO $1"
119 *** FATAL INSTALLATION ERROR ***
121 The program$NO $IS required by '$CMD',
122 but cannot be found; '$CMD' is unable to continue.
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).
132 if test -n "${PDFROFF_POSTPROCESSOR_COMMAND}"
134 GROFF_GHOSTSCRIPT_INTERPRETER=`set command ${PDFROFF_POSTPROCESSOR_COMMAND};
138 # Set up temporary/intermediate file locations, with traps to
139 # clean them up on exit. Note that, for greater portability, we
140 # prefer to refer to events by number, rather than by symbolic
141 # names; thus, the EXIT event is trapped as event zero.
143 export TMPDIR GROFF_TMPDIR
144 TMPDIR=${GROFF_TMPDIR=${TMPDIR-${TMP-${TEMP-"."}}}}
145 if GROFF_TMPDIR=`exec 2>${NULLDEV}; mktemp -dt pdfroff-XXXXXXXXXX`
148 # We successfully created a private temporary directory,
149 # so to clean up, we may simply purge it.
151 trap "rm -rf ${GROFF_TMPDIR}" 0
155 # Creation of a private temporary directory was unsuccessful;
156 # fall back to user nominated directory, (using current directory
157 # as default), and schedule removal of only the temporary files.
159 GROFF_TMPDIR=${TMPDIR}
160 trap "rm -f ${GROFF_TMPDIR}/pdf$$.*" 0
163 # In the case of abnormal termination events, we force an exit
164 # (with status code '1'), leaving the normal exit trap to clean
165 # up the temporary files, as above. Note that we again prefer
166 # to refer to events by number, rather than by symbolic names;
167 # here we trap SIGHUP, SIGINT, SIGQUIT, SIGPIPE and SIGTERM.
169 trap "exit 1" 1 2 3 13 15
171 WRKFILE=${GROFF_TMPDIR}/pdf$$.tmp
173 REFCOPY=${GROFF_TMPDIR}/pdf$$.cmp
174 REFFILE=${GROFF_TMPDIR}/pdf$$.ref
177 TC_DATA=${GROFF_TMPDIR}/pdf$$.tc
178 BD_DATA=${GROFF_TMPDIR}/pdf$$.ps
180 # Initialise 'groff' format control settings,
181 # to discriminate table of contents and document body formatting passes.
183 TOC_FORMAT="-rPHASE=1"
184 BODY_FORMAT="-rPHASE=2"
187 help reference-dictionary no-reference-dictionary
188 stylesheet pdf-output no-pdf-output
189 version report-progress no-toc-relocation
190 emit-ps keep-temporary-files no-kill-null-pages
192 # Parse the command line, to identify 'pdfroff' specific options.
193 # Collect all other parameters into new argument and file lists,
194 # to be passed on to 'groff', enforcing the '-Tps' option.
196 DIFF="" STREAM="" INPUT_FILES=""
197 SHOW_VERSION="" GROFF_STYLE="$GROFF -Tps"
202 # Long options must be processed locally ...
206 # First identify, matching any abbreviation to its full form.
208 MATCH="" OPTNAME=`IFS==; set dummy $1; echo $2`
211 MATCH="$MATCH"`echo --$OPT | $GREP "^$OPTNAME"`
214 # For options in the form --option=value
215 # capture any specified value into $OPTARG.
217 OPTARG=`echo $1 | $SED -n s?"^${OPTNAME}="??p`
219 # Perform case specific processing for matched option ...
225 Usage: $CMD [-option ...] [--long-option ...] [file ...]
230 Display this usage summary, and exit.
234 Display a version identification message and exit.
237 Enable console messages, indicating the progress of the
238 PDF document formatting process.
241 Emit PostScript output instead of PDF; this may be useful
242 when the ultimate PDF output is to be generated by a more
243 specialised postprocessor, (e.g. gpresent), rather than
244 the default GhostScript PDF writer.
247 Write the PDF, (or PostScript), output stream to file
248 'name'; if this option is unspecified, standard output
249 is used for PDF, (or PostScript), output.
252 Suppress the generation of PDF, (or PostScript), output
253 entirely; use this with the --reference-dictionary option,
254 if processing a document stream to produce only a
255 reference dictionary.
257 --no-reference-dictionary
258 Suppress the generation of a '$CMD' reference dictionary
259 for the PDF document. Normally '$CMD' will create a
260 reference dictionary, at the start of document processing;
261 this option can accelerate processing, if it is known in
262 advance, that no reference dictionary is required.
264 --reference-dictionary=name
265 Save the document reference dictionary in file 'name'.
266 If 'name' already exists, when processing commences, it
267 will be used as the base case, from which the updated
268 dictionary will be derived. If this option is not used,
269 then the reference dictionary, created during the normal
270 execution of '$CMD', will be deleted on completion of
274 Use the file 'name' as a 'groff' style sheet, to control
275 the appearance of the document's front cover section. If
276 this option is not specified, then no special formatting
277 is applied, to create a front cover section.
280 Suppress the multiple pass 'groff' processing, which is
281 normally required to position the table of contents at the
282 start of a PDF document.
285 Suppress the 'null page' elimination filter, which is used
286 to remove the excess blank pages produced by the collation
287 algorithm used for 'toc-relocation'.
289 --keep-temporary-files
290 Suppress the normal clean up of temporary files, which is
291 scheduled when 'pdfroff' completes.
298 GROFF_STYLE="$GROFF_STYLE \"$1\""
299 SHOW_VERSION="GNU pdfroff (groff) version @VERSION@"
306 --keep-temporary-files)
311 PDFROFF_POSTPROCESSOR_COMMAND="$CAT"
319 PDF_OUTPUT="$NULLDEV"
322 --reference-dictionary)
326 --no-reference-dictionary)
327 AWK=":" DIFF=":" REFFILE="$NULLDEV" REFCOPY="$NULLDEV"
331 STYLESHEET="$OPTARG" CS_DATA=${GROFF_TMPDIR}/pdf$$.cs
335 TC_DATA="" TOC_FORMAT="" BODY_FORMAT=""
338 --no-kill-null-pages)
339 PDFROFF_COLLATE="$CAT" PDFROFF_KILL_NULL_PAGES=""
342 # any other non-null match must have matched more than one defined case,
343 # so report the ambiguity, and bail out.
346 echo >&2 "$CMD: ambiguous abbreviation in option '$1'"
350 # while no match at all simply represents an undefined case.
353 echo >&2 "$CMD: unknown option '$1'"
359 # A solitary hyphen, as an argument, means "stream STDIN through groff",
360 # while the "-i" option means "append STDIN stream to specified input files",
361 # so set up a mechanism to achieve this, for ALL 'groff' passes.
364 STREAM="$CAT ${GROFF_TMPDIR}/pdf$$.in |"
365 test "$1" = "-" && INPUT_FILES="$INPUT_FILES $1" \
366 || GROFF_STYLE="$GROFF_STYLE $1"
369 # Those standard options which expect an argument, but are specified with
370 # an intervening space, between flag and argument, must be reparsed, so we
371 # can trap invalid use of '-T dev', or missing input files.
375 shift; set reparse "$OPTNAME$@"
378 # Among standard options, '-Tdev' is treated as a special case.
379 # '-Tps' is automatically enforced, so if specified, is silently ignored.
383 # No other '-Tdev' option is permitted.
385 -T*) echo >&2 "$CMD: option '$1' is incompatible with PDF output"
389 # '-h' and '-v' options redirect to their equivalent long forms ...
391 -h*) set redirect --help
394 -v*) shift; set redirect --version "$@"
397 # All other standard options are simply passed through to 'groff',
398 # with no validation beforehand.
400 -*) GROFF_STYLE="$GROFF_STYLE \"$1\""
403 # All non-option arguments are considered as possible input file names,
404 # and are passed on to 'groff', unaltered.
406 *) INPUT_FILES="$INPUT_FILES \"$1\""
412 # If the '-v' or '--version' option was specified,
413 # then we simply emulate the behaviour of 'groff', with this option,
416 if test -n "$SHOW_VERSION"
418 echo >&2 "$SHOW_VERSION"
419 echo >&2; eval $GROFF_STYLE $INPUT_FILES
423 # Establish how to invoke 'echo', suppressing the terminating newline.
424 # (Adapted from 'autoconf' code, as found in 'configure' scripts).
426 case `echo "testing\c"; echo 1,2,3`,`echo -n testing; echo 1,2,3` in
427 *c*,*-n*) n='' c='' ;;
432 # If STDIN is specified among the input files,
433 # or if no input files are specified, then we need to capture STDIN,
434 # so we can replay it into each 'groff' processing pass.
436 test -z "$INPUT_FILES" && STREAM="$CAT ${GROFF_TMPDIR}/pdf$$.in |"
437 test -n "$STREAM" && $CAT > ${GROFF_TMPDIR}/pdf$$.in
439 # Unless reference resolution is explicitly suppressed,
440 # we initiate it by touching the cross reference dictionary file,
441 # and initialise the comparator, to kickstart the reference resolver loop.
447 echo kickstart > $REFCOPY
448 test x${SHOW_PROGRESS+"set"} = x"set" && SAY=echo
450 # In order to correctly resolve 'pdfmark' references,
451 # we need to have both the 'awk' and 'diff' programs available.
454 if test -n "$GROFF_AWK_INTERPRETER"
456 AWK="$GROFF_AWK_INTERPRETER"
457 test -f "$AWK" && test -x "$AWK" || AWK=":"
459 for prog in @GROFF_AWK_INTERPRETERS@
461 AWK=`searchpath $prog "$PATH"`
462 test "$AWK" = ":" || break
465 DIFF=`searchpath diff "$PATH"`
466 test "$AWK" = ":" && echo >&2 "$NOPROG 'awk' in PATH" && NO="$NO 'awk'"
467 test "$DIFF" = ":" && echo >&2 "$NOPROG 'diff' in PATH" && NO="$NO 'diff'"
471 SAY=":" AWK=":" DIFF=":"
472 test $# -gt 1 && NO="s $1 and $2 are" || NO=" $1 is"
477 The program$NO required, but cannot be found;
478 consequently, '$CMD' is unable to resolve 'pdfmark' references.
480 Document processing will continue, but no 'pdfmark' reference dictionary
481 will be compiled; if any 'pdfmark' reference appears in the resulting PDF
482 document, the formatting may not be correct.
488 # Run the multi-pass 'pdfmark' reference resolver loop ...
490 $SAY >&2 $n Resolving references ..$c
491 until $DIFF $REFCOPY $REFFILE 1>$NULLDEV 2>&1
494 # until all references are resolved, to yield consistent values
495 # in each of two consecutive passes, or until it seems that no consistent
496 # resolution is achievable.
499 PASS_INDICATOR="${PASS_INDICATOR}."
500 if test "$PASS_INDICATOR" = "...."
503 # More than three passes required indicates a probable inconsistency
504 # in the source document; diagnose, and bail out.
508 $CMD: unable to resolve references consistently after three passes
509 $CMD: the source document may exhibit instability about the reference(s) ...
512 # Report the unresolved references, as a diff between the two pass files,
513 # preferring 'unified' or 'context' diffs, when available
516 $DIFF -c0 $NULLDEV $NULLDEV 1>$NULLDEV 2>&1 && DIFFOPT='-c0'
517 $DIFF -u0 $NULLDEV $NULLDEV 1>$NULLDEV 2>&1 && DIFFOPT='-u0'
518 $DIFF >&2 $DIFFOPT $REFCOPY $REFFILE
522 # Replace the comparison file copy from any previous pass,
523 # with the most recently updated copy of the reference dictionary.
524 # (Some versions of 'mv' may not support overwriting of an existing file,
525 # so remove the old comparison file first).
530 # Run 'groff' and 'awk', to identify reference marks in the document source,
531 # filtering them into the reference dictionary; discard incomplete 'groff' output
534 eval $STREAM $GROFF_STYLE -Z 1>$NULLDEV 2>$WRKFILE $REFCOPY $INPUT_FILES
535 $AWK '/^gropdf-info:href/ {$1 = ".pdfhref D -N"; print}' $WRKFILE > $REFFILE
540 # We MUST have resolved all 'pdfmark' references, such that the content of the
541 # updated reference dictionary file EXACTLY matches the last saved copy.
543 # If PDF output has been suppressed, then there is nothing more to do.
545 test "$PDF_OUTPUT" = "$NULLDEV" && exit 0
547 # We are now ready to start preparing the intermediate PostScript files,
548 # from which the PDF output will be compiled -- but before proceeding further ...
549 # let's make sure we have a GhostScript interpreter to convert them!
551 if test -n "$GROFF_GHOSTSCRIPT_INTERPRETER"
553 GS="$GROFF_GHOSTSCRIPT_INTERPRETER"
554 test -f "$GS" && test -x "$GS" || GS=":"
556 for prog in @GROFF_GHOSTSCRIPT_INTERPRETERS@
558 GS=`searchpath $prog "$PATH"`
559 test "$GS" = ":" || break
563 # If we could not find a GhostScript interpreter, then we can do no more.
567 echo >&2 "$CMD: installation problem: cannot find GhostScript interpreter"
570 *** FATAL INSTALLATION ERROR ***
572 '$CMD' requires a GhostScript interpreter to convert PostScript to PDF.
573 Since you do not appear to have one installed, '$CMD' connot continue.
579 # We now extend the local copy of the reference dictionary file,
580 # to create a full 'pdfmark' reference map for the document ...
582 $AWK '/^grohtml-info/ {print ".pdfhref Z", $2, $3, $4}' $WRKFILE >> $REFCOPY
584 # ... appending a dummy map reference, to ensure that at least
585 # one such is always present; (this is required, to suppress any
586 # further intermediate output to stderr during the "press-ready"
587 # runs of groff, for PDF output file production).
589 echo ".pdfhref Z 0 0 0" >> $REFCOPY
591 # Evaluate any processing options which may have been specified
592 # as a result of parsing the document source ...
594 eval `$SED -n '/^ *pdfroff-option:set */s///p' $WRKFILE`
596 # ... (which is currently supported to enable "toc-relocation",
597 # only when the document actually relies on it, and if it is not
598 # explicitly disabled from the command line) ...
600 if test x${toc_relocation-"auto"} != xenabled
603 # ... thus we reproduce the effect of the "--no-toc-relocation"
604 # option, when no enabling request is detected in the document
607 TC_DATA="" TOC_FORMAT="" BODY_FORMAT=""
610 # Re-enable progress reporting, if necessary ...
611 # (Missing 'awk' or 'diff' may have disabled it, to avoid display
612 # of spurious messages associated with reference resolution).
614 test x${SHOW_PROGRESS+"set"} = x"set" && SAY=echo
616 # If a document cover style sheet is specified ...
617 # then we run a special formatting pass, to create a cover section file.
619 if test -n "$STYLESHEET"
622 CS_MACRO=${CS_MACRO-"CS"} CE_MACRO=${CE_MACRO-"CE"}
623 $SAY >&2 $n "Formatting document ... front cover section ..$c"
624 CS_FILTER="$STREAM $SED -n '/${DOT}${CS_MACRO}/,/${DOT}${CE_MACRO}/p'"
625 eval $CS_FILTER $INPUT_FILES | eval $GROFF_STYLE $STYLESHEET - > $CS_DATA
629 # If table of contents relocation is to be performed (it is, by default),
630 # then we run an extra 'groff' pass, to format a TOC intermediate file.
632 if test -n "$TC_DATA"
634 $SAY >&2 $n "Formatting document ... table of contents ..$c"
635 eval $STREAM $GROFF_STYLE $TOC_FORMAT $REFCOPY $INPUT_FILES > $TC_DATA
639 # In all cases, a final 'groff' pass is required, to format the document body.
641 $SAY >&2 $n "Formatting document ... body section ..$c"
642 eval $STREAM $GROFF_STYLE $BODY_FORMAT $REFCOPY $INPUT_FILES > $BD_DATA
646 # Invoke GhostScript as a PDF writer, to bind all of the generated
647 # PostScript intermediate files into a single PDF output file.
649 $SAY >&2 $n "Writing PDF output ..$c"
650 if test -z "$PDFROFF_POSTPROCESSOR_COMMAND"
652 PDFROFF_POSTPROCESSOR_COMMAND="$GS -dQUIET -dBATCH -dNOPAUSE -dSAFER
653 -sDEVICE=pdfwrite -sOutputFile="${PDF_OUTPUT-"-"}
655 elif test -n "$PDF_OUTPUT"
660 # (This 'sed' script is a hack, to eliminate redundant blank pages).
662 ${PDFROFF_COLLATE-"$SED"} ${PDFROFF_KILL_NULL_PAGES-'
665 /%%BeginPageSetup/b again
669 /%%EndPageSetup/b finish
677 /^%%Page:.*\n0 Cg EP$/d
678 '} $TC_DATA $BD_DATA | $PDFROFF_POSTPROCESSOR_COMMAND $CS_DATA -
681 # ------------------------------------------------------------------------------
682 # $RCSfile$ $Revision$: end of file