summaryrefslogtreecommitdiffstats
path: root/src/roff
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 19:44:05 +0000
commitd318611dd6f23fcfedd50e9b9e24620b102ba96a (patch)
tree8b9eef82ca40fdd5a8deeabf07572074c236095d /src/roff
parentInitial commit. (diff)
downloadgroff-upstream/1.23.0.tar.xz
groff-upstream/1.23.0.zip
Adding upstream version 1.23.0.upstream/1.23.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/roff')
-rw-r--r--src/roff/groff/groff.1.man2403
-rw-r--r--src/roff/groff/groff.am87
-rw-r--r--src/roff/groff/groff.cpp866
-rw-r--r--src/roff/groff/pipeline.c589
-rw-r--r--src/roff/groff/pipeline.h30
-rwxr-xr-xsrc/roff/groff/tests/ab_works.sh46
-rwxr-xr-xsrc/roff/groff/tests/adjustment_works.sh92
-rw-r--r--src/roff/groff/tests/artifacts/HONEYPOT15
-rw-r--r--src/roff/groff/tests/artifacts/devascii/README1
-rwxr-xr-xsrc/roff/groff/tests/break_zero-length_output_line_sanely.sh43
-rwxr-xr-xsrc/roff/groff/tests/device_control_escapes_express_basic_latin.sh60
-rwxr-xr-xsrc/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh29
-rwxr-xr-xsrc/roff/groff/tests/dot-cp_register_works.sh48
-rwxr-xr-xsrc/roff/groff/tests/dot-nm_register_works.sh40
-rwxr-xr-xsrc/roff/groff/tests/dot-nn_register_works.sh77
-rwxr-xr-xsrc/roff/groff/tests/evc_produces_no_output_if_invalid.sh25
-rwxr-xr-xsrc/roff/groff/tests/fp_should_not_traverse_directories.sh63
-rwxr-xr-xsrc/roff/groff/tests/handle_special_input_code_points.sh47
-rwxr-xr-xsrc/roff/groff/tests/html_works_with_grn_and_eqn.sh46
-rwxr-xr-xsrc/roff/groff/tests/initialization_is_quiet.sh59
-rwxr-xr-xsrc/roff/groff/tests/localization_works.sh61
-rwxr-xr-xsrc/roff/groff/tests/msoquiet_works.sh35
-rwxr-xr-xsrc/roff/groff/tests/on_latin1_device_oq_is_0x27.sh30
-rwxr-xr-xsrc/roff/groff/tests/output_driver_C_and_G_options_work.sh50
-rwxr-xr-xsrc/roff/groff/tests/recognize_end_of_sentence.sh33
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_56555.sh27
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_58153.sh34
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_58162.sh26
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_58337.sh32
-rwxr-xr-xsrc/roff/groff/tests/regression_savannah_59202.sh29
-rwxr-xr-xsrc/roff/groff/tests/smoke-test_html_device.sh90
-rwxr-xr-xsrc/roff/groff/tests/some_escapes_accept_newline_delimiters.sh128
-rwxr-xr-xsrc/roff/groff/tests/soquiet_works.sh34
-rwxr-xr-xsrc/roff/groff/tests/string_case_xform_errors.sh30
-rwxr-xr-xsrc/roff/groff/tests/string_case_xform_requests.sh32
-rwxr-xr-xsrc/roff/groff/tests/string_case_xform_unicode_escape.sh42
-rwxr-xr-xsrc/roff/groff/tests/substring_works.sh120
-rwxr-xr-xsrc/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh44
-rw-r--r--src/roff/nroff/nroff.1.man358
-rw-r--r--src/roff/nroff/nroff.am44
-rw-r--r--src/roff/nroff/nroff.sh204
-rwxr-xr-xsrc/roff/nroff/tests/verbose_option_works.sh68
-rw-r--r--src/roff/troff/TODO125
-rw-r--r--src/roff/troff/charinfo.h301
-rw-r--r--src/roff/troff/column.cpp731
-rw-r--r--src/roff/troff/dictionary.cpp209
-rw-r--r--src/roff/troff/dictionary.h91
-rw-r--r--src/roff/troff/div.cpp1212
-rw-r--r--src/roff/troff/div.h175
-rw-r--r--src/roff/troff/env.cpp4138
-rw-r--r--src/roff/troff/env.h421
-rw-r--r--src/roff/troff/hvunits.h339
-rw-r--r--src/roff/troff/input.cpp9209
-rw-r--r--src/roff/troff/input.h120
-rw-r--r--src/roff/troff/mtsm.cpp651
-rw-r--r--src/roff/troff/mtsm.h165
-rw-r--r--src/roff/troff/node.cpp6656
-rw-r--r--src/roff/troff/node.h670
-rw-r--r--src/roff/troff/number.cpp712
-rw-r--r--src/roff/troff/reg.cpp479
-rw-r--r--src/roff/troff/reg.h74
-rw-r--r--src/roff/troff/request.h97
-rw-r--r--src/roff/troff/token.h249
-rw-r--r--src/roff/troff/troff.1.man1047
-rw-r--r--src/roff/troff/troff.am66
-rw-r--r--src/roff/troff/troff.h97
66 files changed, 34251 insertions, 0 deletions
diff --git a/src/roff/groff/groff.1.man b/src/roff/groff/groff.1.man
new file mode 100644
index 0000000..c551326
--- /dev/null
+++ b/src/roff/groff/groff.1.man
@@ -0,0 +1,2403 @@
+.TH groff @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+groff \- front end to the GNU
+.I roff
+document formatting system
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2022 Free Software Foundation, Inc.
+.\"
+.\" This file is part of groff, the GNU roff type-setting system.
+.\"
+.\" Permission is granted to copy, distribute and/or modify this
+.\" document under the terms of the GNU Free Documentation License,
+.\" Version 1.3 or any later version published by the Free Software
+.\" Foundation; with no Invariant Sections, with no Front-Cover Texts,
+.\" and with no Back-Cover Texts.
+.\"
+.\" A copy of the Free Documentation License is included as a file
+.\" called FDL in the main directory of the groff source package.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_groff_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.\" Define a string for the TeX logo.
+.ie t .ds TeX T\h'-.1667m'\v'.224m'E\v'-.224m'\h'-.125m'X
+.el .ds TeX TeX
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY groff
+.RB [ \-abcCeEgGijklNpRsStUVXzZ ]
+.RB [ \-d\~\c
+.IR ctext ]
+.RB [ \-d\~\c
+.IB string =\c
+.IR text ]
+.RB [ \-D\~\c
+.IR fallback-encoding ]
+.RB [ \-f\~\c
+.IR font-family ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-K\~\c
+.IR input-encoding ]
+.RB [ \-L\~\c
+.IR spooler-argument ]
+.RB [ \-m\~\c
+.IR macro-package ]
+.RB [ \-M\~\c
+.IR macro-directory ]
+.RB [ \-n\~\c
+.IR page-number ]
+.RB [ \-o\~\c
+.IR page-list ]
+.RB [ \-P\~\c
+.IR postprocessor-argument ]
+.RB [ \-r\~\c
+.IR cnumeric-expression ]
+.RB [ \-r\~\c
+.IB register =\c
+.IR numeric-expression ]
+.RB [ \-T\~\c
+.IR output-device ]
+.RB [ \-w\~\c
+.IR warning-category ]
+.RB [ \-W\~\c
+.IR warning-category ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY groff
+.B \-h
+.
+.SY groff
+.B \-\-help
+.YS
+.
+.
+.SY groff
+.B \-v
+.RI [ option\~ .\|.\|.\&]
+.RI [ file\~ .\|.\|.]
+.
+.SY groff
+.B \-\-version
+.RI [ option\~ .\|.\|.\&]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I groff
+is the primary front end to the GNU
+.I roff
+document formatting system.
+.
+.\" BEGIN Keep parallel with groff.texi node "What Is groff?".
+.\" This language is slightly expanded from that in the "ANNOUNCE" file
+.\" and on the groff home page.
+GNU
+.I roff
+is a typesetting system that reads plain text input files that include
+formatting commands to produce output in PostScript,
+PDF,
+HTML,
+DVI,
+or other formats,
+or for display to a terminal.
+.
+Formatting commands can be low-level typesetting primitives,
+macros from a supplied package,
+or user-defined macros.
+.
+All three approaches can be combined.
+.
+If no
+.I file
+operands are specified,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+.I groff
+reads the standard input stream.
+.
+.
+.P
+A reimplementation and extension of the typesetter from AT&T Unix,
+.I groff
+is present on most POSIX systems owing to its long association with Unix
+manuals
+(including man pages).
+.
+It and its predecessor are notable for their production of several
+best-selling software engineering texts.
+.
+.I groff
+is capable of producing typographically sophisticated documents while
+consuming minimal system resources.
+.\" END Keep parallel with groff.texi node "What Is groff?".
+.
+.
+.P
+The
+.I groff
+command orchestrates the execution of preprocessors,
+the transformation of input documents into a device-independent page
+description language,
+and the production of output from that language.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message and exit.
+.
+.
+.P
+Because
+.I groff
+is intended to subsume most users' direct invocations of the
+.MR @g@troff @MAN1EXT@
+formatter,
+the two programs share a set of options.
+.
+However,
+.I groff
+has some options that
+.I @g@troff
+does not share,
+and others which
+.I groff
+interprets differently.
+.
+At the same time,
+not all valid
+.I @g@troff
+options can be given to
+.IR groff .
+.
+.
+.\" ====================================================================
+.SS "\f[I]groff\f[]-specific options"
+.\" ====================================================================
+.
+The following options either do not exist in
+GNU
+.I troff \" GNU
+or are interpreted differently by
+.IR groff .
+.
+.
+.TP
+.BI \-D\~ enc
+Set fallback input encoding used by
+.MR preconv @MAN1EXT@
+to
+.IR enc ;
+implies
+.BR \-k .
+.
+.
+.TP
+.B \-e
+Run
+.MR @g@eqn @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.B \-g
+Run
+.MR @g@grn @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.B \-G
+Run
+.MR grap 1
+preprocessor;
+implies
+.BR \-p .
+.
+.
+.TP
+.BI \-I\~ dir
+Works as
+.IR @g@troff 's
+option
+(see below),
+but also implies
+.B \-g
+and
+.BR \-s .
+.
+It is passed to
+.MR @g@soelim @MAN1EXT@
+and the output driver,
+and
+.I @g@grn
+is passed an
+.B \-M
+option with
+.I dir
+as its argument.
+.
+.
+.TP
+.B \-j
+Run
+.MR @g@chem @MAN1EXT@
+preprocessor;
+implies
+.BR \-p .
+.
+.
+.TP
+.B \-k
+Run
+.MR preconv @MAN1EXT@
+preprocessor.
+.
+Refer to its man page for its behavior if neither of
+.IR groff 's
+.B \-K
+or
+.B \-D
+options is also specified.
+.
+.
+.TP
+.BI \-K\~ enc
+Set input encoding used by
+.MR preconv @MAN1EXT@
+to
+.IR enc ;
+implies
+.BR \-k .
+.
+.
+.TP
+.B \-l
+Send the output to a spooler program for printing.
+.
+The
+.RB \[lq] print \[rq]
+directive in the device description file
+specifies the default command to be used;
+see
+.MR groff_font @MAN5EXT@ .
+.
+If no such directive is present for the output device,
+.ie '@PSPRINT@'' \{\
+this option is ignored.
+.\}
+.el \{\
+output is piped to
+.MR @PSPRINT@ 1 .
+.\}
+.
+See options
+.B \-L
+and
+.BR \-X .
+.
+.
+.TP
+.BI \-L\~ arg
+Pass
+.I arg
+to the print spooler program.
+.
+If multiple
+.IR arg s
+are required,
+pass each with a separate
+.B \-L
+option.
+.
+.I groff
+does not prefix an option dash to
+.I arg
+before passing it to the spooler program.
+.
+.
+.TP
+.B \-M
+Works as
+.IR @g@troff 's
+option
+(see below),
+but is also passed to
+.MR @g@eqn @MAN1EXT@ ,
+.MR grap @MAN1EXT@ ,
+and
+.MR @g@grn @MAN1EXT@ .
+.
+.
+.TP
+.B \-N
+Prohibit newlines between
+.I eqn \" language
+delimiters:
+pass
+.B \-N
+to
+.MR @g@eqn @MAN1EXT@ .
+.
+.
+.TP
+.B \-p
+Run
+.MR @g@pic @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.BI \-P\~ arg
+Pass
+.I arg
+to the postprocessor.
+.
+If multiple
+.IR arg s
+are required,
+pass each with a separate
+.B \-P
+option.
+.
+.I groff
+does not prefix an option dash to
+.I arg
+before passing it to the postprocessor.
+.
+.
+.TP
+.B \-R
+Run
+.MR @g@refer @MAN1EXT@
+preprocessor.
+.
+No mechanism is provided for passing arguments to
+.I @g@refer
+because most
+.I @g@refer
+options have equivalent language elements that can be specified within
+the document.
+.
+.
+.TP
+.B \-s
+Run
+.MR @g@soelim @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.B \-S
+Operate in \[lq]safer\[rq] mode;
+see
+.B \-U
+below for its opposite.
+.
+For security reasons,
+safer mode is enabled by default.
+.
+.
+.TP
+.B \-t
+Run
+.MR @g@tbl @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.BI \-T\~ dev
+Direct
+.I @g@troff
+to format the input for the output device
+.IR dev .
+.
+.I groff
+then calls an output driver to convert
+.IR @g@troff 's
+output to a form appropriate for
+.IR dev ;
+see subsection \[lq]Output devices\[rq] below.
+.
+.
+.TP
+.B \-U
+Operate in unsafe mode:
+pass the
+.B \-U
+option to
+.I @g@pic
+and
+.IR @g@troff .
+.
+.
+.TP
+.B \-v
+.TQ
+.B \-\-version
+Write version information for
+.I groff
+and all programs run by it to the standard output stream;
+that is,
+the given command line is processed in the usual way,
+passing
+.B \-v
+to the formatter and any pre- or postprocessors invoked.
+.
+.
+.TP
+.B \-V
+Output the pipeline that
+.I groff
+would run to the standard output stream,
+but do not execute it.
+.
+If given more than once,
+.I groff
+both writes and runs the pipeline.
+.
+.
+.TP
+.B \-X
+Use
+.MR gxditview @MAN1EXT@
+instead of the usual postprocessor to (pre)view a document on an X11
+display.
+.
+Combining this option with
+.B \-Tps
+uses the font metrics of the PostScript device,
+whereas the
+.B \-TX75
+and
+.B \-TX100
+options use the metrics of X11 fonts.
+.
+.
+.TP
+.B \-Z
+Disable postprocessing.
+.
+.I @g@troff
+output will appear on the standard output stream
+(unless suppressed with
+.BR \-z );
+see
+.MR groff_out @MAN5EXT@
+for a description of this format.
+.
+.
+.\" ====================================================================
+.SS "Transparent options"
+.\" ====================================================================
+.
+The following options are passed as-is to the formatter program
+.MR @g@troff @MAN1EXT@
+and described in more detail in its man page.
+.
+.
+.TP
+.B \-a
+Generate a plain text approximation of the typeset output.
+.
+.
+.TP
+.B \-b
+Write a backtrace to the standard error stream on each error or warning.
+.
+.
+.TP
+.B \-c
+Start with color output disabled.
+.
+.
+.TP
+.B \-C
+Enable AT&T
+.I troff \" AT&T
+compatibility mode;
+implies
+.BR \-c .
+.
+.
+.TP
+.BI \-d\~ cs
+.TQ
+.BI \-d\~ name = string
+Define string.
+.
+.
+.TP
+.B \-E
+Inhibit
+.I @g@troff
+error messages;
+implies
+.BR \-Ww .
+.
+.
+.TP
+.BI \-f\~ fam
+Set default font family.
+.
+.
+.TP
+.BI \-F\~ dir
+Search in directory
+.I dir
+for the selected output device's directory of device and font
+description files.
+.
+.
+.TP
+.B \-i
+Process standard input after the specified input files.
+.
+.
+.TP
+.BI \-I\~ dir
+Search
+.I dir
+for input files.
+.
+.
+.TP
+.BI \-m\~ name
+Process
+.RI name .tmac
+before input files.
+.
+.
+.TP
+.BI \-M\~ dir
+Search directory
+.I dir
+for macro files.
+.
+.
+.TP
+.BI \-n\~ num
+Number the first page
+.IR num .
+.
+.
+.TP
+.BI \-o\~ list
+Output only pages in
+.IR list .
+.
+.
+.TP
+.BI \-r\~ cnumeric-expression
+.TQ
+.BI \-r\~ register = numeric-expression
+Define register.
+.
+.
+.TP
+.BI \-w\~ name
+.TQ
+.BI \-W\~ name
+Enable
+.RB ( \-w )
+or inhibit
+.RB ( \-W )
+emission of warnings in category
+.IR name .
+.
+.
+.TP
+.B \-z
+Suppress formatted device-independent output of
+.IR @g@troff .
+.
+.
+.\" ====================================================================
+.SH Usage
+.\" ====================================================================
+.
+The architecture of the GNU
+.I roff
+system
+follows that of other device-independent
+.I roff
+implementations,
+comprising preprocessors,
+macro packages,
+output drivers
+(or \[lq]postprocessors\[rq]),
+a suite of utilities,
+and the formatter
+.I @g@troff
+at its heart.
+.
+See
+.MR roff @MAN7EXT@
+for a survey of how a
+.I roff
+system works.
+.
+.
+.P
+The front end programs available in the GNU
+.I roff
+system make it easier to use than traditional
+.IR roff s
+that required the construction of pipelines or use of temporary files to
+carry a source document from maintainable form to device-ready output.
+.
+The discussion below summarizes the constituent parts of the GNU
+.I roff
+system.
+.
+It complements
+.MR roff @MAN7EXT@
+with
+.IR groff -specific
+information.
+.
+.
+.\" ====================================================================
+.SS "Getting started"
+.\" ====================================================================
+.
+Those who prefer to learn by experimenting or are desirous of rapid
+feedback from the system may wish to start with a \[lq]Hello,
+world!\&\[rq] document.
+.
+.
+.P
+.EX
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tascii \
+| sed \[aq]/\[ha]$/d\[aq]
+Hello, world!
+.EE
+.
+.
+.P
+We used a
+.I sed
+command only to eliminate the 65 blank lines that would otherwise flood
+the terminal screen.
+.
+.RI ( roff
+systems were developed in the days of paper-based terminals with 66
+lines to a page.)
+.
+.
+.P
+Today's users may prefer output to a UTF-8-capable terminal.
+.
+.
+.P
+.EX
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tutf8 \
+| sed \[aq]/\[ha]$/d\[aq]
+.EE
+.
+.
+.P
+Producing PDF,
+HTML,
+or \*[TeX]'s DVI is also straightforward.
+.
+The hard part may be selecting a viewer program for the output.
+.
+.
+.P
+.EX
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tpdf > hello.pdf
+$ \c
+.B evince hello.pdf
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Thtml > hello.html
+$ \c
+.B firefox hello.html
+$ \c
+.B echo \[dq]Hello, world!\[dq] | groff \-Tdvi > hello.dvi
+$ \c
+.B xdvi hello.html
+.EE
+.
+.
+.\" ====================================================================
+.SS "Using \f[I]groff\f[] as a REPL"
+.\" ====================================================================
+.
+Those with a programmer's bent may be pleased to know that they can use
+.I groff
+in a read-evaluate-print loop (REPL).
+.
+Doing so can be handy to verify one's understanding of the formatter's
+behavior and/or the syntax it accepts.
+.
+Turning on all warnings with
+.B \-ww
+can aid this goal.
+.
+.
+.P
+.EX
+$ \c
+.B groff \-ww \-Tutf8
+.B \[rs]# This is a comment. Let\[aq]s define a register.
+.B .nr a 1
+.B \[rs]# Do integer arithmetic with operators evaluated left-to-right.
+.B .nr b \[rs]n[a]+5/2
+.ne 2v
+.B \[rs]# Let\[aq]s get the result on the standard error stream.
+.B .tm \[rs]n[b]
+3
+.B \[rs]# Now we\[aq]ll define a string.
+.B .ds name Leslie\[rs]" This is another form of comment.
+.B .nr b (\[rs]n[a] + (7/2))
+.B \[rs]# Center the next two text input lines.
+.B .ce 2
+.B Hi, \[rs]*[name].
+.B Your secret number is \[rs]n[b].
+.B \[rs]# We will see that the division rounded toward zero.
+.B It is
+.B \[rs]# Here\[aq]s an if-else control structure.
+.B .ie (\[rs]n[b] % 2) odd.
+.B .el even.
+.B \[rs]# This trick sets the page length to the current vertical
+.B \[rs]# position, so that blank lines don\[aq]t spew when we\[aq]re \
+done.
+.B .pl \[rs]n[nl]u
+.I <Control-D>
+ Hi, Leslie.
+ Your secret number is 4.
+It is even.
+.EE
+.
+.
+.\" ====================================================================
+.SS "Paper format"
+.\" ====================================================================
+.
+In GNU
+.IR roff ,
+the page dimensions for the formatter
+.I @g@troff
+and for output devices are handled separately.
+.
+In the formatter,
+requests are used to set the page length
+.RB ( .pl ),
+page offset
+(or left margin,
+.BR .po ),
+and line length
+.RB ( .ll ).
+.
+The right margin is not explicitly configured;
+the combination of page offset and line length provides the information
+necessary to derive it.
+.
+The
+.I papersize
+macro package,
+automatically loaded by
+.IR @g@troff ,
+provides an interface for configuring page dimensions by convenient
+names,
+like \[lq]letter\[rq] or
+\[lq]A4\[rq];
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+The formatter's default in this installation is
+.RB \[lq] @PAGE@ \[rq].
+.
+.
+.P
+It is up to each macro package to respect the page dimensions configured
+in this way.
+.
+Some offer alternative mechanisms.
+.
+.
+.P
+For each output device,
+the size of the output medium can be set in its
+.I DESC
+file.
+.
+Most output drivers also recognize a command-line option
+.B \-p
+to override the default dimensions and an option
+.B \-l
+to use landscape orientation.
+.
+See
+.MR groff_font @MAN5EXT@
+for a description of the
+.B papersize
+directive,
+which takes an argument of the same form as
+.BR \-p .
+.
+The output driver's man page,
+such as
+.MR grops @MAN1EXT@ ,
+may also be helpful.
+.
+.I groff
+uses the command-line option
+.B \-P
+to pass options to output devices;
+for example,
+use the following for PostScript output on A4 paper in landscape
+orientation.
+.
+.
+.IP
+.EX
+groff \-Tps \-dpaper=a4l \-P\-pa4 \-P\-l \-ms foo.ms > foo.ps
+.EE
+.
+.
+.\" ====================================================================
+.SS "Front end"
+.\" ====================================================================
+.
+The
+.I groff
+program is a wrapper around the
+.MR @g@troff @MAN1EXT@
+program.
+.
+It allows one to specify preprocessors via command-line options and
+automatically runs the appropriate postprocessor for the selected
+output device.
+.
+Doing so,
+the manual construction of pipelines or management of temporary files
+required of users of traditional
+.MR roff @MAN7EXT@
+systems can be avoided.
+.
+Use the
+.MR grog @MAN1EXT@
+program to infer an appropriate
+.I groff
+command line to format a document.
+.
+.
+.\" ====================================================================
+.SS Language
+.\" ====================================================================
+.
+Input to a
+.I roff
+system is in plain text interleaved with control lines and escape
+sequences.
+.
+The combination constitutes a document in one of a family of languages
+we also call
+.IR roff ;
+see
+.MR roff @MAN7EXT@
+for background.
+.
+An overview of GNU
+.I roff
+language syntax and features,
+including lists of all supported escape sequences,
+requests,
+and predefined registers,
+can be found in
+.MR groff @MAN7EXT@ .
+.
+GNU
+.I roff
+extensions to the AT&T
+.I troff
+language,
+a common subset of
+.I roff
+dialects extant today,
+are detailed in
+.MR groff_diff @MAN7EXT@ .
+.
+.
+.\" ====================================================================
+.SS Preprocessors
+.\" ====================================================================
+.
+A preprocessor interprets a domain-specific language that produces
+.I roff
+language output.
+.
+Frequently,
+such input is confined to sections or regions of a
+.I roff
+input file
+(bracketed with macro calls specific to each preprocessor),
+which it replaces.
+.
+Preprocessors therefore often interpret a subset of
+.I roff
+syntax along with their own language.
+.
+GNU
+.I roff
+provides reimplementations of most preprocessors familiar to users of
+AT&T
+.IR troff ; \" AT&T
+these routinely have extended features and/or require GNU
+.I troff \" GNU
+to format their output.
+.
+.
+.br
+.ne 10v
+.P
+.RS
+.TS
+tab($);
+Li Lx.
+@g@tbl$lays out tables;
+@g@eqn$typesets mathematics;
+@g@pic$draws diagrams;
+@g@refer$processes bibliographic references;
+@g@soelim$preprocesses \[lq]sourced\[rq] input files;
+@g@grn$T{
+renders
+.MR gremlin 1
+diagrams;
+T}
+@g@chem$T{
+draws chemical structural formul\[ae]
+using
+.IR pic ; \" generic
+T}
+gperl$T{
+populates
+.I groff
+registers and strings using
+.MR perl 1 ;
+T}
+glilypond$T{
+embeds
+.I LilyPond
+sheet music;
+and
+T}
+gpinyin$T{
+eases Mandarin Chinese input using Hanyu Pinyin.
+T}
+.TE
+.RE
+.
+.
+.P
+A preprocessor unique to GNU
+.I roff
+is
+.MR preconv @MAN1EXT@ ,
+which converts various input encodings to something GNU
+.I troff \" GNU
+can understand.
+.
+When used,
+it is run before any other preprocessors.
+.
+.
+.P
+Most preprocessors enclose content between a pair of characteristic
+tokens.
+.
+Such a token must occur at the beginning of an input line and use the
+dot control character.
+.
+Spaces and tabs must not follow the control character or precede the
+end of the input line.
+.
+Deviating from these rules defeats a token's recognition by the
+preprocessor.
+.
+Tokens are generally preserved in preprocessor output and interpreted as
+macro calls subsequently by
+.IR @g@troff .
+.
+The
+.I @g@ideal
+preprocessor is not yet available in
+.IR groff .
+.
+.
+.P
+.TS
+box, center, tab (^);
+c | c | c
+CfCR | CfCR | CfCR.
+preprocessor^starting token^ending token
+=
+@g@chem^.cstart^.cend
+@g@eqn^.EQ^.EN
+grap^.G1^.G2
+@g@grn^.GS^.GE
+.\" Keep the .IF line below the @g@ideal line.
+@g@ideal^.IS^.IE
+^^.IF
+.\" Keep the .PF line below the @g@pic line.
+@g@pic^.PS^.PE
+^^.PF
+^^.PY
+@g@refer^.R1^.R2
+@g@tbl^.TS^.TE
+_
+glilypond^.lilypond start^.lilypond stop
+gperl^.Perl start^.Perl stop
+gpinyin^.pinyin start^.pinyin stop
+.TE
+.
+.
+.\" ====================================================================
+.SS "Macro packages"
+.\" ====================================================================
+.
+Macro files are
+.I roff
+input files designed to produce no output themselves but instead ease
+the preparation of other
+.I roff
+documents.
+.
+When a macro file is installed at a standard location and suitable for
+use by a general audience,
+it is termed a
+.IR "macro package" .
+.
+.
+.P
+Macro packages can be loaded prior to any
+.I roff
+input documents with the
+.BR \-m \~option.
+.
+The GNU
+.I roff
+system implements most well-known macro packages for AT&T
+.I troff \" AT&T
+.\" exceptions: mpm, mv
+in a compatible way and extends them.
+.
+These have one- or two-letter names arising from intense practices of
+naming economy in early Unix culture,
+a laconic approach that led to many of the packages being identified in
+general usage with the
+.I nroff
+and
+.I troff
+option letter used to invoke them,
+sometimes to punning effect,
+as with \[lq]man\[rq]
+(short for \[lq]manual\[rq]),
+and even with the option dash,
+as in the case of the
+.I s
+package,
+much better known as
+.I ms
+or even
+.IR \-ms .
+.
+.
+.P
+Macro packages serve a variety of purposes.
+.
+Some are \[lq]full-service\[rq] packages,
+adopting responsibility for page layout among other fundamental tasks,
+and defining their own lexicon of macros for document composition;
+each such package stands alone and a given document can use at most one.
+.
+.
+.TP
+.I an
+is used to compose man pages in the format originating in Version\~7
+Unix (1979);
+see
+.MR groff_man @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-man .
+.
+.
+.TP
+.I doc
+is used to compose man pages in the format originating in 4.3BSD-Reno
+(1990);
+see
+.MR groff_mdoc @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-mdoc .
+.
+.
+.TP
+.I e
+is the Berkeley general-purpose macro suite,
+developed as an alternative to AT&T's
+.IR s ;
+see
+.MR groff_me @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-me .
+.
+.
+.TP
+.I m
+implements the format used by the
+second-generation AT&T macro suite for general documents,
+a successor to
+.IR s ;
+see
+.MR groff_mm @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-mm .
+.
+.
+.TP
+.I om
+(invariably called \[lq]mom\[rq])
+is a modern package written by Peter Schaffter specifically for GNU
+.IR roff .
+.
+Consult the
+.UR file://\:@HTMLDOCDIR@/\:mom/\:toc\:.html
+.I mom
+HTML manual
+.UE
+for extensive documentation.
+.
+She\[em]for
+.I mom
+takes the female pronoun\[em]can be specified on the command line as
+.BR \-mom .
+.
+.
+.TP
+.I s
+is the original AT&T general-purpose document format;
+see
+.MR groff_ms @MAN7EXT@ .
+.
+It can be specified on the command line as
+.BR \-ms .
+.
+.
+.P
+Others are supplemental.
+.
+For instance,
+.
+.I \%andoc
+is a wrapper package specific to GNU
+.I roff
+that recognizes whether a document uses
+.I man
+or
+.I mdoc
+format and loads the corresponding macro package.
+.
+It can be specified on the command line as
+.BR \%\-mandoc .
+.
+A
+.MR man 1
+librarian program \" such as man-db, since 2001
+may use this macro file to delegate loading of the correct macro
+package;
+it is thus unnecessary for
+.I man
+itself to scan the contents of a document to decide the issue.
+.
+.
+.P
+Many macro files augment the function of the full-service packages,
+or of
+.I roff
+documents that do not employ such a package\[em]the latter are sometimes
+characterized as \[lq]raw\[rq].
+.
+These auxiliary packages are described,
+along with
+details of macro file naming and placement,
+in
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS Formatters
+.\" ====================================================================
+.
+The formatter,
+the program that interprets
+.I roff
+language input,
+is
+.MR @g@troff @MAN1EXT@ .
+.
+It provides the features of the AT&T
+.I troff \" AT&T
+and
+.I nroff \" AT&T
+programs as well as many extensions.
+.
+The command-line option
+.B \-C
+switches
+.I @g@troff
+into
+.IR "compatibility mode" ,
+which tries to emulate AT&T
+.I troff \" AT&T
+as closely as is practical to enable the formatting of documents written
+for the older system.
+.
+.
+.P
+A shell script,
+.MR @g@nroff @MAN1EXT@ ,
+emulates the behavior of AT&T
+.IR nroff . \" AT&T
+.
+It attempts to correctly encode the output based on the locale,
+relieving the user of the need to specify an output device with the
+.B \-T
+option and is therefore convenient for use with terminal output devices,
+described in the next subsection.
+.
+.
+.P
+GNU
+.I troff \" GNU
+generates output in a device-independent,
+but not device-agnostic,
+page description language detailed in
+.MR groff_out @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS "Output devices"
+.\" ====================================================================
+.
+.I @g@troff
+output is formatted for a particular
+.IR "output device" ,
+typically specified by the
+.B \-T
+option to the formatter or a front end.
+.
+If neither this option nor the
+.I \%GROFF_TYPESETTER
+environment variable is used,
+the default output device is
+.BR @DEVICE@ .
+.
+An output device may be any of the following.
+.
+.
+.TP 9n \" to fit "X100\-12" even on troff devices
+.B ascii
+for terminals using the ISO 646 1991:IRV character set and encoding,
+also known as US-ASCII.
+.
+.
+.TP
+.B cp1047
+for terminals using the IBM code page 1047 character set and encoding.
+.
+.
+.TP
+.B dvi
+for TeX DVI format.
+.
+.
+.TP
+.B html
+.TQ
+.B xhtml
+for HTML and XHTML output,
+respectively.
+.
+.
+.TP
+.B latin1
+for terminals using the ISO Latin-1
+(ISO 8859-1)
+character set and encoding.
+.
+.
+.TP
+.B lbp
+for Canon CaPSL printers
+(LBP-4 and LBP-8 series laser printers).
+.
+.
+.TP
+.B lj4
+for HP LaserJet4-compatible
+(or other PCL5-compatible)
+printers.
+.
+.
+.TP
+.B pdf
+for PDF output.
+.
+.
+.TP
+.B ps
+for PostScript output.
+.
+.
+.TP
+.B utf8
+for terminals using the ISO 10646 (\[lq]Unicode\[rq]) character set in
+UTF-8 encoding.
+.
+.
+.TP
+.B X75
+for previewing with
+.I \%gxditview
+using
+75 dpi resolution and a
+10-point base type size.
+.
+.
+.TP
+.B X75\-12
+for previewing with
+.I \%gxditview
+using
+75 dpi resolution and a
+12-point base type size.
+.
+.
+.TP
+.B X100
+for previewing with
+.I \%gxditview
+using
+100 dpi resolution and a
+10-point base type size.
+.
+.
+.TP
+.B X100\-12
+for previewing with
+.I \%gxditview
+using
+100 dpi resolution
+and a
+12-point base type size.
+.
+.
+.\" ====================================================================
+.SS Postprocessors
+.\" ====================================================================
+.
+Any program that interprets the output of
+GNU
+.I troff \" GNU
+is a
+postprocessor.
+.
+The postprocessors provided by GNU
+.I roff
+are
+.IR "output drivers" ,
+which prepare a document for viewing or printing.
+.
+Postprocessors for other purposes,
+such as page resequencing or statistical measurement of a document,
+are conceivable.
+.
+.
+.P
+An output driver supports one or more output devices,
+each with its own device description file.
+.
+A device determines its postprocessor with the
+.B postpro
+directive in its device description file;
+see
+.MR groff_font @MAN5EXT@ .
+.
+The
+.B \-X
+option overrides this selection,
+causing
+.I \%gxditview
+to serve as the output driver.
+.
+.
+.TP
+.MR grodvi @MAN1EXT@
+provides
+.BR dvi .
+.
+.
+.TP
+.MR grohtml @MAN1EXT@
+provides
+.B html
+and
+.BR xhtml .
+.
+.
+.TP
+.MR grolbp @MAN1EXT@
+provides
+.BR lbp .
+.
+.
+.TP
+.MR grolj4 @MAN1EXT@
+provides
+.BR lj4 .
+.
+.
+.TP
+.MR gropdf @MAN1EXT@
+provides
+.BR pdf .
+.
+.
+.TP
+.MR grops @MAN1EXT@
+provides
+.BR ps .
+.
+.
+.TP
+.MR grotty @MAN1EXT@
+provides
+.BR ascii ,
+.BR cp1047 ,
+.BR latin1 ,
+and
+.BR utf8 .
+.
+.
+.TP
+.MR gxditview @MAN1EXT@
+provides
+.BR X75 ,
+.BR X75\-12 ,
+.BR X100 ,
+and
+.BR X100\-12 ,
+and additionally can preview
+.BR ps .
+.
+.
+.\" ====================================================================
+.SS Utilities
+.\" ====================================================================
+.
+GNU
+.I roff
+includes a suite of utilities.
+.
+.
+.TP
+.MR gdiffmk @MAN1EXT@
+marks differences between a pair of
+.I roff
+input files.
+.
+.
+.TP
+.MR grog @MAN1EXT@
+infers the
+.I groff
+command a document requires.
+.
+.
+.P
+Several utilities prepare descriptions of fonts,
+enabling the formatter to use them when producing output for a given
+device.
+.
+.
+.TP
+.MR addftinfo @MAN1EXT@
+adds information to AT&T
+.I troff \" AT&T
+font description files to enable their use with
+GNU
+.IR troff .\" GNU
+.
+.
+.TP
+.MR afmtodit @MAN1EXT@
+creates font description files for PostScript Type\~1 fonts.
+.
+.
+.TP
+.MR pfbtops @MAN1EXT@
+translates a PostScript Type\~1 font in PFB
+(Printer Font Binary)
+format to PFA
+(Printer Font ASCII),
+which can then be interpreted by
+.IR \%afmtodit .
+.
+.
+.TP
+.MR hpftodit @MAN1EXT@
+creates font description files for the HP LaserJet\~4 family of
+printers.
+.
+.
+.TP
+.MR tfmtodit @MAN1EXT@
+creates font description files for the TeX DVI device.
+.
+.
+.TP
+.MR xtotroff @MAN1EXT@
+creates font description files for X Window System core fonts.
+.
+.
+.P
+A trio of tools transform material constructed using
+.I roff
+preprocessor languages into graphical image files.
+.
+.
+.TP
+.MR eqn2graph @MAN1EXT@
+converts an
+.I eqn
+equation into a cropped image.
+.
+.
+.TP
+.MR grap2graph @MAN1EXT@
+converts a
+.I grap
+diagram into a cropped image.
+.
+.
+.TP
+.MR pic2graph @MAN1EXT@
+converts a
+.I pic
+diagram into a cropped image.
+.
+.
+.P
+Another set of programs works with the bibliographic data files used
+by the
+.MR refer @MAN1EXT@
+preprocessor.
+.
+.
+.TP
+.MR @g@indxbib @MAN1EXT@
+makes inverted indices for bibliographic databases,
+speeding lookup operations on them.
+.
+.
+.TP
+.MR lkbib @MAN1EXT@
+searches the databases.
+.
+.
+.TP
+.MR @g@lookbib @MAN1EXT@
+interactively searches
+the databases.
+.
+.
+.\" ====================================================================
+.SH "Exit status"
+.\" ====================================================================
+.
+.I groff
+exits with a failure status if there was a problem parsing its arguments
+and a successful status if either of the options
+.B \-h
+or
+.B \-\-help
+was specified.
+.
+Otherwise,
+.I groff
+runs a pipeline to process its input;
+if all commands within the pipeline exit successfully,
+.I groff
+does likewise.
+.
+If not,
+.IR groff 's
+exit status encodes a summary of problems encountered,
+setting bit\~0 if a command exited with a failure status,
+bit\~1 if a command was terminated with a signal,
+and bit\~2 if a command could not be executed.
+.
+(Thus,
+if all three misfortunes befell one's pipeline,
+.I groff
+would exit with status 2\[ha]0 + 2\[ha]1 + 2\[ha]2 = 1+2+4 = 7.)
+.
+To troubleshoot pipeline problems,
+you may wish to re-run the
+.I groff
+command with the
+.B \-V
+option and break the reported pipeline down into separate stages,
+inspecting the exit status of and diagnostic messages emitted by each
+command.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+Normally,
+the path separator in environment variables ending with
+.I PATH
+is the colon;
+this may vary depending on the operating system.
+.
+For example,
+Windows uses a semicolon instead.
+.
+.
+.TP
+.I GROFF_BIN_PATH
+This search path,
+followed by
+.IR PATH ,
+is used to locate commands executed by
+.IR groff .
+.
+If it is not set,
+the installation directory of the GNU
+.I roff
+executables,
+.IR @BINDIR@ ,
+is searched before
+.IR PATH .
+.
+.
+.TP
+.I GROFF_COMMAND_PREFIX
+GNU
+.I roff
+can be configured at compile time to apply a prefix to the names of the
+programs it provides that had a counterpart in AT&T
+.IR troff , \" AT&T
+so that name collisions are avoided at run time.
+.
+The default prefix is empty.
+.
+.
+.IP
+When used,
+this prefix is conventionally the letter \[lq]g\[rq].
+.
+For example,
+GNU
+.I troff \" GNU
+would be installed as
+.IR gtroff .
+.
+Besides
+.IR troff , \" GNU
+the prefix applies to
+the formatter
+.IR nroff ; \" GNU
+the preprocessors
+.IR eqn , \" generic
+.IR grn , \" generic
+.IR pic , \" generic
+.IR \%refer , \" generic
+.IR tbl , \" generic
+and
+.IR \%soelim ; \" generic
+and the utilities
+.I \%indxbib \" generic
+and
+.IR \%lookbib . \" generic
+.
+.
+.TP
+.I GROFF_ENCODING
+The value of this variable is passed to the
+.IR preconv (@MAN1EXT@)
+preprocessor's
+.B \-e
+option to select the character encoding of input files.
+.
+This variable's existence implies
+the
+.I groff
+option
+.BR \-k .
+.
+If set but empty,
+.I groff
+calls
+.I preconv
+without an
+.B \-e
+option.
+.
+.IR groff 's
+.B \-K
+option overrides
+.IR \%GROFF_ENCODING .
+.
+.
+.TP
+.I GROFF_FONT_PATH
+Seek the selected output device's directory of device and font
+description files in this list of directories.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_font @MAN5EXT@ .
+.
+.
+.TP
+.I GROFF_TMAC_PATH
+Seek macro files in this list of directories.
+.
+See
+.MR @g@troff @MAN1EXT@
+and
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.TP
+.I GROFF_TMPDIR
+Create temporary files in this directory.
+.
+If not set,
+but the environment variable
+.I \%TMPDIR
+is set,
+temporary files are created there instead.
+.
+On Windows systems,
+if neither of the foregoing are set,
+the environment variables
+.I TMP
+and
+.I TEMP
+(in that order)
+are checked also.
+.
+Otherwise,
+temporary files are created in
+.IR /tmp .
+.
+The
+.MR @g@refer @MAN1EXT@ ,
+.MR grohtml @MAN1EXT@ ,
+and
+.MR grops @MAN1EXT@
+commands use temporary files.
+.
+.
+.TP
+.I GROFF_TYPESETTER
+Set the default output device.
+.
+If empty or not set,
+.B @DEVICE@
+is used.
+.
+The
+.B \-T
+option overrides
+.IR \%GROFF_TYPESETTER .
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A time stamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation time stamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR localtime 3
+when the formatter starts up and stored in registers usable by documents
+and macro packages.
+.
+.
+.TP
+.I TZ
+The time zone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Examples
+.\" ====================================================================
+.
+.I roff
+systems are best known for formatting man pages.
+.
+Once a
+.MR man 1
+librarian program has located a man page,
+it may execute a
+.I groff
+command much like the following.
+.
+.RS
+.EX
+groff \-t \-man \-Tutf8 /usr/share/man/man1/groff.1
+.EE
+.RE
+.
+The librarian will also pipe the output through a pager,
+which might not interpret the SGR terminal escape sequences
+.I groff
+emits for boldface,
+underlining,
+or italics;
+see section \[lq]Limitations\[rq] below.
+.
+.
+.P
+To process a
+.I roff
+input file using the preprocessors
+.I @g@tbl
+and
+.I @g@pic
+and the
+.I me
+macro package in the way to which AT&T
+.I troff \" AT&T
+users were accustomed,
+one would type
+(or script)
+a pipeline.
+.
+.
+.IP
+.EX
+@g@pic foo.me | @g@tbl | @g@troff \-me \-Tutf8 | grotty
+.EE
+.
+.
+.P
+Using
+.IR groff ,
+this pipe can be shortened to an equivalent command.
+.
+.IP
+.EX
+groff \-p \-t \-me \-T utf8 foo.me
+.EE
+.
+.
+.P
+An even easier way to do this is to use
+.MR grog @MAN1EXT@
+to guess the preprocessor and macro options and execute the result by
+using the command substitution feature of the shell.
+.
+.IP
+.EX
+$(grog \-Tutf8 foo.me)
+.EE
+.
+.
+.P
+Each command-line option to a postprocessor must be specified with any
+required leading dashes
+.RB \[lq] \- \[rq]
+.\" No GNU roff postprocessor uses long options for anything except
+.\" --help or --version.
+.\"or
+.\".RB \[lq] \-\- \[rq]
+because
+.I groff
+passes the arguments as-is to the postprocessor;
+this permits arbitrary arguments to be transmitted.
+.
+For example,
+to pass a title to the
+.I gxditview
+postprocessor,
+the shell commands
+.
+.RS
+.EX
+groff \-X \-P \-title \-P \[aq]trial run\[aq] mydoc.t
+.EE
+.RE
+.
+and
+.
+.RS
+.EX
+groff \-X \-Z mydoc.t | gxditview \-title \[aq]trial run\[aq] \-
+.EE
+.RE
+.
+are equivalent.
+.
+.
+.\" ====================================================================
+.SH Limitations
+.\" ====================================================================
+.
+When paging output for the
+.BR ascii ,
+.BR cp1047 ,
+.BR latin1 ,
+and
+.B utf8
+devices,
+programs like
+.MR more 1
+and
+.MR less 1
+may require command-line options to correctly handle some terminal
+escape sequences;
+see
+.MR grotty @MAN1EXT@ .
+.
+.
+.P
+On EBCDIC hosts such as OS/390 Unix,
+the output devices
+.B ascii
+and
+.B latin1
+aren't available.
+.
+Conversely,
+the output device
+.B cp1047
+is not available on systems based on the ISO\~646 or ISO\~8859 character
+encoding standards.
+.
+.
+.\" ====================================================================
+.SH "Installation directories"
+.\" ====================================================================
+.
+GNU
+.I roff
+installs files in varying locations depending on its compile-time
+configuration.
+.
+On this installation,
+the following locations are used.
+.
+.
+.if !'@APPDEFDIR@'' \{\
+.TP
+.I @APPDEFDIR@
+Application defaults directory for
+.MR gxditview @MAN1EXT@ .
+.\}
+.
+.
+.TP
+.I @BINDIR@
+Directory containing
+.IR groff 's
+executable commands.
+.
+.
+.TP
+.I @COMMON_WORDS_FILE@
+List of common words for
+.MR indxbib @MAN1EXT@ .
+.
+.
+.TP
+.I @DATASUBDIR@
+Directory for data files.
+.
+.
+.TP
+.I @DEFAULT_INDEX@
+Default index for
+.MR lkbib @MAN1EXT@
+and
+.MR refer @MAN1EXT@ .
+.
+.
+.TP
+.I @DOCDIR@
+Documentation directory.
+.
+.
+.TP
+.I @EXAMPLEDIR@
+Example directory.
+.
+.
+.TP
+.I @FONTDIR@
+Font directory.
+.
+.
+.TP
+.I @HTMLDOCDIR@
+HTML documentation directory.
+.
+.
+.TP
+.I @LEGACYFONTDIR@
+Legacy font directory.
+.
+.
+.TP
+.I @LOCALFONTDIR@
+Local font directory.
+.
+.
+.TP
+.I @LOCALMACRODIR@
+Local macro package
+.RI ( tmac
+file) directory.
+.
+.
+.TP
+.I @MACRODIR@
+Macro package
+.RI ( tmac
+file) directory.
+.
+.
+.TP
+.I @OLDFONTDIR@
+Font directory for compatibility with old versions of
+.IR groff ;
+see
+.MR grops @MAN1EXT@ .
+.
+.
+.TP
+.I @PDFDOCDIR@
+PDF documentation directory.
+.
+.
+.if !'@COMPATIBILITY_WRAPPERS@'no' \{\
+.TP
+.I @SYSTEMMACRODIR@
+System macro package
+.RI ( tmac
+file) directory.
+.\}
+.
+.
+.\" ====================================================================
+.SS "\f[I]groff\f[] macro directory"
+.\" ====================================================================
+.
+Most macro files supplied with GNU
+.I roff
+are stored in
+.I @MACRODIR@
+for the installation corresponding to this document.
+.
+As a rule,
+multiple directories are searched for macro files;
+see
+.MR @g@troff @MAN1EXT@ .
+.
+For a catalog of macro files GNU
+.I roff
+provides,
+see
+.MR groff_tmac @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SS "\f[I]groff\f[] device and font description directory"
+.\" ====================================================================
+.
+Device and font description files supplied with GNU
+.I roff
+are stored in
+.I @FONTDIR@
+for the installation corresponding to this document.
+.
+As a rule,
+multiple directories are searched for device and font description files;
+see
+.MR @g@troff @MAN1EXT@ .
+.
+For the formats of these files,
+see
+.MR groff_font @MAN5EXT@ .
+.
+.
+.\" ====================================================================
+.SH Availability
+.\" ====================================================================
+.
+Obtain links to
+.I groff
+releases for download,
+its source repository,
+discussion mailing lists,
+a support ticket tracker,
+and further information from the
+.UR http://\:www\:.gnu\:.org/\:software/\:groff
+.I groff
+page of the GNU website
+.UE .
+.
+.
+.P
+A free implementation of the
+.I grap
+preprocessor,
+written by
+.MT faber@\:lunabase\:.org
+Ted Faber
+.ME ,
+can be found at the
+.UR http://\:www\:.lunabase\:.org/\:\[ti]faber/\:Vault/\:software/\
+\:grap/
+.I grap
+website
+.UE .
+.
+.I groff
+supports only this
+.IR grap .
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+.I groff
+(both the front-end command and the overall system)
+was primarily written by
+.MT jjc@\:jclark\:.com
+James Clark
+.ME .
+.
+Contributors to this document include Clark,
+Trent A.\& Fisher,
+.MT wl@gnu.org
+Werner Lemberg
+.ME ,
+.MT groff\-bernd.warken\-72@\:web\:.de
+Bernd Warken
+.ME ,
+and
+.MT g.branden\:.robinson@\:gmail\:.com
+G.\& Branden Robinson
+.ME .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.IR "Groff: The GNU Implementation of troff" ,
+by Trent A.\& Fisher and Werner Lemberg,
+is the primary
+.I groff
+manual.
+.
+You can browse it interactively with \[lq]info groff\[rq].
+.
+.
+.\" groff ships 59 man pages generated from 58 source files. The
+.\" numbered comments refer to their sorting order in the source tree,
+.\" so that it is easier to tell that we've enumerated all of them.
+.TP
+Introduction, \c
+history, \c
+and further reading:
+.MR roff @MAN7EXT@ \" #23
+.
+.
+.TP
+.RI "Viewer for\~" groff "\~(and AT&T device-independent\~" troff \
+)\~documents:
+.MR gxditview @MAN1EXT@ \" #33
+.
+.
+.TP
+Preprocessors:
+.MR @g@chem @MAN1EXT@ , \" #1
+.MR @g@eqn @MAN1EXT@ , \" #34
+.MR @g@neqn @MAN1EXT@ , \" #35
+.MR glilypond @MAN1EXT@ , \" #4
+.MR @g@grn @MAN1EXT@ , \" #36
+.MR preconv @MAN1EXT@ , \" #38
+.MR gperl @MAN1EXT@ , \" #5
+.MR @g@pic @MAN1EXT@ , \" #37
+.MR gpinyin @MAN1EXT@ , \" #6
+.MR @g@refer @MAN1EXT@ , \" #39
+.MR @g@soelim @MAN1EXT@ , \" #40
+.MR @g@tbl @MAN1EXT@ \" #41
+.
+.
+.TP
+Macro packages and package-specific utilities:
+.MR groff_hdtbl @MAN7EXT@ , \" #9
+.MR groff_man @MAN7EXT@ , \" #55a
+.MR groff_man_style @MAN7EXT@ , \" #55b
+.MR groff_mdoc @MAN7EXT@ , \" #56
+.MR groff_me @MAN7EXT@ , \" #57
+.MR groff_mm @MAN7EXT@ , \" # 10
+.MR groff_mmse @MAN7EXT@ , \" # 11
+.MR mmroff @MAN1EXT@ , \" #12
+.MR groff_mom @MAN7EXT@ , \" #13
+.MR pdfmom @MAN1EXT@ , \" #30
+.MR groff_ms @MAN7EXT@ , \" #58
+.MR groff_rfc1345 @MAN7EXT@ , \" 16
+.MR groff_trace @MAN7EXT@ , \" #59
+.MR groff_www @MAN7EXT@ \" #60
+.
+.
+.TP
+Bibliographic database management tools:
+.MR @g@indxbib @MAN1EXT@ , \" #49
+.MR lkbib @MAN1EXT@ , \" #50
+.MR @g@lookbib @MAN1EXT@ \" #51
+.
+.
+.TP
+Language, \c
+conventions, \c
+and GNU extensions:
+.MR groff @MAN7EXT@ , \" #17
+.MR groff_char @MAN7EXT@ , \" #18
+.MR groff_diff @MAN7EXT@ , \" #19
+.MR groff_font @MAN5EXT@ , \" #20
+.MR groff_tmac @MAN5EXT@ \" #22
+.
+.
+.TP
+Intermediate output language:
+.MR groff_out @MAN5EXT@ \" #21
+.
+.
+.TP
+Formatter program:
+.MR @g@troff @MAN1EXT@ \" #45
+.
+.
+.TP
+Formatter wrappers:
+.\".MR groff @MAN1EXT@ , \" 42 -- this page
+.MR @g@nroff @MAN1EXT@ , \" #44
+.MR pdfroff @MAN1EXT@ \" #14
+.
+.
+.TP
+Postprocessors for output devices:
+.MR grodvi @MAN1EXT@ , \" #24
+.MR grohtml @MAN1EXT@ , \" #25
+.MR grolbp @MAN1EXT@ , \" #26
+.MR grolj4 @MAN1EXT@ , \" #27
+.MR gropdf @MAN1EXT@ , \" #29
+.MR grops @MAN1EXT@ , \" #31
+.MR grotty @MAN1EXT@ \" #32
+.
+.
+.TP
+Font support utilities:
+.MR addftinfo @MAN1EXT@ , \" #46
+.MR afmtodit @MAN1EXT@ , \" #47
+.MR hpftodit @MAN1EXT@ , \" #48
+.MR pfbtops @MAN1EXT@ , \" #52
+.MR tfmtodit @MAN1EXT@ , \" #53
+.MR xtotroff @MAN1EXT@ \" #54
+.
+.
+.TP
+Graphics conversion utilities:
+.MR eqn2graph @MAN1EXT@ , \" #2
+.MR grap2graph @MAN1EXT@ , \" #7
+.MR pic2graph @MAN1EXT@ \" #15
+.
+.
+.TP
+Difference-marking utility:
+.MR gdiffmk @MAN1EXT@ \" #3
+.
+.
+.TP
+\[lq]groff guess\[rq] utility:
+.MR grog @MAN1EXT@ \" #43
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_groff_1_man_C]
+.do rr *groff_groff_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/roff/groff/groff.am b/src/roff/groff/groff.am
new file mode 100644
index 0000000..8937d4c
--- /dev/null
+++ b/src/roff/groff/groff.am
@@ -0,0 +1,87 @@
+# Copyright (C) 1993-2020 Free Software Foundation, Inc.
+#
+# Original Makefile.sub rewritten by
+# Bernd Warken <groff-bernd.warken-72@web.de>
+# and Werner LEMBERG <wl@gnu.org>
+#
+# Automake migration by
+# Bertrand Garrigues <bertrand.garrigues@laposte.net>
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+bin_PROGRAMS += groff
+groff_LDADD = \
+ libgroff.a \
+ lib/libgnu.a \
+ $(LIBM)
+groff_SOURCES = \
+ src/roff/groff/groff.cpp \
+ src/roff/groff/pipeline.c \
+ src/roff/groff/pipeline.h
+src/roff/groff/groff.$(OBJEXT): defs.h
+man1_MANS += src/roff/groff/groff.1
+EXTRA_DIST += src/roff/groff/groff.1.man
+
+groff_TESTS = \
+ src/roff/groff/tests/ab_works.sh \
+ src/roff/groff/tests/adjustment_works.sh \
+ src/roff/groff/tests/break_zero-length_output_line_sanely.sh \
+ src/roff/groff/tests/device_control_escapes_express_basic_latin.sh \
+ src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh \
+ src/roff/groff/tests/dot-cp_register_works.sh \
+ src/roff/groff/tests/dot-nm_register_works.sh \
+ src/roff/groff/tests/dot-nn_register_works.sh \
+ src/roff/groff/tests/evc_produces_no_output_if_invalid.sh \
+ src/roff/groff/tests/fp_should_not_traverse_directories.sh \
+ src/roff/groff/tests/handle_special_input_code_points.sh \
+ src/roff/groff/tests/html_works_with_grn_and_eqn.sh \
+ src/roff/groff/tests/initialization_is_quiet.sh \
+ src/roff/groff/tests/localization_works.sh \
+ src/roff/groff/tests/msoquiet_works.sh \
+ src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh \
+ src/roff/groff/tests/output_driver_C_and_G_options_work.sh \
+ src/roff/groff/tests/recognize_end_of_sentence.sh \
+ src/roff/groff/tests/regression_savannah_56555.sh \
+ src/roff/groff/tests/regression_savannah_58153.sh \
+ src/roff/groff/tests/regression_savannah_58162.sh \
+ src/roff/groff/tests/regression_savannah_58337.sh \
+ src/roff/groff/tests/regression_savannah_59202.sh \
+ src/roff/groff/tests/smoke-test_html_device.sh \
+ src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh \
+ src/roff/groff/tests/soquiet_works.sh \
+ src/roff/groff/tests/string_case_xform_errors.sh \
+ src/roff/groff/tests/string_case_xform_requests.sh \
+ src/roff/groff/tests/string_case_xform_unicode_escape.sh \
+ src/roff/groff/tests/substring_works.sh \
+ src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh
+TESTS += $(groff_TESTS)
+EXTRA_DIST += $(groff_TESTS)
+
+# required test artifacts
+EXTRA_DIST += \
+ src/roff/groff/tests/artifacts/HONEYPOT \
+ src/roff/groff/tests/artifacts/devascii/README
+
+groff_XFAIL_TESTS = \
+ src/roff/groff/tests/string_case_xform_unicode_escape.sh
+XFAIL_TESTS += $(groff_XFAIL_TESTS)
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/roff/groff/groff.cpp b/src/roff/groff/groff.cpp
new file mode 100644
index 0000000..4eb7329
--- /dev/null
+++ b/src/roff/groff/groff.cpp
@@ -0,0 +1,866 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// A front end for groff.
+
+#include "lib.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#include "errarg.h"
+#include "error.h"
+#include "stringclass.h"
+#include "cset.h"
+#include "font.h"
+#include "device.h"
+#include "pipeline.h"
+#include "nonposix.h"
+#include "relocate.h"
+#include "defs.h"
+
+#define GXDITVIEW "gxditview"
+
+// troff will be passed an argument of -rXREG=1 if the -X option is
+// specified
+#define XREG ".X"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+// The number of commands must be in sync with MAX_COMMANDS in
+// pipeline.h.
+
+// grap, chem, and ideal must come before pic;
+// tbl must come before eqn
+const int PRECONV_INDEX = 0;
+const int SOELIM_INDEX = PRECONV_INDEX + 1;
+const int REFER_INDEX = SOELIM_INDEX + 1;
+const int GRAP_INDEX = REFER_INDEX + 1;
+const int CHEM_INDEX = GRAP_INDEX + 1;
+const int IDEAL_INDEX = CHEM_INDEX + 1;
+const int PIC_INDEX = IDEAL_INDEX + 1;
+const int TBL_INDEX = PIC_INDEX + 1;
+const int GRN_INDEX = TBL_INDEX + 1;
+const int EQN_INDEX = GRN_INDEX + 1;
+const int TROFF_INDEX = EQN_INDEX + 1;
+const int POST_INDEX = TROFF_INDEX + 1;
+const int SPOOL_INDEX = POST_INDEX + 1;
+
+const int NCOMMANDS = SPOOL_INDEX + 1;
+
+class possible_command {
+ char *name;
+ string args;
+ char **argv;
+
+ void build_argv();
+public:
+ possible_command();
+ ~possible_command();
+ void clear_name();
+ void set_name(const char *);
+ void set_name(const char *, const char *);
+ const char *get_name();
+ void append_arg(const char *, const char * = 0 /* nullptr */);
+ void insert_arg(const char *);
+ void insert_args(string s);
+ void clear_args();
+ char **get_argv();
+ void print(int is_last, FILE *fp);
+};
+
+extern "C" const char *Version_string;
+
+int lflag = 0;
+char *spooler = 0 /* nullptr */;
+char *postdriver = 0 /* nullptr */;
+char *predriver = 0 /* nullptr */;
+bool need_postdriver = true;
+char *saved_path = 0 /* nullptr */;
+char *groff_bin_path = 0 /* nullptr */;
+char *groff_font_path = 0 /* nullptr */;
+
+possible_command commands[NCOMMANDS];
+
+int run_commands(int no_pipe);
+void print_commands(FILE *);
+void append_arg_to_string(const char *arg, string &str);
+void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno);
+const char *xbasename(const char *);
+
+void usage(FILE *stream);
+
+static char *xstrdup(const char *s) {
+ if (0 /* nullptr */ == s)
+ return const_cast<char *>(s);
+ char *str = strdup(s);
+ if (0 /* nullptr */ == str)
+ fatal("unable to copy string: %1", strerror(errno));
+ return str;
+}
+
+static void xputenv(const char *s) {
+ if (putenv(const_cast<char *>(s)) != 0)
+ fatal("unable to write to environment: %1", strerror(errno));
+ return;
+}
+
+static void xexit(int status) {
+ free(spooler);
+ free(predriver);
+ free(postdriver);
+ free(saved_path);
+ free(groff_bin_path);
+ free(groff_font_path);
+ exit(status);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ assert(NCOMMANDS <= MAX_COMMANDS);
+ string Pargs, Largs, Fargs;
+ int Kflag = 0;
+ int vflag = 0;
+ int Vflag = 0;
+ int zflag = 0;
+ int iflag = 0;
+ int Xflag = 0;
+ int oflag = 0;
+ int safer_flag = 1;
+ int is_xhtml = 0;
+ int eflag = 0;
+ int need_pic = 0;
+ int opt;
+ const char *command_prefix = getenv("GROFF_COMMAND_PREFIX");
+ const char *encoding = getenv("GROFF_ENCODING");
+ if (!command_prefix)
+ command_prefix = PROG_PREFIX;
+ commands[TROFF_INDEX].set_name(command_prefix, "troff");
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'v' },
+ { NULL, 0, 0, 0 }
+ };
+ while ((opt = getopt_long(
+ argc, argv,
+ "abcCd:D:eEf:F:gGhiI:jJkK:lL:m:M:"
+ "n:No:pP:r:RsStT:UvVw:W:XzZ",
+ long_options, NULL))
+ != EOF) {
+ char buf[3];
+ buf[0] = '-';
+ buf[1] = opt;
+ buf[2] = '\0';
+ switch (opt) {
+ case 'i':
+ iflag = 1;
+ break;
+ case 'I':
+ commands[GRN_INDEX].set_name(command_prefix, "grn");
+ commands[GRN_INDEX].append_arg("-M", optarg);
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ commands[SOELIM_INDEX].append_arg(buf, optarg);
+ // .psbb may need to search for files
+ commands[TROFF_INDEX].append_arg(buf, optarg);
+ // \X'ps:import' may need to search for files
+ Pargs += buf;
+ Pargs += optarg;
+ Pargs += '\0';
+ break;
+ case 'D':
+ commands[PRECONV_INDEX].set_name("preconv");
+ commands[PRECONV_INDEX].append_arg("-D", optarg);
+ break;
+ case 'K':
+ commands[PRECONV_INDEX].append_arg("-e", optarg);
+ Kflag = 1;
+ // fall through
+ case 'k':
+ commands[PRECONV_INDEX].set_name("preconv");
+ break;
+ case 't':
+ commands[TBL_INDEX].set_name(command_prefix, "tbl");
+ break;
+ case 'J':
+ // commands[IDEAL_INDEX].set_name(command_prefix, "gideal");
+ // need_pic = 1;
+ break;
+ case 'j':
+ commands[CHEM_INDEX].set_name(command_prefix, "chem");
+ need_pic = 1;
+ break;
+ case 'p':
+ commands[PIC_INDEX].set_name(command_prefix, "pic");
+ break;
+ case 'g':
+ commands[GRN_INDEX].set_name(command_prefix, "grn");
+ break;
+ case 'G':
+ commands[GRAP_INDEX].set_name(command_prefix, "grap");
+ need_pic = 1;
+ break;
+ case 'e':
+ eflag = 1;
+ commands[EQN_INDEX].set_name(command_prefix, "eqn");
+ break;
+ case 's':
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ break;
+ case 'R':
+ commands[REFER_INDEX].set_name(command_prefix, "refer");
+ break;
+ case 'z':
+ case 'a':
+ commands[TROFF_INDEX].append_arg(buf);
+ // fall through
+ case 'Z':
+ zflag++;
+ need_postdriver = false;
+ break;
+ case 'l':
+ lflag++;
+ break;
+ case 'V':
+ Vflag++;
+ break;
+ case 'v':
+ vflag = 1;
+ printf("GNU groff version %s\n", Version_string);
+ printf(
+ "Copyright (C) 2022 Free Software Foundation, Inc.\n"
+ "GNU groff comes with ABSOLUTELY NO WARRANTY.\n"
+ "You may redistribute copies of groff and its subprograms\n"
+ "under the terms of the GNU General Public License.\n"
+ "For more information about these matters, see the file\n"
+ "named COPYING.\n");
+ printf("\ncalled subprograms:\n\n");
+ fflush(stdout);
+ // Pass -v to all possible subprograms
+ commands[PRECONV_INDEX].append_arg(buf);
+ commands[CHEM_INDEX].append_arg(buf);
+ commands[IDEAL_INDEX].append_arg(buf);
+ commands[POST_INDEX].append_arg(buf);
+ // fall through
+ case 'C':
+ commands[SOELIM_INDEX].append_arg(buf);
+ commands[REFER_INDEX].append_arg(buf);
+ commands[PIC_INDEX].append_arg(buf);
+ commands[GRAP_INDEX].append_arg(buf);
+ commands[TBL_INDEX].append_arg(buf);
+ commands[GRN_INDEX].append_arg(buf);
+ commands[EQN_INDEX].append_arg(buf);
+ commands[TROFF_INDEX].append_arg(buf);
+ break;
+ case 'N':
+ commands[EQN_INDEX].append_arg(buf);
+ break;
+ case 'h':
+ usage(stdout);
+ break;
+ case 'E':
+ case 'b':
+ commands[TROFF_INDEX].append_arg(buf);
+ break;
+ case 'c':
+ commands[TROFF_INDEX].append_arg(buf);
+ break;
+ case 'S':
+ safer_flag = 1;
+ break;
+ case 'U':
+ safer_flag = 0;
+ break;
+ case 'T':
+ if (strcmp(optarg, "xhtml") == 0) {
+ // force soelim to aid the html preprocessor
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ Pargs += "-x";
+ Pargs += '\0';
+ Pargs += 'x';
+ Pargs += '\0';
+ is_xhtml = 1;
+ device = "html";
+ break;
+ }
+ if (strcmp(optarg, "html") == 0)
+ // force soelim to aid the html preprocessor
+ commands[SOELIM_INDEX].set_name(command_prefix, "soelim");
+ if (strcmp(optarg, "Xps") == 0) {
+ warning("-TXps option is obsolete: use -X -Tps instead");
+ device = "ps";
+ Xflag++;
+ }
+ else
+ device = optarg;
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ if (Fargs.length() > 0) {
+ Fargs += PATH_SEP_CHAR;
+ Fargs += optarg;
+ }
+ else
+ Fargs = optarg;
+ break;
+ case 'o':
+ oflag = 1;
+ // fall through
+ case 'f':
+ case 'm':
+ case 'r':
+ case 'd':
+ case 'n':
+ case 'w':
+ case 'W':
+ commands[TROFF_INDEX].append_arg(buf, optarg);
+ break;
+ case 'M':
+ commands[EQN_INDEX].append_arg(buf, optarg);
+ commands[GRAP_INDEX].append_arg(buf, optarg);
+ commands[GRN_INDEX].append_arg(buf, optarg);
+ commands[TROFF_INDEX].append_arg(buf, optarg);
+ break;
+ case 'P':
+ Pargs += optarg;
+ Pargs += '\0';
+ break;
+ case 'L':
+ append_arg_to_string(optarg, Largs);
+ break;
+ case 'X':
+ Xflag++;
+ need_postdriver = false;
+ break;
+ case '?':
+ usage(stderr);
+ xexit(EXIT_FAILURE);
+ break;
+ default:
+ assert(0 == "no case to handle option character");
+ break;
+ }
+ }
+ if (need_pic)
+ commands[PIC_INDEX].set_name(command_prefix, "pic");
+ if (encoding) {
+ commands[PRECONV_INDEX].set_name("preconv");
+ if (!Kflag && *encoding)
+ commands[PRECONV_INDEX].append_arg("-e", encoding);
+ }
+ if (!safer_flag) {
+ commands[TROFF_INDEX].insert_arg("-U");
+ commands[PIC_INDEX].append_arg("-U");
+ }
+ font::set_unknown_desc_command_handler(handle_unknown_desc_command);
+ const char *desc = font::load_desc();
+ if (0 /* nullptr */ == desc)
+ fatal("cannot load 'DESC' description file for device '%1'",
+ device);
+ if (need_postdriver && (0 /* nullptr */ == postdriver))
+ fatal_with_file_and_line(desc, 0, "device description file missing"
+ " 'postpro' directive");
+ if (predriver && !zflag) {
+ commands[TROFF_INDEX].insert_arg(commands[TROFF_INDEX].get_name());
+ commands[TROFF_INDEX].set_name(predriver);
+ // pass the device arguments to the predrivers as well
+ commands[TROFF_INDEX].insert_args(Pargs);
+ if (eflag && is_xhtml)
+ commands[TROFF_INDEX].insert_arg("-e");
+ if (vflag)
+ commands[TROFF_INDEX].insert_arg("-v");
+ }
+ const char *real_driver = 0 /* nullptr */;
+ if (Xflag) {
+ real_driver = postdriver;
+ postdriver = xstrdup(GXDITVIEW); // so we can free() it in xexit()
+ commands[TROFF_INDEX].append_arg("-r" XREG "=", "1");
+ }
+ if (postdriver)
+ commands[POST_INDEX].set_name(postdriver);
+ int gxditview_flag = postdriver
+ && strcmp(xbasename(postdriver), GXDITVIEW) == 0;
+ if (gxditview_flag && argc - optind == 1) {
+ commands[POST_INDEX].append_arg("-title");
+ commands[POST_INDEX].append_arg(argv[optind]);
+ commands[POST_INDEX].append_arg("-xrm");
+ commands[POST_INDEX].append_arg("*iconName:", argv[optind]);
+ string filename_string("|");
+ append_arg_to_string(argv[0], filename_string);
+ append_arg_to_string("-Z", filename_string);
+ for (int i = 1; i < argc; i++)
+ append_arg_to_string(argv[i], filename_string);
+ filename_string += '\0';
+ commands[POST_INDEX].append_arg("-filename");
+ commands[POST_INDEX].append_arg(filename_string.contents());
+ }
+ if (gxditview_flag && Xflag) {
+ string print_string(real_driver);
+ if (spooler) {
+ print_string += " | ";
+ print_string += spooler;
+ print_string += Largs;
+ }
+ print_string += '\0';
+ commands[POST_INDEX].append_arg("-printCommand");
+ commands[POST_INDEX].append_arg(print_string.contents());
+ }
+ const char *p = Pargs.contents();
+ const char *end = p + Pargs.length();
+ while (p < end) {
+ commands[POST_INDEX].append_arg(p);
+ p = strchr(p, '\0') + 1;
+ }
+ if (gxditview_flag)
+ commands[POST_INDEX].append_arg("-");
+ if (lflag && !vflag && !Xflag && spooler) {
+ commands[SPOOL_INDEX].set_name(BSHELL);
+ commands[SPOOL_INDEX].append_arg(BSHELL_DASH_C);
+ Largs += '\0';
+ Largs = spooler + Largs;
+ commands[SPOOL_INDEX].append_arg(Largs.contents());
+ }
+ if (zflag) {
+ commands[POST_INDEX].set_name(0 /* nullptr */);
+ commands[SPOOL_INDEX].set_name(0 /* nullptr */);
+ }
+ commands[TROFF_INDEX].append_arg("-T", device);
+ if (strcmp(device, "html") == 0) {
+ if (is_xhtml) {
+ if (oflag)
+ fatal("'-o' option is invalid with device 'xhtml'");
+ if (zflag)
+ commands[EQN_INDEX].append_arg("-Tmathml:xhtml");
+ else if (eflag)
+ commands[EQN_INDEX].clear_name();
+ }
+ else {
+ if (oflag)
+ fatal("'-o' option is invalid with device 'html'");
+ // html renders equations as images via ps
+ commands[EQN_INDEX].append_arg("-Tps:html");
+ }
+ }
+ else
+ commands[EQN_INDEX].append_arg("-T", device);
+
+ commands[GRN_INDEX].append_arg("-T", device);
+
+ int first_index;
+ for (first_index = 0; first_index < TROFF_INDEX; first_index++)
+ if (commands[first_index].get_name() != 0 /* nullptr */)
+ break;
+ if (optind < argc) {
+ if (argv[optind][0] == '-' && argv[optind][1] != '\0')
+ commands[first_index].append_arg("--");
+ for (int i = optind; i < argc; i++)
+ commands[first_index].append_arg(argv[i]);
+ if (iflag)
+ commands[first_index].append_arg("-");
+ }
+ if (Fargs.length() > 0) {
+ string e = "GROFF_FONT_PATH";
+ e += '=';
+ e += Fargs;
+ char *fontpath = getenv("GROFF_FONT_PATH");
+ if (fontpath && *fontpath) {
+ e += PATH_SEP_CHAR;
+ e += fontpath;
+ }
+ e += '\0';
+ groff_font_path = xstrdup(e.contents());
+ xputenv(groff_font_path);
+ }
+ {
+ // we save the original path in GROFF_PATH__ and put it into the
+ // environment -- troff will pick it up later.
+ char *path = getenv("PATH");
+ string g = "GROFF_PATH__";
+ g += '=';
+ if (path && *path)
+ g += path;
+ g += '\0';
+ saved_path = xstrdup(g.contents());
+ xputenv(saved_path);
+ char *binpath = getenv("GROFF_BIN_PATH");
+ string f = "PATH";
+ f += '=';
+ if (binpath && *binpath)
+ f += binpath;
+ else {
+ binpath = relocatep(BINPATH);
+ f += binpath;
+ }
+ if (path && *path) {
+ f += PATH_SEP_CHAR;
+ f += path;
+ }
+ f += '\0';
+ groff_bin_path = xstrdup(f.contents());
+ xputenv(groff_bin_path);
+ }
+ if (Vflag)
+ print_commands(Vflag == 1 ? stdout : stderr);
+ if (Vflag == 1)
+ xexit(EXIT_SUCCESS);
+ xexit(run_commands(vflag));
+}
+
+const char *xbasename(const char *s)
+{
+ if (!s)
+ return 0 /* nullptr */;
+ // DIR_SEPS[] are possible directory separator characters; see
+ // nonposix.h. We want the rightmost separator of all possible ones.
+ // Example: d:/foo\\bar.
+ const char *p = strrchr(s, DIR_SEPS[0]), *p1;
+ const char *sep = &DIR_SEPS[1];
+
+ while (*sep)
+ {
+ p1 = strrchr(s, *sep);
+ if (p1 && (!p || p1 > p))
+ p = p1;
+ sep++;
+ }
+ return p ? p + 1 : s;
+}
+
+void handle_unknown_desc_command(const char *command, const char *arg,
+ const char *filename, int lineno)
+{
+ if (strcmp(command, "print") == 0) {
+ if (arg == 0 /* nullptr */)
+ error_with_file_and_line(filename, lineno, "'print' directive"
+ " requires an argument");
+ else
+ spooler = xstrdup(arg);
+ return;
+ }
+ if (strcmp(command, "prepro") == 0) {
+ if (arg == 0 /* nullptr */)
+ error("'prepro' directive requires an argument");
+ else {
+ for (const char *p = arg; *p; p++)
+ if (csspace(*p)) {
+ error_with_file_and_line(filename, lineno, "invalid 'prepro'"
+ " directive argument '%1': program"
+ " name required", arg);
+ }
+ predriver = xstrdup(arg);
+ }
+ return;
+ }
+ if (strcmp(command, "postpro") == 0) {
+ if (arg == 0 /* nullptr */)
+ error_with_file_and_line(filename, lineno, "'postpro' directive"
+ " requires an argument");
+ else {
+ for (const char *p = arg; *p; p++)
+ if (csspace(*p)) {
+ error_with_file_and_line(filename, lineno, "invalid 'postpro'"
+ " directive argument '%1': program"
+ " name required", arg);
+ return;
+ }
+ postdriver = xstrdup(arg);
+ }
+ return;
+ }
+}
+
+void print_commands(FILE *fp)
+{
+ int last;
+ for (last = SPOOL_INDEX; last >= 0; last--)
+ if (commands[last].get_name() != 0 /* nullptr */)
+ break;
+ for (int i = 0; i <= last; i++)
+ if (commands[i].get_name() != 0 /* nullptr */)
+ commands[i].print(i == last, fp);
+}
+
+// Run the commands. Return the code with which to exit.
+
+int run_commands(int no_pipe)
+{
+ char **v[NCOMMANDS]; // vector of argv arrays to pipe together
+ int ncommands = 0;
+ for (int i = 0; i < NCOMMANDS; i++)
+ if (commands[i].get_name() != 0 /* nullptr */)
+ v[ncommands++] = commands[i].get_argv();
+ return run_pipeline(ncommands, v, no_pipe);
+}
+
+possible_command::possible_command()
+: name(0), argv(0)
+{
+}
+
+possible_command::~possible_command()
+{
+ free(name);
+ delete[] argv;
+}
+
+void possible_command::set_name(const char *s)
+{
+ free(name);
+ name = xstrdup(s);
+}
+
+void possible_command::clear_name()
+{
+ delete[] name;
+ delete[] argv;
+ name = NULL;
+ argv = NULL;
+}
+
+void possible_command::set_name(const char *s1, const char *s2)
+{
+ free(name);
+ name = (char*)malloc(strlen(s1) + strlen(s2) + 1);
+ strcpy(name, s1);
+ strcat(name, s2);
+}
+
+const char *possible_command::get_name()
+{
+ return name;
+}
+
+void possible_command::clear_args()
+{
+ args.clear();
+}
+
+void possible_command::append_arg(const char *s, const char *t)
+{
+ args += s;
+ if (t)
+ args += t;
+ args += '\0';
+}
+
+void possible_command::insert_arg(const char *s)
+{
+ string str(s);
+ str += '\0';
+ str += args;
+ args = str;
+}
+
+void possible_command::insert_args(string s)
+{
+ const char *p = s.contents();
+ const char *end = p + s.length();
+ int l = 0;
+ if (p >= end)
+ return;
+ // find the total number of arguments in our string
+ do {
+ l++;
+ p = strchr(p, '\0') + 1;
+ } while (p < end);
+ // now insert each argument preserving the order
+ for (int i = l - 1; i >= 0; i--) {
+ p = s.contents();
+ for (int j = 0; j < i; j++)
+ p = strchr(p, '\0') + 1;
+ insert_arg(p);
+ }
+}
+
+void possible_command::build_argv()
+{
+ if (argv)
+ return;
+ // Count the number of arguments.
+ int len = args.length();
+ int argc = 1;
+ char *p = 0 /* nullptr */;
+ if (len > 0) {
+ p = &args[0];
+ for (int i = 0; i < len; i++)
+ if (p[i] == '\0')
+ argc++;
+ }
+ // Build an argument vector.
+ argv = new char *[argc + 1];
+ argv[0] = name;
+ for (int i = 1; i < argc; i++) {
+ argv[i] = p;
+ p = strchr(p, '\0') + 1;
+ }
+ argv[argc] = 0 /* nullptr */;
+}
+
+void possible_command::print(int is_last, FILE *fp)
+{
+ build_argv();
+ if (IS_BSHELL(argv[0])
+ && argv[1] != 0 /* nullptr */
+ && strcmp(argv[1], BSHELL_DASH_C) == 0
+ && argv[2] != 0 /* nullptr */ && argv[3] == 0 /* nullptr */)
+ fputs(argv[2], fp);
+ else {
+ fputs(argv[0], fp);
+ string str;
+ for (int i = 1; argv[i] != 0 /* nullptr */; i++) {
+ str.clear();
+ append_arg_to_string(argv[i], str);
+ put_string(str, fp);
+ }
+ }
+ if (is_last)
+ putc('\n', fp);
+ else
+ fputs(" | ", fp);
+}
+
+void append_arg_to_string(const char *arg, string &str)
+{
+ str += ' ';
+ int needs_quoting = 0;
+ // Native Windows programs don't support '..' style of quoting, so
+ // always behave as if ARG included the single quote character.
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ int contains_single_quote = 1;
+#else
+ int contains_single_quote = 0;
+#endif
+ const char*p;
+ for (p = arg; *p != '\0'; p++)
+ switch (*p) {
+ case ';':
+ case '&':
+ case '(':
+ case ')':
+ case '|':
+ case '^':
+ case '<':
+ case '>':
+ case '\n':
+ case ' ':
+ case '\t':
+ case '\\':
+ case '"':
+ case '$':
+ case '?':
+ case '*':
+ needs_quoting = 1;
+ break;
+ case '\'':
+ contains_single_quote = 1;
+ break;
+ }
+ if (contains_single_quote || arg[0] == '\0') {
+ str += '"';
+ for (p = arg; *p != '\0'; p++)
+ switch (*p) {
+#if !(defined(_WIN32) && !defined(__CYGWIN__))
+ case '"':
+ case '\\':
+ case '$':
+ str += '\\';
+#else
+ case '"':
+ case '\\':
+ if (*p == '"' || (*p == '\\' && p[1] == '"'))
+ str += '\\';
+#endif
+ // fall through
+ default:
+ str += *p;
+ break;
+ }
+ str += '"';
+ }
+ else if (needs_quoting) {
+ str += '\'';
+ str += arg;
+ str += '\'';
+ }
+ else
+ str += arg;
+}
+
+char **possible_command::get_argv()
+{
+ build_argv();
+ return argv;
+}
+
+void usage(FILE *stream)
+{
+ // Add `J` to the cluster if we ever get ideal(1) support.
+ fprintf(stream,
+"usage: %s [-abcCeEgGijklNpRsStUVXzZ] [-d ctext] [-d string=text]"
+" [-D fallback-encoding] [-f font-family] [-F font-directory]"
+" [-I inclusion-directory] [-K input-encoding] [-L spooler-argument]"
+" [-m macro-package] [-M macro-directory] [-n page-number]"
+" [-o page-list] [-P postprocessor-argument] [-r cnumeric-expression]"
+" [-r register=numeric-expression] [-T output-device]"
+" [-w warning-category] [-W warning-category]"
+" [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ program_name, program_name, program_name);
+ if (stdout == stream) {
+ fputs(
+"\n"
+"groff (GNU roff) is a typesetting system that reads plain text input\n"
+"files that include formatting commands to produce output in\n"
+"PostScript, PDF, HTML, or DVI formats or for display to a terminal.\n"
+"See the groff(1) manual page.\n",
+ stream);
+ exit(EXIT_SUCCESS);
+ }
+}
+
+extern "C" {
+
+void c_error(const char *format, const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ error(format, arg1, arg2, arg3);
+}
+
+void c_fatal(const char *format, const char *arg1, const char *arg2,
+ const char *arg3)
+{
+ fatal(format, arg1, arg2, arg3);
+}
+
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/groff/pipeline.c b/src/roff/groff/pipeline.c
new file mode 100644
index 0000000..defafc2
--- /dev/null
+++ b/src/roff/groff/pipeline.c
@@ -0,0 +1,589 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_STRERROR
+#include <string.h>
+#else
+extern char *strerror();
+#endif
+
+#ifdef _POSIX_VERSION
+
+#include <sys/wait.h>
+#define PID_T pid_t
+
+#else /* not _POSIX_VERSION */
+
+/* traditional Unix */
+
+#define WIFEXITED(s) (((s) & 0377) == 0)
+#define WIFSTOPPED(s) (((s) & 0377) == 0177)
+#define WIFSIGNALED(s) (((s) & 0377) != 0 && (((s) & 0377) != 0177))
+#define WEXITSTATUS(s) (((s) >> 8) & 0377)
+#define WTERMSIG(s) ((s) & 0177)
+#define WSTOPSIG(s) (((s) >> 8) & 0377)
+
+#ifndef WCOREFLAG
+#define WCOREFLAG 0200
+#endif
+
+#define PID_T int
+
+#endif /* not _POSIX_VERSION */
+
+/* SVR4 uses WCOREFLG; Net 2 uses WCOREFLAG. */
+#ifndef WCOREFLAG
+#ifdef WCOREFLG
+#define WCOREFLAG WCOREFLG
+#endif /* WCOREFLG */
+#endif /* not WCOREFLAG */
+
+#ifndef WCOREDUMP
+#ifdef WCOREFLAG
+#define WCOREDUMP(s) ((s) & WCOREFLAG)
+#else /* not WCOREFLAG */
+#define WCOREDUMP(s) (0)
+#endif /* WCOREFLAG */
+#endif /* not WCOREDUMP */
+
+#include "pipeline.h"
+
+/* Prototype */
+int run_pipeline(int, char ***, int);
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void c_error(const char *, const char *, const char *,
+ const char *);
+extern void c_fatal(const char *, const char *, const char *,
+ const char *);
+extern const char *i_to_a(int); /* from libgroff */
+
+#ifdef __cplusplus
+}
+#endif
+
+static void sys_fatal(const char *);
+static const char *xstrsignal(int);
+
+
+#if defined(__MSDOS__) \
+ || (defined(_WIN32) && !defined(_UWIN) && !defined(__CYGWIN__)) \
+ || defined(__EMX__)
+
+#include <process.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "nonposix.h"
+
+static const char *sh = "sh";
+static const char *cmd = "cmd";
+static const char *command = "command";
+
+extern int strcasecmp(const char *, const char *);
+
+char *sbasename(const char *path)
+{
+ char *base;
+ const char *p1, *p2;
+
+ p1 = path;
+ if ((p2 = strrchr(p1, '\\'))
+ || (p2 = strrchr(p1, '/'))
+ || (p2 = strrchr(p1, ':')))
+ p1 = p2 + 1;
+ if ((p2 = strrchr(p1, '.'))
+ && ((strcasecmp(p2, ".exe") == 0)
+ || (strcasecmp(p2, ".com") == 0)))
+ ;
+ else
+ p2 = p1 + strlen(p1);
+
+ base = malloc((size_t)(p2 - p1));
+ strncpy(base, p1, p2 - p1);
+ *(base + (p2 - p1)) = '\0';
+
+ return(base);
+}
+
+/* Get the name of the system shell */
+char *system_shell_name(void)
+{
+ const char *shell_name;
+
+ /*
+ Use a Unixy shell if it's installed. Use SHELL if set; otherwise,
+ let spawnlp try to find sh; if that fails, use COMSPEC if set; if
+ not, try cmd.exe; if that fails, default to command.com.
+ */
+
+ if ((shell_name = getenv("SHELL")) != NULL)
+ ;
+ else if (spawnlp(_P_WAIT, sh, sh, "-c", ":", NULL) == 0)
+ shell_name = sh;
+ else if ((shell_name = getenv("COMSPEC")) != NULL)
+ ;
+ else if (spawnlp(_P_WAIT, cmd, cmd, "/c", ";", NULL) == 0)
+ shell_name = cmd;
+ else
+ shell_name = command;
+
+ return sbasename(shell_name);
+}
+
+const char *system_shell_dash_c(void)
+{
+ char *shell_name;
+ const char *dash_c;
+
+ shell_name = system_shell_name();
+
+ /* Assume that if the shell name ends in 'sh', it's Unixy */
+ if (strcasecmp(shell_name + strlen(shell_name) - strlen("sh"), "sh") == 0)
+ dash_c = "-c";
+ else
+ dash_c = "/c";
+
+ free(shell_name);
+ return dash_c;
+}
+
+int is_system_shell(const char *prog)
+{
+ int result;
+ char *this_prog, *system_shell;
+
+ if (!prog) /* paranoia */
+ return 0;
+
+ this_prog = sbasename(prog);
+ system_shell = system_shell_name();
+
+ result = strcasecmp(this_prog, system_shell) == 0;
+
+ free(this_prog);
+ free(system_shell);
+
+ return result;
+}
+
+#ifdef _WIN32
+
+/*
+ Windows 32 doesn't have fork(), so we need to start asynchronous child
+ processes with spawn() rather than exec(). If there is more than one
+ command, i.e., a pipeline, the parent must set up each child's I/O
+ redirection prior to the spawn. The original stdout must be restored
+ before spawning the last process in the pipeline, and the original
+ stdin must be restored in the parent after spawning the last process
+ and before waiting for any of the children.
+*/
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int i;
+ int last_input = 0; /* pacify some compilers */
+ int save_stdin = 0;
+ int save_stdout = 0;
+ int ret = 0;
+ char err_str[BUFSIZ];
+ PID_T pids[MAX_COMMANDS];
+
+ for (i = 0; i < ncommands; i++) {
+ int pdes[2];
+ PID_T pid;
+
+ /* If no_pipe is set, just run the commands in sequence
+ to show the version numbers */
+ if (ncommands > 1 && !no_pipe) {
+ /* last command doesn't need a new pipe */
+ if (i < ncommands - 1) {
+ if (pipe(pdes) < 0) {
+ sprintf(err_str, "%s: pipe", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ }
+ /* 1st command; writer */
+ if (i == 0) {
+ /* save stdin */
+ if ((save_stdin = dup(STDIN_FILENO)) < 0)
+ sys_fatal("dup stdin");
+ /* save stdout */
+ if ((save_stdout = dup(STDOUT_FILENO)) < 0)
+ sys_fatal("dup stdout");
+
+ /* connect stdout to write end of pipe */
+ if (dup2(pdes[1], STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(pdes[1]) < 0) {
+ sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /*
+ Save the read end of the pipe so that it can be connected to
+ stdin of the next program in the pipeline during the next
+ pass through the loop.
+ */
+ last_input = pdes[0];
+ }
+ /* reader and writer */
+ else if (i < ncommands - 1) {
+ /* connect stdin to read end of last pipe */
+ if (dup2(last_input, STDIN_FILENO) < 0) {
+ sprintf(err_str, " %s: dup2(stdin)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(last_input) < 0) {
+ sprintf(err_str, "%s: close(last_input)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* connect stdout to write end of new pipe */
+ if (dup2(pdes[1], STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(pdes[1]) < 0) {
+ sprintf(err_str, "%s: close(pipe[WRITE])", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ last_input = pdes[0];
+ }
+ /* last command; reader */
+ else {
+ /* connect stdin to read end of last pipe */
+ if (dup2(last_input, STDIN_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(stdin)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ if (close(last_input) < 0) {
+ sprintf(err_str, "%s: close(last_input)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* restore original stdout */
+ if (dup2(save_stdout, STDOUT_FILENO) < 0) {
+ sprintf(err_str, "%s: dup2(save_stdout))", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ /* close stdout copy */
+ if (close(save_stdout) < 0) {
+ sprintf(err_str, "%s: close(save_stdout)", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ }
+ }
+ if ((pid = spawnvp(_P_NOWAIT, commands[i][0], commands[i])) < 0) {
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ _exit(EXEC_FAILED_EXIT_STATUS);
+ }
+ pids[i] = pid;
+ }
+
+ if (ncommands > 1 && !no_pipe) {
+ /* restore original stdin if it was redirected */
+ if (dup2(save_stdin, STDIN_FILENO) < 0) {
+ sprintf(err_str, "dup2(save_stdin))");
+ sys_fatal(err_str);
+ }
+ /* close stdin copy */
+ if (close(save_stdin) < 0) {
+ sprintf(err_str, "close(save_stdin)");
+ sys_fatal(err_str);
+ }
+ }
+
+ for (i = 0; i < ncommands; i++) {
+ int status;
+ PID_T pid;
+
+ pid = pids[i];
+ if ((pid = WAIT(&status, pid, _WAIT_CHILD)) < 0) {
+ sprintf(err_str, "%s: wait", commands[i][0]);
+ sys_fatal(err_str);
+ }
+ else if (status != 0)
+ ret |= 1;
+ }
+ return ret;
+}
+
+#else /* not _WIN32 */
+
+/* MS-DOS doesn't have 'fork', so we need to simulate the pipe by
+ running the programs in sequence with standard streams redirected to
+ and from temporary files.
+*/
+
+
+/* A signal handler that just records that a signal has happened. */
+static int child_interrupted;
+
+static RETSIGTYPE signal_catcher(int signo)
+{
+ child_interrupted++;
+}
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int save_stdin = dup(0);
+ int save_stdout = dup(1);
+ char *tmpfiles[2];
+ int infile = 0;
+ int outfile = 1;
+ int i, f, ret = 0;
+
+ /* Choose names for a pair of temporary files to implement the pipeline.
+ Microsoft's 'tempnam' uses the directory specified by 'getenv("TMP")'
+ if it exists; in case it doesn't, try the GROFF alternatives, or
+ 'getenv("TEMP")' as last resort -- at least one of these had better
+ be set, since Microsoft's default has a high probability of failure. */
+ char *tmpdir;
+ if ((tmpdir = getenv("GROFF_TMPDIR")) == NULL
+ && (tmpdir = getenv("TMPDIR")) == NULL)
+ tmpdir = getenv("TEMP");
+
+ /* Don't use 'tmpnam' here: Microsoft's implementation yields unusable
+ file names if current directory is on network share with read-only
+ root. */
+ tmpfiles[0] = tempnam(tmpdir, NULL);
+ tmpfiles[1] = tempnam(tmpdir, NULL);
+
+ for (i = 0; i < ncommands; i++) {
+ int exit_status;
+ RETSIGTYPE (*prev_handler)(int);
+
+ if (i && !no_pipe) {
+ /* redirect stdin from temp file */
+ f = open(tmpfiles[infile], O_RDONLY|O_BINARY, 0666);
+ if (f < 0)
+ sys_fatal("open stdin");
+ if (dup2(f, 0) < 0)
+ sys_fatal("dup2 stdin");
+ if (close(f) < 0)
+ sys_fatal("close stdin");
+ }
+ if ((i < ncommands - 1) && !no_pipe) {
+ /* redirect stdout to temp file */
+ f = open(tmpfiles[outfile], O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
+ if (f < 0)
+ sys_fatal("open stdout");
+ if (dup2(f, 1) < 0)
+ sys_fatal("dup2 stdout");
+ if (close(f) < 0)
+ sys_fatal("close stdout");
+ }
+ else if (dup2(save_stdout, 1) < 0)
+ sys_fatal("restore stdout");
+
+ /* run the program */
+ child_interrupted = 0;
+ prev_handler = signal(SIGINT, signal_catcher);
+ exit_status = spawnvp(P_WAIT, commands[i][0], commands[i]);
+ signal(SIGINT, prev_handler);
+ if (child_interrupted) {
+ c_error("%1: Interrupted", commands[i][0], (char *)0, (char *)0);
+ ret |= 2;
+ }
+ else if (exit_status < 0) {
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ ret |= 4;
+ }
+ if (exit_status != 0)
+ ret |= 1;
+ /* There's no sense to continue with the pipe if one of the
+ programs has ended abnormally, is there? */
+ if (ret != 0)
+ break;
+ /* swap temp files: make output of this program be input for the next */
+ infile = 1 - infile;
+ outfile = 1 - outfile;
+ }
+ if (dup2(save_stdin, 0) < 0)
+ sys_fatal("restore stdin");
+ unlink(tmpfiles[0]);
+ unlink(tmpfiles[1]);
+ return ret;
+}
+
+#endif /* not _WIN32 */
+
+#else /* not __MSDOS__, not _WIN32 */
+
+int run_pipeline(int ncommands, char ***commands, int no_pipe)
+{
+ int i;
+ int last_input = 0;
+ PID_T pids[MAX_COMMANDS];
+ int ret = 0;
+ int proc_count = ncommands;
+
+ for (i = 0; i < ncommands; i++) {
+ int pdes[2];
+ PID_T pid;
+
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (pipe(pdes) < 0)
+ sys_fatal("pipe");
+ }
+ pid = fork();
+ if (pid < 0)
+ sys_fatal("fork");
+ if (pid == 0) {
+ /* child */
+ if (last_input != 0) {
+ if (close(0) < 0)
+ sys_fatal("close");
+ if (dup(last_input) < 0)
+ sys_fatal("dup");
+ if (close(last_input) < 0)
+ sys_fatal("close");
+ }
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (close(1) < 0)
+ sys_fatal("close");
+ if (dup(pdes[1]) < 0)
+ sys_fatal("dup");
+ if (close(pdes[1]) < 0)
+ sys_fatal("close");
+ if (close(pdes[0]))
+ sys_fatal("close");
+ }
+ execvp(commands[i][0], commands[i]);
+ c_error("couldn't exec %1: %2",
+ commands[i][0], strerror(errno), (char *)0);
+ _exit(EXEC_FAILED_EXIT_STATUS);
+ }
+ /* in the parent */
+ if (last_input != 0) {
+ if (close(last_input) < 0)
+ sys_fatal("close");
+ }
+ if ((i != ncommands - 1) && !no_pipe) {
+ if (close(pdes[1]) < 0)
+ sys_fatal("close");
+ last_input = pdes[0];
+ }
+ pids[i] = pid;
+ }
+ while (proc_count > 0) {
+ int status;
+ PID_T pid = wait(&status);
+
+ if (pid < 0)
+ sys_fatal("wait");
+ for (i = 0; i < ncommands; i++)
+ if (pids[i] == pid) {
+ pids[i] = -1;
+ --proc_count;
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+#ifdef SIGPIPE
+ if (sig == SIGPIPE) {
+ if (i == ncommands - 1) {
+ /* This works around a problem that occurred when using the
+ rerasterize action in gxditview. What seemed to be
+ happening (on SunOS 4.1.1) was that pclose() closed the
+ pipe and waited for groff, gtroff got a SIGPIPE, but
+ gpic blocked writing to gtroff, and so groff blocked
+ waiting for gpic and gxditview blocked waiting for
+ groff. I don't understand why gpic wasn't getting a
+ SIGPIPE. */
+ int j;
+
+ for (j = 0; j < ncommands; j++)
+ if (pids[j] > 0)
+ (void)kill(pids[j], SIGPIPE);
+ }
+ }
+ else
+#endif /* SIGPIPE */
+ {
+ c_error("%1: %2%3",
+ commands[i][0],
+ xstrsignal(sig),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+ ret |= 2;
+ }
+ }
+ else if (WIFEXITED(status)) {
+ int exit_status = WEXITSTATUS(status);
+
+ if (exit_status == EXEC_FAILED_EXIT_STATUS)
+ ret |= 4;
+ else if (exit_status != 0)
+ ret |= 1;
+ }
+ else
+ c_error("unexpected status %1", i_to_a(status), (char *)0,
+ (char *)0);
+ break;
+ }
+ }
+ return ret;
+}
+
+#endif /* not __MSDOS__, not _WIN32 */
+
+static void sys_fatal(const char *s)
+{
+ c_fatal("%1: %2", s, strerror(errno), (char *)0);
+}
+
+static const char *xstrsignal(int n)
+{
+ static char buf[sizeof("Signal ") + 1 + sizeof(int) * 3];
+
+#ifdef NSIG
+#if HAVE_DECL_STRSIGNAL
+ if (n >= 0 && n < NSIG && strsignal(n) != 0)
+ return strsignal(n);
+#else
+#if HAVE_DECL_SYS_SIGLIST
+ if (n >= 0 && n < NSIG && sys_siglist[n] != 0)
+ return sys_siglist[n];
+#endif /* HAVE_DECL_SYS_SIGLIST */
+#endif /* HAVE_DECL_STRSIGNAL */
+#endif /* NSIG */
+ sprintf(buf, "Signal %d", n);
+ return buf;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/groff/pipeline.h b/src/roff/groff/pipeline.h
new file mode 100644
index 0000000..17c02a6
--- /dev/null
+++ b/src/roff/groff/pipeline.h
@@ -0,0 +1,30 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef __cplusplus
+extern "C" {
+ int run_pipeline(int, char ***, int);
+}
+#endif
+
+/* run_pipeline can handle at most this many commands,
+ see the const numbers in groff.cpp */
+#define MAX_COMMANDS 15
+
+/* Children exit with this status if execvp fails. */
+#define EXEC_FAILED_EXIT_STATUS 0xff
diff --git a/src/roff/groff/tests/ab_works.sh b/src/roff/groff/tests/ab_works.sh
new file mode 100755
index 0000000..b353837
--- /dev/null
+++ b/src/roff/groff/tests/ab_works.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (C) 2021-2022 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Verify exit status and regression-test Savannah #60782.
+#
+# We don't test the X11 devices because groff launches an X client,
+# which has to be killed. Using "-z" to avoid this masks the bug.
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+for d in ascii cp1047 dvi html latin1 lbp lj4 pdf ps utf8
+do
+ echo "verifying exit status of .ab request using $d device" >&2
+ printf '.ab\n' | "$groff" -Z -T$d
+ test $? -eq 1 || exit 1
+done
+
+echo "verifying empty output of .ab request with no arguments" >&2
+OUT=$(printf '.ab\n' | "$groff" -Z -Tascii 2>&1)
+test "$OUT" = "" || exit 1
+
+echo "verifying that arguments to .ab request go to stderr" >&2
+OUT=$(printf '.ab foo\n' | "$groff" -Z -Tascii 2>&1 > /dev/null)
+test "$OUT" = "foo" || exit 1
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/adjustment_works.sh b/src/roff/groff/tests/adjustment_works.sh
new file mode 100755
index 0000000..e4cd65d
--- /dev/null
+++ b/src/roff/groff/tests/adjustment_works.sh
@@ -0,0 +1,92 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='.pl 1v
+.ll 9n
+foo bar\p
+.na
+foo bar\p
+.ad l
+foo bar\p
+.na
+foo bar\p
+.ad b
+foo bar\p
+.na
+foo bar\p
+.ad c
+foo bar\p
+.na
+foo bar\p
+.ad r
+foo bar\p
+.na
+foo bar\p
+.ad
+foo bar\p
+.ad b
+.ad 100
+foo bar\p'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii)
+B='foo bar' # 3 spaces
+L='foo bar' # left or off
+C=' foo bar' # trailing space truncated
+R=' foo bar' # 2 leading spaces
+
+echo "verifying default adjustment mode 'b'" >&2
+echo "$OUTPUT" | sed -n '1p' | grep -Fqx "$B"
+
+echo "verifying that .na works" >&2
+echo "$OUTPUT" | sed -n '2p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'l'" >&2
+echo "$OUTPUT" | sed -n '3p' | grep -Fqx "$L"
+
+echo "verifying that .na works after '.ad l'" >&2
+echo "$OUTPUT" | sed -n '4p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'b'" >&2
+echo "$OUTPUT" | sed -n '5p' | grep -Fqx "$B"
+
+echo "verifying that .na works after '.ad b'" >&2
+echo "$OUTPUT" | sed -n '6p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'c'" >&2
+echo "$OUTPUT" | sed -n '7p' | grep -Fqx "$C"
+
+echo "verifying that .na works after '.ad c'" >&2
+echo "$OUTPUT" | sed -n '8p' | grep -Fqx "$L"
+
+echo "verifying adjustment mode 'r'" >&2
+echo "$OUTPUT" | sed -n '9p' | grep -Fqx "$R"
+
+echo "verifying that .na works after '.ad r'" >&2
+echo "$OUTPUT" | sed -n '10p' | grep -Fqx "$L"
+
+echo "verifying that '.ad' restores previous adjustment mode" >&2
+echo "$OUTPUT" | sed -n '11p' | grep -Fqx "$R"
+
+echo "verifying that out-of-range adjustment mode 100 is ignored" >&2
+echo "$OUTPUT" | sed -n '12p' | grep -Fqx "$B"
diff --git a/src/roff/groff/tests/artifacts/HONEYPOT b/src/roff/groff/tests/artifacts/HONEYPOT
new file mode 100644
index 0000000..51a57dd
--- /dev/null
+++ b/src/roff/groff/tests/artifacts/HONEYPOT
@@ -0,0 +1,15 @@
+name HONEYPOT
+spacewidth 24
+charset
+a 0 0 77
+b 0 0 79
+d 0 0 76
+e 0 0 82
+i 0 0 105
+l 0 0 65
+m 0 0 109
+o 0 0 86
+r 0 0 73
+s 0 0 115
+w 0 0 69
+y 0 0 121
diff --git a/src/roff/groff/tests/artifacts/devascii/README b/src/roff/groff/tests/artifacts/devascii/README
new file mode 100644
index 0000000..d89ba03
--- /dev/null
+++ b/src/roff/groff/tests/artifacts/devascii/README
@@ -0,0 +1 @@
+This directory is intentionally empty (apart from this file).
diff --git a/src/roff/groff/tests/break_zero-length_output_line_sanely.sh b/src/roff/groff/tests/break_zero-length_output_line_sanely.sh
new file mode 100755
index 0000000..8d6deb0
--- /dev/null
+++ b/src/roff/groff/tests/break_zero-length_output_line_sanely.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Do not core dump when attempting to distribute a space amount of zero
+# if someone sets the line length to zero. See Savannah #61089.
+# Reproducer courtesy of John Gardner.
+
+INPUT='.de _
+. na
+. nh
+. ll 0
+. di A
+\&\\$1
+. di
+. br
+..
+._ " XYZ"
+.A
+'
+
+OUTPUT=$(printf "%s" "$INPUT" | "$groff" -Tascii)
+echo "$OUTPUT" | grep -qx XYZ
diff --git a/src/roff/groff/tests/device_control_escapes_express_basic_latin.sh b/src/roff/groff/tests/device_control_escapes_express_basic_latin.sh
new file mode 100755
index 0000000..6418913
--- /dev/null
+++ b/src/roff/groff/tests/device_control_escapes_express_basic_latin.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+fail=
+
+# Confirm translation of a groff special character escape sequence to a
+# basic Latin character when used in a device control escape sequence.
+#
+# $1 is the special character escape _without_ the leading backslash.
+# $2 is the expected output character _shell-quoted as necessary_.
+# $3 is a human-readable glyph description for the test log.
+# $4 is the groff -T device name under test.
+check_char () {
+ sc=$1
+ output=$2
+ description=$3
+ device=$4
+ printf 'checking conversion of \\%s to %s (%s) on device %s' \
+ "$sc" "$output" "$description" "$device" >&2
+ if ! printf '\\X#\\%s %s#\n' "$sc" "$desc" | "$groff" -T$device -Z \
+ | grep -Fqx 'x X '$output' '
+ then
+ printf '...FAILED' >&2
+ fail=yes
+ fi
+ printf '\n' >&2
+}
+
+for device in utf8 html
+do
+ check_char - - "minus sign" $device
+ check_char '[aq]' "'" "neutral apostrophe" $device
+ check_char '[dq]' '"' "double quote" $device
+ check_char '[ga]' '`' "grave accent" $device
+ check_char '[ha]' ^ "caret/hat" $device
+ check_char '[rs]' '\' "reverse solidus/backslash" $device
+ check_char '[ti]' '~' "tilde" $device
+done
+
+test -z "$fail" || exit 1
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh b/src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh
new file mode 100755
index 0000000..4f4a12a
--- /dev/null
+++ b/src/roff/groff/tests/do_not_loop_infinitely_when_breaking_cjk.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='ン
+ã‚ã‚AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+'
+
+echo "$DOC" | "$groff" -D utf8 -Tutf8 -mja
diff --git a/src/roff/groff/tests/dot-cp_register_works.sh b/src/roff/groff/tests/dot-cp_register_works.sh
new file mode 100755
index 0000000..ffbb402
--- /dev/null
+++ b/src/roff/groff/tests/dot-cp_register_works.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+DOC='.pl 1v
+A
+.do if 1 \n[.cp] \" Get initial compatibility state (depends on -C).
+B
+.do if 1 \n[.cp] \" Did observing the state change it?
+.cp 1
+C
+.do if 1 \n[.cp] \" Saved compatibility state should be 1 now.
+.cp 0
+D
+.do if 1 \n[.cp] \" Verify 1->0 transition.
+.cp 1
+E
+.do if 1 \n[.cp] \" Verify 0->1 transition.
+.cp 0
+F
+.if !\n[.C] \n[.cp] \" Outside of .do context, should return -1.
+'
+
+set -e
+
+printf "%s" "$DOC" | "$groff" -Tascii \
+ | grep -x "A 0 B 0 C 1 D 0 E 1 F -1"
+
+printf "%s" "$DOC" | "$groff" -C -Tascii \
+ | grep -x "A 1 B 1 C 1 D 0 E 1 F -1"
diff --git a/src/roff/groff/tests/dot-nm_register_works.sh b/src/roff/groff/tests/dot-nm_register_works.sh
new file mode 100755
index 0000000..809bbd3
--- /dev/null
+++ b/src/roff/groff/tests/dot-nm_register_works.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+DOC='\
+.nf
+foo (\n[.nm])
+.nm 1
+bar (\n[.nm])
+.nn
+baz (\n[.nm])
+.nm
+qux (\n[.nm])
+.fi
+'
+
+set -e
+
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx 'foo (0)'
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx ' 1 bar (1)'
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx 'baz (1)'
+printf '%s' "$DOC" | "$groff" -T utf8 | grep -Fqx 'qux (0)'
diff --git a/src/roff/groff/tests/dot-nn_register_works.sh b/src/roff/groff/tests/dot-nn_register_works.sh
new file mode 100755
index 0000000..0998cb0
--- /dev/null
+++ b/src/roff/groff/tests/dot-nn_register_works.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Unit test .nn register.
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+input='.ec @
+.de is-numbered
+. nop This line
+. ie (@@n[.nm] & (1-@@n[.nn])) IS
+. el ISN'"'"'T
+. nop numbered.
+. br
+..
+Test line numbering.
+.is-numbered
+.nm 1
+.nn 2
+.is-numbered
+.is-numbered
+.is-numbered
+.nm
+.is-numbered
+.pl @n[nl]u'
+
+# Apply line numbers to the output externally for easy grepping.
+output=$(echo "$input" | $groff -Tascii | nl)
+echo "$output"
+
+echo "verifying that line 1 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+1[[:space:]]+Test line numbering\." || wail
+
+echo "verifying that line 2 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+2[[:space:]]+This line ISN'T" || wail
+
+echo "verifying that line 3 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+3[[:space:]]+This line ISN'T" || wail
+
+echo "verifying that line 4 is numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+4[[:space:]]+1 +This line IS numbered" || wail
+
+echo "verifying that line 5 isn't numbered" >&2
+echo "$output" | \
+ grep -Eq "[[:space:]]+5[[:space:]]+This line ISN'T" || wail
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/evc_produces_no_output_if_invalid.sh b/src/roff/groff/tests/evc_produces_no_output_if_invalid.sh
new file mode 100755
index 0000000..47b0d1d
--- /dev/null
+++ b/src/roff/groff/tests/evc_produces_no_output_if_invalid.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #60913.
+
+test -z "$(printf '.evc foo bar\n' | "$groff")"
diff --git a/src/roff/groff/tests/fp_should_not_traverse_directories.sh b/src/roff/groff/tests/fp_should_not_traverse_directories.sh
new file mode 100755
index 0000000..f60f42f
--- /dev/null
+++ b/src/roff/groff/tests/fp_should_not_traverse_directories.sh
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #61424.
+#
+# The `fp` request should not be able to access font description files
+# outside of the device and font description search path (configurable
+# with the -F option and GROFF_FONT_PATH environment variable).
+#
+# An absolute file name _won't_ work: it gets dev\*[.T]/ stuck on the
+# front of it by libgroff.
+#
+# Locate directory containing our test artifacts.
+artifact_dir=
+base=src/roff/groff/tests
+device=artifacts
+
+for buildroot in . .. ../..
+do
+ d=$buildroot/$base/$device
+ if [ -d "$d" ]
+ then
+ artifact_dir=$d
+ break
+ fi
+done
+
+# If we can't find it, we can't test.
+test -z "$artifact_dir" && exit 77 # skip
+
+input='.fp 5 ../HONEYPOT
+.ft 5
+word
+.fp 5 HONEYPOT ../HONEYPOT
+.ft HONEYPOT
+.br
+my word is able
+.pl \n[nl]u'
+
+output=$(printf "%s" "$input" | "$groff" -b -ww -F "$artifact_dir" \
+ -Tascii)
+echo "$output" | grep -Fx word
+
+# vim:set ai et sw=4 ts=4 tw=72:
diff --git a/src/roff/groff/tests/handle_special_input_code_points.sh b/src/roff/groff/tests/handle_special_input_code_points.sh
new file mode 100755
index 0000000..c996150
--- /dev/null
+++ b/src/roff/groff/tests/handle_special_input_code_points.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Regression-test Savannah #58962.
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+input='.if " "\~" .tm input no-break space matches \\~
+.if "­"\%" .tm input soft hyphen matches \\%'
+
+fail=
+
+wail () {
+ echo "...FAILED"
+ fail=yes
+}
+
+output=$(printf "%s\n" "$input" | "$groff" -Z 2>&1)
+echo "$output"
+
+printf "checking that input no-break space is mapped to \\~\n"
+echo "$output" | grep -qx 'input no-break space matches \\~' || wail
+
+printf "checking that input soft hyphen is mapped to \\%%\n"
+echo "$output" | grep -qx 'input soft hyphen matches \\%' || wail
+
+test -z "$fail"
diff --git a/src/roff/groff/tests/html_works_with_grn_and_eqn.sh b/src/roff/groff/tests/html_works_with_grn_and_eqn.sh
new file mode 100755
index 0000000..e070944
--- /dev/null
+++ b/src/roff/groff/tests/html_works_with_grn_and_eqn.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Keep this list of programs in sync with GROFF_CHECK_GROHTML_PROGRAMS
+# in m4/groff.m4.
+for cmd in pnmcrop pnmcut pnmtopng pnmtops psselect
+do
+ if ! command -v $cmd >/dev/null
+ then
+ echo "cannot locate '$cmd' command; skipping test" >&2
+ exit 77 # skip
+ fi
+done
+
+# Commit c71b4ef4aa provoked an infinite loop in post-grohtml with these
+# preprocessors.
+
+input='.EQ
+gsize 12
+delim $$
+.EN
+.pp
+.pp
+The faster clocks are $ PN $'
+
+output=$("$groff" -b -ww -Thtml -eg -me "$input")
+test -n "$output"
diff --git a/src/roff/groff/tests/initialization_is_quiet.sh b/src/roff/groff/tests/initialization_is_quiet.sh
new file mode 100755
index 0000000..38ac81f
--- /dev/null
+++ b/src/roff/groff/tests/initialization_is_quiet.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (C) 2021-2023 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=YES
+}
+
+# Regression-test Savannah #60874.
+#
+# groff should start up in any supported locale, in compatibility mode
+# or not, without producing diagnostics.
+
+# Keep preconv from being run.
+#
+# The "unset" in Solaris /usr/xpg4/bin/sh can actually fail.
+if ! unset GROFF_ENCODING
+then
+ echo "unable to clear environment; skipping" >&2
+ exit 77
+fi
+
+for compat in "" " -C"
+do
+ for locale in cs de en fr it ja sv zh
+ do
+ echo testing \"-m $locale$compat\" >&2
+ output=$("$groff" -ww -m $locale$compat -a </dev/null 2>/dev/null)
+ error=$("$groff" -ww -m $locale$compat -z </dev/null 2>&1)
+ test -n "$error" && echo "$error"
+ test -n "$output" && echo "$output"
+ test -n "$error$output" && wail
+ done
+done
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=4 tabstop=4 textwidth=72:
diff --git a/src/roff/groff/tests/localization_works.sh b/src/roff/groff/tests/localization_works.sh
new file mode 100755
index 0000000..0585259
--- /dev/null
+++ b/src/roff/groff/tests/localization_works.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+DOC='\*[locale]'
+
+echo "testing default localization (English)" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii)
+echo "$OUTPUT" | grep -qx english
+
+echo "testing Czech localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m cs)
+echo "$OUTPUT" | grep -qx czech
+
+echo "testing German localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m de)
+echo "$OUTPUT" | grep -qx german
+
+echo "testing English localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m en)
+echo "$OUTPUT" | grep -qx english
+
+echo "testing French localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m fr)
+echo "$OUTPUT" | grep -qx french
+
+echo "testing Italian localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m it)
+echo "$OUTPUT" | grep -qx italian
+
+echo "testing Japanese localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m ja)
+echo "$OUTPUT" | grep -qx japanese
+
+echo "testing Swedish localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m sv)
+echo "$OUTPUT" | grep -qx swedish
+
+echo "testing Chinese localization" >&2
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii -m zh)
+echo "$OUTPUT" | grep -qx chinese
diff --git a/src/roff/groff/tests/msoquiet_works.sh b/src/roff/groff/tests/msoquiet_works.sh
new file mode 100755
index 0000000..80c085a
--- /dev/null
+++ b/src/roff/groff/tests/msoquiet_works.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+DOC='.msoquiet nonexistent'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii 2>&1)
+echo "$OUTPUT"
+
+echo "testing that .msoquiet of nonexistent file produces no warning" \
+ >&2
+test -z "$OUTPUT"
diff --git a/src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh b/src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh
new file mode 100755
index 0000000..b2c17bc
--- /dev/null
+++ b/src/roff/groff/tests/on_latin1_device_oq_is_0x27.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# This test fails with some versions of GNU Bash, such as 3.2; the here
+# document nested within a command substitution confuses it.
+#
+# https://lists.gnu.org/archive/html/bug-bash/2017-02/msg00024.html
+
+expected="' = '"
+actual=$(printf '.pl 1v\n\\[oq] = '"'"'\n' | "$groff" -Tlatin1)
+test "$actual" = "$expected"
diff --git a/src/roff/groff/tests/output_driver_C_and_G_options_work.sh b/src/roff/groff/tests/output_driver_C_and_G_options_work.sh
new file mode 100755
index 0000000..2bb1cc3
--- /dev/null
+++ b/src/roff/groff/tests/output_driver_C_and_G_options_work.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Feed groff empty input documents and verify that expected comments
+# emerge from the output drivers.
+
+# Expect Creator: and CreationDate: comments.
+echo "testing presence of Creator: comment in HTML output" >&2
+echo | "$groff" -Thtml | grep -Fq '<!-- Creator:'
+
+echo "testing presence of CreationDate: comment in HTML output" >&2
+echo | "$groff" -Thtml | grep -Fq '<!-- CreationDate:'
+
+# Make sure the options are recognized so we can distinguish a match
+# failure. We can't use -Z or -z because they keep the output driver
+# from running at all.
+for OPT in -C -G
+do
+ if ! echo | "$groff" -Thtml -P$OPT > /dev/null
+ then
+ echo "option $OPT not recognized!" >&2
+ exit 2
+ fi
+done
+
+# Now shut them off.
+echo "testing absence of Creator: comment in HTML output" >&2
+! echo | "$groff" -Thtml -P-G | grep -Fq '<!-- Creator:'
+
+echo "testing absence of CreationDate: comment in HTML output" >&2
+! echo | "$groff" -Thtml -P-C | grep -Fq '<!-- CreationDate:'
diff --git a/src/roff/groff/tests/recognize_end_of_sentence.sh b/src/roff/groff/tests/recognize_end_of_sentence.sh
new file mode 100755
index 0000000..aa1e4dc
--- /dev/null
+++ b/src/roff/groff/tests/recognize_end_of_sentence.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Verify that the characters trailing the period are all transparent for
+# purposes of end-of-sentence recognition. We use UTF-8 so we won't get
+# warnings about \[dg] and \[dd] missing from other encodings.
+#
+# Also confirm that we get _two_ spaces after the end of a sentence.
+
+"$groff" -Tutf8 <<EOF | grep -qE 'Eat\.[^ ]+ Drink\.'
+.pl 1v
+Eat."')]*\[dg]\[dd]\[rq]\[cq]
+Drink.
+EOF
diff --git a/src/roff/groff/tests/regression_savannah_56555.sh b/src/roff/groff/tests/regression_savannah_56555.sh
new file mode 100755
index 0000000..3d13f16
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_56555.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Check for segfault if we try to write a glyph before setting up.
+# Savannah #56555.
+"$groff" >/dev/null <<EOF
+\!ta
+EOF
diff --git a/src/roff/groff/tests/regression_savannah_58153.sh b/src/roff/groff/tests/regression_savannah_58153.sh
new file mode 100755
index 0000000..25771fd
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_58153.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (C) 2020-2023 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Ensure that we get backtrace output across file and pipe boundaries.
+# Savannah #58153.
+OUT=$("$groff" -b -ww -U 2>&1 >/dev/null <<EOF
+.ec @
+.pso printf '@s[-20]'
+EOF
+)
+
+set -e
+
+printf "%s\n" "$OUT" | grep -qw 'backtrace: pipe'
+printf "%s\n" "$OUT" | grep -qw 'backtrace: file'
diff --git a/src/roff/groff/tests/regression_savannah_58162.sh b/src/roff/groff/tests/regression_savannah_58162.sh
new file mode 100755
index 0000000..d744e99
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_58162.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Compatibility mode should not get shut off by macro file inclusion.
+printf '.ds FOO FAIL\n\\*[FOO]' | "$groff" -C -Tutf8 | grep -Fx 'FOO]'
diff --git a/src/roff/groff/tests/regression_savannah_58337.sh b/src/roff/groff/tests/regression_savannah_58337.sh
new file mode 100755
index 0000000..7928651
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_58337.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# groff should ignore negative inter-word and inter-sentence space
+# sizes. And certainly not fail an assertion. Savannah #58337.
+"$groff" -Tascii <<EOF | grep -Fqx 'A B. C.'
+.pl 1v
+.ss -1 -1
+A B.
+C.
+EOF
diff --git a/src/roff/groff/tests/regression_savannah_59202.sh b/src/roff/groff/tests/regression_savannah_59202.sh
new file mode 100755
index 0000000..6d8bdc1
--- /dev/null
+++ b/src/roff/groff/tests/regression_savannah_59202.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# troff should not segfault when its standard output is closed.
+# Savannah #59202.
+
+# If a core file already exists, it should be dealt with; skip test.
+test -e core && exit 77
+echo | "$groff" >&-
+! test -e core
diff --git a/src/roff/groff/tests/smoke-test_html_device.sh b/src/roff/groff/tests/smoke-test_html_device.sh
new file mode 100755
index 0000000..877fc28
--- /dev/null
+++ b/src/roff/groff/tests/smoke-test_html_device.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Copyright (C) 2020, 2022 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# Keep this list of programs in sync with GROFF_CHECK_GROHTML_PROGRAMS
+# in m4/groff.m4.
+for cmd in pnmcrop pnmcut pnmtopng pnmtops psselect
+do
+ if ! command -v $cmd >/dev/null
+ then
+ echo "cannot locate '$cmd' command; skipping test" >&2
+ exit 77 # skip
+ fi
+done
+
+fail=
+
+wail () {
+ echo ...FAILED >&2
+ fail=yes
+}
+
+cleanup () {
+ rm -f grohtml-[0-9]*-[12].png
+ trap - HUP INT QUIT TERM
+}
+
+trap 'trap "" HUP INT QUIT TERM; cleanup; kill -s INT $$' \
+ HUP INT QUIT TERM
+
+input='.TS
+L.
+foobar
+.TE'
+
+# Inline images are named grohtml-$$-<image sequence number>.png.
+
+echo "checking production of inline image for tbl(1) table" >&2
+output=$(echo "$input" | "$groff" -t -Thtml)
+echo "$output" | grep -q '<img src="grohtml-[0-9]\+-1.png"' || wail
+
+input='.EQ
+x sup 2 + y sup 2 = z sup 2
+.EN'
+
+echo "checking production of inline image for eqn(1) equation" >&2
+output=$(echo "$input" | "$groff" -e -Thtml)
+echo "$output" | grep -q '<img src="grohtml-[0-9]\+-2.png"' || wail
+
+cleanup
+
+# We can't run remaining tests if the environment doesn't support UTF-8.
+test "$(locale charmap)" = UTF-8 || exit 77 # skip
+
+# Check two forms of character transformation.
+#
+# dash's built-in printf doesn't support \x or \u escapes, so likely
+# other shells don't either, and expecting one that does to be in the
+# $PATH seems optimistic. So use UTF-8 octal bytes directly.
+echo "checking -k -Thtml" >&2
+printf '\303\241' | "$groff" -k -Thtml | grep -qx '<p>&aacute;</p>' \
+ || wail
+
+# We test compatibility-mode HTML output somewhat differently since
+# preconv only emits groffish \[uXXXX] escapes for non-ASCII codepoints.
+echo "checking -C -k -Thtml" >&2
+printf "\('a" | "$groff" -C -k -Thtml | grep -qx '<p>&aacute;</p>' \
+ || wail
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh b/src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh
new file mode 100755
index 0000000..96be055
--- /dev/null
+++ b/src/roff/groff/tests/some_escapes_accept_newline_delimiters.sh
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+fail=
+
+wail () {
+ echo "...FAILED" >&2
+ fail=yes
+}
+
+# Regression-test Savannah #63011.
+#
+# A handful of escape sequences bizarrely accept newlines as argument
+# delimiters. Don't throw diagnostics if they are used.
+
+input="\A
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'A' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'A' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww)
+test "$output" = "1 D" || wail
+
+input=".sp
+\b
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'b' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'b' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww)
+echo "$output" | grep -Fqx "B D" || wail
+
+input="\o
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'o' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'o' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww \
+ | LC_ALL=C od -t c)
+# 7 spaces between C and D.
+printf "%s\n" "$output" \
+ | grep -Eqx '0000000 +A +\\b +B +\\b +C D +\\n *' || wail
+
+input="\w
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'w' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'w' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww)
+test "$output" = "72 D" || wail
+
+input="\X
+tty: link http://example.com
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'X' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+echo "checking correct handling of newline delimiter to 'X' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -P -c)
+test "$output" = ' D' || wail
+
+input="\Z
+ABC
+D
+.pl \n(nlu"
+
+echo "checking that newline is accepted as delimiter to 'Z' escape" >&2
+error=$(printf "%s\n" "$input" | "$groff" -Tascii -ww -z 2>&1)
+test -z "$error" || wail
+
+# This looks really weird but is consistent. A newline used as a
+# delimiter still gets interpreted as an input line ending. What we see
+# here is: 'ABC' is formatted, the drawing position is reset to the
+# beginning of the line, a word space (from filling, overstriking 'A')
+# goes on the output, followed by 'D', so it appears as 'ADC'.
+#
+# `printf '\\Z@ABC@\nD\n'` produces the same output.
+echo "checking correct handling of newline delimiter to 'Z' escape" >&2
+output=$(printf "%s\n" "$input" | "$groff" -Tascii -ww \
+ | LC_ALL=C od -t c)
+printf "%s\n" "$output" | grep -Eqx '0000000 +A +B +\\b +D +C +\\n *' \
+ || wail
+
+test -z "$fail"
+
+# vim:set autoindent expandtab shiftwidth=2 tabstop=2 textwidth=72:
diff --git a/src/roff/groff/tests/soquiet_works.sh b/src/roff/groff/tests/soquiet_works.sh
new file mode 100755
index 0000000..e2ea286
--- /dev/null
+++ b/src/roff/groff/tests/soquiet_works.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+set -e
+
+# Keep preconv from being run.
+unset GROFF_ENCODING
+
+DOC='.soquiet nonexistent'
+
+OUTPUT=$(echo "$DOC" | "$groff" -Tascii 2>&1)
+echo "$OUTPUT"
+
+echo "testing that .soquiet of nonexistent file produces no error" >&2
+test -z "$OUTPUT"
diff --git a/src/roff/groff/tests/string_case_xform_errors.sh b/src/roff/groff/tests/string_case_xform_errors.sh
new file mode 100755
index 0000000..a16d763
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_errors.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+expected="troff:<standard input>:1: error: cannot apply string case transformation to a request ('br')"
+
+actual=$("$groff" -Tutf8 2>&1 <<EOF
+.stringdown br
+EOF
+)
+
+echo "$actual" | grep -qx "$expected"
diff --git a/src/roff/groff/tests/string_case_xform_requests.sh b/src/roff/groff/tests/string_case_xform_requests.sh
new file mode 100755
index 0000000..a9b8fa6
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_requests.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+input=".pl 1v
+.ds resume R\\['e]sum\\['e]\\\"
+\\*[resume]
+.stringdown resume
+\\*[resume]
+.stringup resume
+\\*[resume]"
+expected="Résumé résumé RÉSUMÉ"
+actual=$(echo "$input" | "$groff" -Tutf8)
+test "$actual" = "$expected"
diff --git a/src/roff/groff/tests/string_case_xform_unicode_escape.sh b/src/roff/groff/tests/string_case_xform_unicode_escape.sh
new file mode 100755
index 0000000..529e0c3
--- /dev/null
+++ b/src/roff/groff/tests/string_case_xform_unicode_escape.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Copyright (C) 2019-2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# The following is what we expect in the future when we support Unicode
+# case transformations.
+expected="attaché ATTACHÉ"
+
+# For now, we expect problems like this:
+# troff: backtrace: '<standard input>':4: string 'attache'
+# troff: backtrace: file '<standard input>':5
+# troff: <standard input>:5: warning: can't find special character
+# 'U0065_0301'
+
+actual=$("$groff" -Tutf8 2>&1 <<EOF
+.pl 1v
+.ds attache attach\\[u0065_0301]\\\"
+\\*[attache]
+.stringup attache
+\\*[attache]
+EOF
+)
+
+echo "$actual" | grep -Fqx "$expected"
diff --git a/src/roff/groff/tests/substring_works.sh b/src/roff/groff/tests/substring_works.sh
new file mode 100755
index 0000000..a57d579
--- /dev/null
+++ b/src/roff/groff/tests/substring_works.sh
@@ -0,0 +1,120 @@
+#!/bin/sh
+#
+# Copyright (C) 2021 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+
+# This test is based on a contribution by Jim Avera; see
+# <https://savannah.gnu.org/bugs/?60802>.
+
+"$groff" -z -ww <<'EOF'
+.
+.nr debug 1
+.de debugmsg
+. if \\n[debug] .tm \\$*
+..
+.de errormsg
+. tm ERROR: \\$*
+. nr nerrors (\\n[nerrors]+1)
+..
+.nr nerrors 0
+.
+.\" .substring xx n1 [n2]
+.\" Replace contents of string named xx with the substring bounded by
+.\" zero-based indices indices n1 and n2. Negative indices count
+.\" backwards from the end of the string. If omitted, n2 is `-1`.
+.\"
+.\" If n1 > n2, n1 and n2 are swapped. If n1 equals or exceeds the
+.\" string length, it is set to `-1`.
+.\"
+.\" NOT YET IMPLEMENTED:
+.\" If n1 > n2, or if n1 equals or exceeds the string length, then
+.\" any contents of xx are replaced with the empty string.
+.\"
+.de jtest \" input_string n1 n2 expected_result
+. if \\n[.$]<4 .ab jtest: expected at least 4 arguments, got \\n[.$]
+. ds t*input "\\$1
+. ds t*n1 \\$2
+. ds t*n2 \\$3
+. ds t*expected "\\$4
+. shift 4
+. ds t*comment "\\$*
+.
+. ds t*str "\\*[t*input]
+. ie '\\*[t*n2]'' .substring t*str \\*[t*n1]
+. el .substring t*str \\*[t*n1] \\*[t*n2]
+. ie '\\*[t*str]'\\*[t*expected]' \{\
+. debugmsg .substring '\\*[t*input]' \\*[t*n1] \\*[t*n2] -> \
+'\\*[t*str]' (OK) \\*[t*comment]
+. \}
+. el \{\
+. errormsg .substring '\\*[t*input]' \\*[t*n1] \\*[t*n2] yielded \
+'\\*[t*str]', EXPECTED '\\*[t*expected]' \\*[t*comment]
+. \}
+..
+.
+.debugmsg --- Pick a single character from non-empty ---
+.jtest "abc" 0 0 "a"
+.jtest "abc" 1 1 "b"
+.jtest "abc" 2 2 "c"
+.
+.debugmsg --- Pick multiple characters from non-empty ---
+.jtest "abcd" 0 1 "ab"
+.jtest "abcd" 1 1 "b"
+.jtest "abcd" 0 3 "abcd"
+.jtest "abcd" 0 -1 "abcd"
+.jtest "abcd" 0 "" "abcd"
+.jtest "abcd" 1 3 "bcd"
+.jtest "abcd" 2 3 "cd"
+.jtest "abcd" 3 3 "d"
+.
+.debugmsg --- Omit n2 with non-empty input and non-empty result ---
+.jtest "abc" 0 "" "abc"
+.jtest "abc" 1 "" "bc"
+.jtest "abc" 2 "" "c"
+.jtest "a" 0 "" "a"
+.
+.\"debugmsg --- Specify empty substring with n2==(n1-1) ---
+.\"jtest "abcd" 3 2 ""
+.\"jtest "abcd" 2 1 ""
+.\"jtest "abcd" 1 0 ""
+.debugmsg --- Pick multiple characters from non-empty using inverted \
+range ---
+.jtest "abcd" 3 2 "cd"
+.jtest "abcd" 2 1 "bc"
+.jtest "abcd" 1 0 "ab"
+.
+.\"debugmsg --- Specify empty substring with n1==length and n2 omitted ---
+.\"jtest "abcd" 4 "" ""
+.\"jtest "abc" 3 "" ""
+.\"jtest "ab" 2 "" ""
+.\"jtest "a" 1 "" ""
+.\"jtest "" 0 "" ""
+.debugmsg --- Pick single character using out-of-bounds start index \
+(unless string empty) ---
+.jtest "abcd" 4 "" "d"
+.jtest "abc" 3 "" "c"
+.jtest "ab" 2 "" "b"
+.jtest "a" 1 "" "a"
+.jtest "" 0 "" ""
+.jtest "" 0 -1 ""
+.jtest "" 0 -2 ""
+.
+.if \n[nerrors] .ab Aborting, got \n[nerrors] errors.
+EOF
diff --git a/src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh b/src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh
new file mode 100755
index 0000000..0d1cacd
--- /dev/null
+++ b/src/roff/groff/tests/use_point_size_escape_with_single_digit_arg.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+groff="${abs_top_builddir:-.}/test-groff"
+export GROFF_TYPESETTER=
+
+# The vertical space is so that the 36-point 'A' won't be truncated by
+# the top of the page. That could be confusing and misleading to anyone
+# who ever has to troubleshoot this test case.
+DOC=".vs 10v
+\s36A"
+
+set -e
+
+# Verify that the idiosyncratic behavior of \sN is supported in
+# compatibility mode...
+echo "testing \s36A in compatibility mode (36-point 'A')" >&2
+echo "$DOC" | "$groff" -C -Z | grep -qx 's36000'
+
+# ...and not in regular mode.
+echo "testing \s36A in non-compatibility mode (3-point '6A')" >&2
+echo "$DOC" | "$groff" -Z | grep -qx 's3000'
+
+# Check that we get a diagnostic when relying on the ambiguous form.
+echo "testing for diagnostic on \s36 in compatibility mode" >&2
+echo "$DOC" | "$groff" -C -Z 2>&1 >/dev/null \
+ | grep -q 'ambiguous type size in escape sequence'
diff --git a/src/roff/nroff/nroff.1.man b/src/roff/nroff/nroff.1.man
new file mode 100644
index 0000000..cdefee1
--- /dev/null
+++ b/src/roff/nroff/nroff.1.man
@@ -0,0 +1,358 @@
+.TH @g@nroff @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@nroff \- format documents with
+.I groff
+for TTY (terminal) devices
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" Permission is granted to make and distribute verbatim copies of this
+.\" manual provided the copyright notice and this permission notice are
+.\" preserved on all copies.
+.\"
+.\" Permission is granted to copy and distribute modified versions of
+.\" this manual under the conditions for verbatim copying, provided that
+.\" the entire resulting derived work is distributed under the terms of
+.\" a permission notice identical to this one.
+.\"
+.\" Permission is granted to copy and distribute translations of this
+.\" manual into another language, under the above conditions for
+.\" modified versions, except that this permission notice may be
+.\" included in translations approved by the Free Software Foundation
+.\" instead of in the original English.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_nroff_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@nroff
+.RB [ \-bcCEhikpRStUVz ]
+.RB [ \-d\~\c
+.IR ctext ]
+.RB [ \-d\~\c
+.IB string =\c
+.IR text ]
+.RB [ \-K\~\c
+.IR fallback-encoding ]
+.RB [ \-m\~\c
+.IR macro-package ]
+.RB [ \-M\~\c
+.IR macro-directory ]
+.RB [ \-n\~\c
+.IR page-number ]
+.RB [ \-o\~\c
+.IR page-list ]
+.RB [ \-P\~\c
+.IR postprocessor-argument ]
+.RB [ \-r\~\c
+.IR cnumeric-expression ]
+.RB [ \-r\~\c
+.IB register =\c
+.IR numeric-expression ]
+.RB [ \-T\~\c
+.IR output-device ]
+.RB [ \-w\~\c
+.IR warning-category ]
+.RB [ \-W\~\c
+.IR warning-category ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@nroff
+.B \-\-help
+.YS
+.
+.
+.SY @g@nroff
+.B \-v
+.
+.SY @g@nroff
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+.I @g@nroff
+formats documents written in the
+.MR groff @MAN7EXT@
+language for typewriter-like devices such as terminal emulators.
+.
+GNU
+.I nroff \" GNU
+emulates the AT&T
+.I nroff \" AT&T
+command using
+.MR groff @MAN1EXT@ .
+.
+.I @g@nroff
+generates output via
+.MR grotty @MAN1EXT@ ,
+.IR groff 's
+terminal output driver,
+which needs to know the character encoding scheme used by the device.
+.
+Consequently,
+acceptable arguments to the
+.B \-T
+option are
+.BR ascii ,
+.BR latin1 ,
+.BR utf8 ,
+and
+.BR cp1047 ;
+any others are ignored.
+.
+If neither the
+.I \%GROFF_TYPESETTER
+environment variable nor the
+.B \-T
+command-line option
+(which overrides the environment variable)
+specifies a (valid) device,
+.I @g@nroff
+consults the locale to select an appropriate output device.
+.
+It first tries the
+.MR locale 1
+program,
+then checks several locale-related environment variables;
+see section \[lq]Environment\[rq] below.
+.
+If all of the foregoing fail,
+.B \-Tascii
+is implied.
+.
+.
+.P
+The
+.BR \-b ,
+.BR \-c ,
+.BR \-C ,
+.BR \-d ,
+.BR \-E ,
+.BR \-i ,
+.BR \-m ,
+.BR \-M ,
+.BR \-n ,
+.BR \-o ,
+.BR \-r ,
+.BR \-U ,
+.BR \-w ,
+.BR \-W ,
+and
+.B \-z
+options have the effects described in
+.MR @g@troff @MAN1EXT@ .
+.
+.B \-c
+and
+.B \-h
+imply
+.RB \[lq] \-P\-c \[rq]
+and
+.RB \[lq] \-P\-h \[rq],
+respectively;
+.B \-c
+is also interpreted directly by
+.IR @g@troff .
+.
+In addition,
+this implementation ignores the AT&T
+.I nroff \" AT&T
+options
+.BR \-e ,
+.BR \-q ,
+and
+.B \-s
+(which are not implemented in
+.IR groff ).
+.
+The options
+.BR \-k ,
+.BR \-K ,
+.BR \-p ,
+.BR \-P ,
+.BR \-R ,
+.BR \-t ,
+and
+.B \-S
+are documented in
+.MR groff @MAN1EXT@ .
+.
+.B \-V
+causes
+.I @g@nroff
+to display the constructed
+.I groff
+command on the standard output stream,
+but does not execute it.
+.
+.B \-v
+and
+.B \-\-version
+show version information about
+.I @g@nroff
+and the programs it runs,
+while
+.B \-\-help
+displays a usage message;
+all exit afterward.
+.
+.
+.\" ====================================================================
+.SH "Exit status"
+.\" ====================================================================
+.
+.I @g@nroff
+exits with error
+.RB status\~ 2
+if there was a problem parsing its arguments,
+with
+.RB status\~ 0
+if any of the options
+.BR \-V ,
+.BR \-v ,
+.BR \-\-version ,
+or
+.B \-\-help
+were specified,
+and with the status of
+.I groff
+otherwise.
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+Normally,
+the path separator in environment variables ending with
+.I PATH
+is the colon;
+this may vary depending on the operating system.
+.
+For example,
+Windows uses a semicolon instead.
+.
+.
+.TP
+.I GROFF_BIN_PATH
+is a colon-separated list of directories in which to search for the
+.I groff
+executable before searching in
+.IR PATH .
+.
+If unset,
+.I @BINDIR@
+is used.
+.
+.
+.TP
+.I GROFF_TYPESETTER
+specifies the default output device for
+.IR groff .
+.
+.
+.TP
+.I LC_ALL
+.TQ
+.I LC_CTYPE
+.TQ
+.I LANG
+.TQ
+.I LESSCHARSET
+are pattern-matched in this order for contents matching standard
+character encodings supported by
+.I groff
+in the event no
+.B \-T
+option is given and
+.I \%GROFF_TYPESETTER
+is unset,
+or the values specified are invalid.
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @MACRODIR@/\:\%tty\-char\:.tmac
+defines fallback definitions of
+.I roff
+special characters.
+.
+These definitions more poorly optically approximate typeset output
+than those of
+.I tty.tmac
+in favor of communicating semantic information.
+.
+.I nroff
+loads it automatically.
+.
+.
+.\" ====================================================================
+.SH Notes
+.\" ====================================================================
+.
+Pager programs like
+.MR more 1
+and
+.MR less 1
+may require command-line options to correctly handle some output
+sequences;
+see
+.MR grotty @MAN1EXT@ .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.MR groff @MAN1EXT@ ,
+.MR @g@troff @MAN1EXT@ ,
+.MR grotty @MAN1EXT@ ,
+.MR locale 1 ,
+.MR roff @MAN7EXT@
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_nroff_1_man_C]
+.do rr *groff_nroff_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/roff/nroff/nroff.am b/src/roff/nroff/nroff.am
new file mode 100644
index 0000000..82738d9
--- /dev/null
+++ b/src/roff/nroff/nroff.am
@@ -0,0 +1,44 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_SCRIPTS += nroff
+nroff_srcdir = $(top_srcdir)/src/roff/nroff
+PREFIXMAN1 += src/roff/nroff/nroff.1
+EXTRA_DIST += \
+ src/roff/nroff/nroff.1.man \
+ src/roff/nroff/nroff.sh
+
+nroff_TESTS = \
+ src/roff/nroff/tests/verbose_option_works.sh
+TESTS += $(nroff_TESTS)
+EXTRA_DIST += $(nroff_TESTS)
+
+nroff: $(nroff_srcdir)/nroff.sh $(SH_DEPS_SED_SCRIPT)
+ $(AM_V_GEN)rm -f $@ \
+ && sed -f $(SH_DEPS_SED_SCRIPT) \
+ -e $(SH_SCRIPT_SED_CMD) \
+ -e "s|[@]VERSION[@]|$(VERSION)|" \
+ $(nroff_srcdir)/nroff.sh \
+ >$@ \
+ && chmod +x $@
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/roff/nroff/nroff.sh b/src/roff/nroff/nroff.sh
new file mode 100644
index 0000000..77f5c06
--- /dev/null
+++ b/src/roff/nroff/nroff.sh
@@ -0,0 +1,204 @@
+#! /bin/sh
+# Emulate nroff with groff.
+#
+# Copyright (C) 1992-2021 Free Software Foundation, Inc.
+#
+# Written by James Clark, Werner Lemberg, and G. Branden Robinson.
+#
+# 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 (GPL) as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 'groff' is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prog="$0"
+
+T=
+Topt=
+opts=
+dry_run=
+is_option_argument_pending=
+
+usage="usage: $prog [-bcCEhikpRStUVz] [-d ctext] [-d string=text] \
+[-K fallback-encoding] [-m macro-package] [-M macro-directory] \
+[-n page-number] [-o page-list] [-P postprocessor-argument] \
+[-r cnumeric-expression] [-r register=numeric-expression] \
+[-T output-device] [-w warning-category] [-W warning-category] \
+[file ...]
+usage: $prog {-v | --version}
+usage: $prog --help"
+
+for arg
+do
+ if [ -n "$is_option_argument_pending" ]
+ then
+ is_option_argument_pending=
+ opts="$opts $arg"
+ shift
+ continue
+ fi
+
+ case $arg in
+ -c)
+ opts="$opts $arg -P-c" ;;
+ -h)
+ opts="$opts -P-h" ;;
+ -[eq] | -s*)
+ # ignore these options
+ ;;
+ -[dKmMnoPrTwW])
+ is_option_argument_pending=yes
+ opts="$opts $arg" ;;
+ -[bCEikpRStUz] | -[dKMmrnoPwW]*)
+ opts="$opts $arg" ;;
+ -T*)
+ Topt=$arg ;;
+ -u*)
+ # -u is for Solaris compatibility and not otherwise documented.
+ #
+ # Solaris 2.2 through at least Solaris 9 'man' invokes
+ # 'nroff -u0 ... | col -x'. Ignore the -u0, since 'less' and
+ # 'more' can use the emboldening info. But disable SGR, since
+ # Solaris 'col' mishandles it.
+ opts="$opts -P-c" ;;
+ -V)
+ dry_run=yes ;;
+ -v | --version)
+ echo "GNU nroff (groff) version @VERSION@"
+ opts="$opts $arg" ;;
+ --help)
+ echo "$usage"
+ exit 0 ;;
+ --)
+ shift
+ break ;;
+ -)
+ break ;;
+ -*)
+ echo "$prog: usage error: invalid option '$arg'" >&2
+ echo "$usage" >&2
+ exit 2 ;;
+ *)
+ break ;;
+ esac
+ shift
+done
+
+if [ -n "$is_option_argument_pending" ]
+then
+ echo "$prog: usage error: option '$arg' requires an argument" >&2
+ exit 2
+fi
+
+# Determine the -T option. Was a valid one specified?
+case "$Topt" in
+ -Tascii | -Tlatin1 | -Tutf8 | -Tcp1047)
+ T=$Topt ;;
+esac
+
+# -T option absent or invalid; try environment.
+if [ -z "$T" ]
+then
+ Tenv=-T$GROFF_TYPESETTER
+ case "$Tenv" in
+ -Tascii | -Tlatin1 | -Tutf8 | -Tcp1047)
+ T=$Tenv ;;
+ esac
+fi
+
+# Finally, infer a -T option from the locale. Try 'locale charmap'
+# first because it is the most reliable, then look at environment
+# variables.
+if [ -z "$T" ]
+then
+ # The separate `exec` is to work around a ~2004 bug in Cygwin sh.exe.
+ case "`exec 2>/dev/null ; locale charmap`" in
+ UTF-8)
+ Tloc=utf8 ;;
+ ISO-8859-1 | ISO-8859-15)
+ Tloc=latin1 ;;
+ IBM-1047)
+ Tloc=cp1047 ;;
+ *)
+ # Some old shells don't support ${FOO:-bar} expansion syntax. We
+ # should switch to it when it is safe to abandon support for them.
+ case "${LC_ALL-${LC_CTYPE-${LANG}}}" in
+ *.UTF-8)
+ Tloc=utf8 ;;
+ iso_8859_1 | *.ISO-8859-1 | *.ISO8859-1 | \
+ iso_8859_15 | *.ISO-8859-15 | *.ISO8859-15)
+ Tloc=latin1 ;;
+ *.IBM-1047)
+ Tloc=cp1047 ;;
+ *)
+ case "$LESSCHARSET" in
+ utf-8)
+ Tloc=utf8 ;;
+ latin1)
+ Tloc=latin1 ;;
+ cp1047)
+ Tloc=cp1047 ;;
+ *)
+ Tloc=ascii ;;
+ esac ;;
+ esac ;;
+ esac
+ T=-T$Tloc
+fi
+
+# Load nroff-style character definitions too.
+opts="-mtty-char$opts"
+
+# Set up the 'GROFF_BIN_PATH' variable to be exported in the current
+# 'GROFF_RUNTIME' environment.
+@GROFF_BIN_PATH_SETUP@
+export GROFF_BIN_PATH
+
+# Let our test harness redirect us. See LC_ALL comment above.
+groff=${GROFF_TEST_GROFF-groff}
+
+# Note 1: It would be nice to apply the DRY ("Don't Repeat Yourself")
+# principle here and store the entire command string to be executed into
+# a variable, and then either display it or execute it. For example:
+#
+# cmd="PATH=... groff ... $@"
+# ...
+# printf "%s\n" "$cmd"
+# ...
+# eval $cmd
+#
+# Unfortunately, the shell is a nightmarish hellscape of quoting issues.
+# Naïve attempts to solve the problem fail when arguments to nroff
+# contain embedded whitespace or shell metacharacters. The solution
+# below works with those, but there is insufficient quoting in -V (dry
+# run) mode, such that you can't copy-and-paste the output of 'nroff -V'
+# if you pass it a filename like foo"bar (with the embedded quotation
+# mark) and expect it to run without further quoting.
+#
+# If POSIX adopts Bash's ${var@Q} or an equivalent, this issue can be
+# revisited.
+#
+# Note 2: The construction '${1+"@$"}' preserves the absence of
+# arguments in old shells; see "Shell Substitutions" in the GNU Autoconf
+# manual. We don't want 'nroff' to become 'groff ... ""' if $# equals
+# zero.
+if [ -n "$dry_run" ]
+then
+ echo PATH="$GROFF_RUNTIME$PATH" $groff $T $opts ${1+"$@"}
+else
+ PATH="$GROFF_RUNTIME$PATH" $groff $T $opts ${1+"$@"}
+fi
+
+# Local Variables:
+# fill-column: 72
+# End:
+# vim: set autoindent expandtab shiftwidth=2 softtabstop=2 textwidth=72:
diff --git a/src/roff/nroff/tests/verbose_option_works.sh b/src/roff/nroff/tests/verbose_option_works.sh
new file mode 100755
index 0000000..85ca8f0
--- /dev/null
+++ b/src/roff/nroff/tests/verbose_option_works.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Ensure a predictable character encoding.
+export LC_ALL=C
+export LESSCHARSET=
+export GROFF_TYPESETTER=
+
+set -e
+
+export GROFF_TEST_GROFF=${abs_top_builddir:-.}/test-groff
+
+# The $PATH used by an installed nroff at runtime does not match what
+# we're trying to test, which should be using the groff and runtime
+# support from the build tree. Therefore the $PATH that nroff -V
+# reports will _always_ be wrong for test purposes. Skip over it.
+#
+# If the build environment has a directory in the $PATH matching
+# "test-groff " (with the trailing space), failure may result if sed
+# doesn't match greedily. POSIX says it should.
+sedexpr='s/^PATH=.*test-groff /test-groff /'
+PATH=${abs_top_builddir:-.}:$PATH
+
+nroff_ver=$(nroff -v | awk 'NR == 1 {print $NF}')
+groff_ver=$(nroff -v | awk 'NR == 2 {print $NF}')
+
+echo nroff: $nroff_ver >&2
+echo groff: $groff_ver >&2
+test "$nroff_ver" = "$groff_ver"
+
+echo "testing 'nroff -V'" >&2
+nroff -V | sed "$sedexpr" | grep -x "test-groff -Tascii -mtty-char"
+
+echo "testing 'nroff -V 1'" >&2
+nroff -V 1 | sed "$sedexpr" | grep -x "test-groff -Tascii -mtty-char 1"
+
+echo "testing 'nroff -V \"1a 1b\"'" >&2
+nroff -V \"1a 1b\" | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char \"1a 1b\""
+
+echo "testing 'nroff -V \"1a 1b\" 2'" >&2
+nroff -V \"1a 1b\" 2 | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char \"1a 1b\" 2"
+
+echo "testing 'nroff -V 1a\\\"1b 2'" >&2
+nroff -V 1a\"1b 2 | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char 1a\"1b 2"
+
+echo "testing 'nroff -V -d FOO=BAR 1'" >&2
+nroff -V -d FOO=BAR 1 | sed "$sedexpr" \
+ | grep -x "test-groff -Tascii -mtty-char -d FOO=BAR 1"
diff --git a/src/roff/troff/TODO b/src/roff/troff/TODO
new file mode 100644
index 0000000..533167a
--- /dev/null
+++ b/src/roff/troff/TODO
@@ -0,0 +1,125 @@
+A line prefix request to make e.g. French quotation possible:
+
+ He said: >> blablablabla
+ >> blablabla blabla bla
+ >> blabla blabla bla bla
+ >> bla bla bla blablabla
+ >> blabla. <<
+
+Give a more helpful error message when the indent is set to a value
+greater than the line-length.
+
+Tracing. This is a pain to implement because requests are responsible
+for reading their own arguments.
+
+Possibly implement -s option (stop every N pages). This functionality
+would be more appropriate in a postprocessor.
+
+Line breaking should be smarter. In particular, it should be possible
+to shrink spaces. Also avoid having a line that's been shrunk a lot
+next to a line that's been stretched a lot. The difficulty is to
+design a mechanism that allows the user complete control over the
+decision of where to break the line.
+
+Provide a mechanism to control the shape of the rag in non-justified
+text.
+
+Add a discretionary break escape sequence. \='...'...'...' like TeX.
+
+Think about kerning between characters and spaces. (Need to implement
+get_breakpoints and split methods for kern_pair_node class.)
+
+In troff, if .L > 1 when a diversion is reread in no-fill mode, then
+extra line-spacing is added on. Groff at the moment treats line-spacing
+like vertical spacing and doesn't do this.
+
+Suppose \(ch comes from a special font S, and that the current font is
+R. Suppose that R contains a hyphen character and that S does not.
+Suppose that the current font is R. Suppose that \(ch is in a word
+and has a non-zero hyphen-type. Then we ought to be able to hyphenate,
+but we won't be able to because we will look for the hyphen only in
+font S and not in font R.
+
+Perhaps the current interpolation depth should be accessible in a number
+register.
+
+Should \w deal with a newline like \X?
+
+Have another look at uses of token::delimiter. Perhaps we need to
+distinguish the case where we want to see if a token could start a
+number, from the case where we want to see if it could occur somewhere
+in a number expression.
+
+Provide a facility like copy thru in pic.
+
+Fancier implementation of font families which doesn't group fonts into
+families purely on the basis of their names.
+
+In the DESC file make the number of fonts optional if they are all on
+one line.
+
+Number register to give the diversion level.
+
+Time various alternative implementations of scale (both in font.c and
+number.c). On a sparc it's faster to always do it in floating point.
+
+Devise a more compact representation for the hyphenation patterns trie.
+
+Have a per-environment parameter to increase letter-spacing.
+
+Request to set character height.
+
+Request to set character slant.
+
+Support non-uniformly scalable fonts. Perhaps associate a suffix with
+a particular range of sizes. E.g.,
+ sizesuffix .display 14-512
+Then is you ask for R at pointsize 16, groff will first look for
+R.display and then R. Probably necessary to be able to specify a
+separate unitwidth for each sizesuffix (e.g., uuu for X).
+
+Make it possible to suppress hyphenation on a word-by-word basis.
+(Perhaps store hyphenation flags in tfont.)
+
+Possibly allow multiple simultaneous input line traps.
+
+Unpaddable, breakable space escape sequence.
+
+Support hanging punctuation.
+
+In justified text, if the last line of a paragraph is only a little
+bit short it might be desirable to justify the line. Allow the user
+control over this.
+
+The pm request could print where the macro was defined. Also could
+optionally print the contents of a macro.
+
+Provide some way to round numbers to multiples of the current
+horizontal or vertical motion quantum.
+
+Better string-processing support (search).
+
+Generalized ligatures.
+
+Request to remove an environment. (Maintain a count of the references
+to the environment from the environment table, environment dictionary
+or environment stack.)
+
+Perhaps in the nr request a leading '-' should only be recognized as a
+decrement when it's at the same interpolation depth as the request.
+
+Don't ever change a charinfo. Create new variants instead and chain
+them together.
+
+Unix troff appears to read the first character of a request name in
+copy mode. Should we do the same?
+
+Number register giving name of end macro.
+
+More thorough range checking.
+
+Provide syntax for octal and hexadecimal numeric constants. Perhaps
+o#100 and x#7f as per Scheme. Or perhaps PostScript 16#7f. Ambiguity
+between whether 'c' is treated as digit or scaling indicator.
+
+Local variables.
diff --git a/src/roff/troff/charinfo.h b/src/roff/troff/charinfo.h
new file mode 100644
index 0000000..0a7a481
--- /dev/null
+++ b/src/roff/troff/charinfo.h
@@ -0,0 +1,301 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <vector>
+#include <utility>
+
+extern int class_flag; // set if there was a call to '.class'
+extern void get_flags();
+
+class macro;
+
+class charinfo : glyph {
+ static int next_index;
+ charinfo *translation;
+ macro *mac;
+ unsigned char special_translation;
+ unsigned char hyphenation_code;
+ unsigned int flags;
+ unsigned char ascii_code;
+ unsigned char asciify_code;
+ char not_found;
+ char transparent_translate; // non-zero means translation applies
+ // to transparent throughput
+ char translate_input; // non-zero means that asciify_code is
+ // active for .asciify (set by .trin)
+ char_mode mode;
+ // Unicode character classes
+ std::vector<std::pair<int, int> > ranges;
+ std::vector<charinfo *> nested_classes;
+public:
+ enum { // Values for the flags bitmask. See groff
+ // manual, description of the '.cflags' request.
+ ENDS_SENTENCE = 0x01,
+ BREAK_BEFORE = 0x02,
+ BREAK_AFTER = 0x04,
+ OVERLAPS_HORIZONTALLY = 0x08,
+ OVERLAPS_VERTICALLY = 0x10,
+ TRANSPARENT = 0x20,
+ IGNORE_HCODES = 0x40,
+ DONT_BREAK_BEFORE = 0x80,
+ DONT_BREAK_AFTER = 0x100,
+ INTER_CHAR_SPACE = 0x200
+ };
+ enum {
+ TRANSLATE_NONE,
+ TRANSLATE_SPACE,
+ TRANSLATE_DUMMY,
+ TRANSLATE_STRETCHABLE_SPACE,
+ TRANSLATE_HYPHEN_INDICATOR
+ };
+ symbol nm;
+ charinfo(symbol);
+ glyph *as_glyph();
+ int ends_sentence();
+ int overlaps_vertically();
+ int overlaps_horizontally();
+ int can_break_before();
+ int can_break_after();
+ int transparent();
+ int ignore_hcodes();
+ int prohibit_break_before();
+ int prohibit_break_after();
+ int inter_char_space();
+ unsigned char get_hyphenation_code();
+ unsigned char get_ascii_code();
+ unsigned char get_asciify_code();
+ int get_unicode_code();
+ void set_hyphenation_code(unsigned char);
+ void set_ascii_code(unsigned char);
+ void set_asciify_code(unsigned char);
+ void set_translation_input();
+ int get_translation_input();
+ charinfo *get_translation(int = 0);
+ void set_translation(charinfo *, int, int);
+ void get_flags();
+ void set_flags(unsigned int);
+ void set_special_translation(int, int);
+ int get_special_translation(int = 0);
+ macro *set_macro(macro *);
+ macro *setx_macro(macro *, char_mode);
+ macro *get_macro();
+ int first_time_not_found();
+ void set_number(int);
+ int get_number();
+ int numbered();
+ int is_normal();
+ int is_fallback();
+ int is_special();
+ symbol *get_symbol();
+ void add_to_class(int);
+ void add_to_class(int, int);
+ void add_to_class(charinfo *);
+ bool is_class();
+ bool contains(int, bool = false);
+ bool contains(symbol, bool = false);
+ bool contains(charinfo *, bool = false);
+};
+
+charinfo *get_charinfo(symbol);
+extern charinfo *charset_table[];
+charinfo *get_charinfo_by_number(int);
+
+inline int charinfo::overlaps_horizontally()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & OVERLAPS_HORIZONTALLY;
+}
+
+inline int charinfo::overlaps_vertically()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & OVERLAPS_VERTICALLY;
+}
+
+inline int charinfo::can_break_before()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & BREAK_BEFORE;
+}
+
+inline int charinfo::can_break_after()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & BREAK_AFTER;
+}
+
+inline int charinfo::ends_sentence()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & ENDS_SENTENCE;
+}
+
+inline int charinfo::transparent()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & TRANSPARENT;
+}
+
+inline int charinfo::ignore_hcodes()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & IGNORE_HCODES;
+}
+
+inline int charinfo::prohibit_break_before()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & DONT_BREAK_BEFORE;
+}
+
+inline int charinfo::prohibit_break_after()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & DONT_BREAK_AFTER;
+}
+
+inline int charinfo::inter_char_space()
+{
+ if (class_flag)
+ ::get_flags();
+ return flags & INTER_CHAR_SPACE;
+}
+
+inline int charinfo::numbered()
+{
+ return number >= 0;
+}
+
+inline int charinfo::is_normal()
+{
+ return mode == CHAR_NORMAL;
+}
+
+inline int charinfo::is_fallback()
+{
+ return mode == CHAR_FALLBACK;
+}
+
+inline int charinfo::is_special()
+{
+ return mode == CHAR_SPECIAL;
+}
+
+inline charinfo *charinfo::get_translation(int transparent_throughput)
+{
+ return (transparent_throughput && !transparent_translate
+ ? 0
+ : translation);
+}
+
+inline unsigned char charinfo::get_hyphenation_code()
+{
+ return hyphenation_code;
+}
+
+inline unsigned char charinfo::get_ascii_code()
+{
+ return ascii_code;
+}
+
+inline unsigned char charinfo::get_asciify_code()
+{
+ return (translate_input ? asciify_code : 0);
+}
+
+inline void charinfo::set_flags(unsigned int c)
+{
+ flags = c;
+}
+
+inline glyph *charinfo::as_glyph()
+{
+ return this;
+}
+
+inline void charinfo::set_translation_input()
+{
+ translate_input = 1;
+}
+
+inline int charinfo::get_translation_input()
+{
+ return translate_input;
+}
+
+inline int charinfo::get_special_translation(int transparent_throughput)
+{
+ return (transparent_throughput && !transparent_translate
+ ? int(TRANSLATE_NONE)
+ : special_translation);
+}
+
+inline macro *charinfo::get_macro()
+{
+ return mac;
+}
+
+inline int charinfo::first_time_not_found()
+{
+ if (not_found)
+ return 0;
+ else {
+ not_found = 1;
+ return 1;
+ }
+}
+
+inline symbol *charinfo::get_symbol()
+{
+ return &nm;
+}
+
+inline void charinfo::add_to_class(int c)
+{
+ class_flag = 1;
+ // TODO ranges cumbersome for single characters?
+ ranges.push_back(std::pair<int, int>(c, c));
+}
+
+inline void charinfo::add_to_class(int lo,
+ int hi)
+{
+ class_flag = 1;
+ ranges.push_back(std::pair<int, int>(lo, hi));
+}
+
+inline void charinfo::add_to_class(charinfo *ci)
+{
+ class_flag = 1;
+ nested_classes.push_back(ci);
+}
+
+inline bool charinfo::is_class()
+{
+ return (!ranges.empty() || !nested_classes.empty());
+}
diff --git a/src/roff/troff/column.cpp b/src/roff/troff/column.cpp
new file mode 100644
index 0000000..55563ba
--- /dev/null
+++ b/src/roff/troff/column.cpp
@@ -0,0 +1,731 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef COLUMN
+
+#include "troff.h"
+#include "symbol.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "stringclass.h"
+
+void output_file::vjustify(vunits, symbol)
+{
+ // do nothing
+}
+
+struct justification_spec;
+struct output_line;
+
+class column : public output_file {
+private:
+ output_file *out;
+ vunits bottom;
+ output_line *col;
+ output_line **tail;
+ void add_output_line(output_line *);
+ void begin_page(int pageno, vunits page_length);
+ void flush();
+ void print_line(hunits, vunits, node *, vunits, vunits);
+ void vjustify(vunits, symbol);
+ void transparent_char(unsigned char c);
+ void copy_file(hunits, vunits, const char *);
+ int is_printing();
+ void check_bottom();
+public:
+ column();
+ ~column();
+ void start();
+ void output();
+ void justify(const justification_spec &);
+ void trim();
+ void reset();
+ vunits get_bottom();
+ vunits get_last_extra_space();
+ int is_active() { return out != 0; }
+};
+
+column *the_column = 0;
+
+struct transparent_output_line;
+struct vjustify_output_line;
+
+class output_line {
+ output_line *next;
+public:
+ output_line();
+ virtual ~output_line();
+ virtual void output(output_file *, vunits);
+ virtual transparent_output_line *as_transparent_output_line();
+ virtual vjustify_output_line *as_vjustify_output_line();
+ virtual vunits distance();
+ virtual vunits height();
+ virtual void reset();
+ virtual vunits extra_space(); // post line
+ friend class column;
+ friend class justification_spec;
+};
+
+class position_output_line : public output_line {
+ vunits dist;
+public:
+ position_output_line(vunits);
+ vunits distance();
+};
+
+class node_output_line : public position_output_line {
+ node *nd;
+ hunits page_offset;
+ vunits before;
+ vunits after;
+public:
+ node_output_line(vunits, node *, hunits, vunits, vunits);
+ ~node_output_line();
+ void output(output_file *, vunits);
+ vunits height();
+ vunits extra_space();
+};
+
+class vjustify_output_line : public position_output_line {
+ vunits current;
+ symbol typ;
+public:
+ vjustify_output_line(vunits dist, symbol);
+ vunits height();
+ vjustify_output_line *as_vjustify_output_line();
+ void vary(vunits amount);
+ void reset();
+ symbol type();
+};
+
+inline symbol vjustify_output_line::type()
+{
+ return typ;
+}
+
+class copy_file_output_line : public position_output_line {
+ symbol filename;
+ hunits hpos;
+public:
+ copy_file_output_line(vunits, const char *, hunits);
+ void output(output_file *, vunits);
+};
+
+class transparent_output_line : public output_line {
+ string buf;
+public:
+ transparent_output_line();
+ void output(output_file *, vunits);
+ void append_char(unsigned char c);
+ transparent_output_line *as_transparent_output_line();
+};
+
+output_line::output_line() : next(0)
+{
+}
+
+output_line::~output_line()
+{
+}
+
+void output_line::reset()
+{
+}
+
+transparent_output_line *output_line::as_transparent_output_line()
+{
+ return 0;
+}
+
+vjustify_output_line *output_line::as_vjustify_output_line()
+{
+ return 0;
+}
+
+void output_line::output(output_file *, vunits)
+{
+}
+
+vunits output_line::distance()
+{
+ return V0;
+}
+
+vunits output_line::height()
+{
+ return V0;
+}
+
+vunits output_line::extra_space()
+{
+ return V0;
+}
+
+position_output_line::position_output_line(vunits d)
+: dist(d)
+{
+}
+
+vunits position_output_line::distance()
+{
+ return dist;
+}
+
+node_output_line::node_output_line(vunits d, node *n, hunits po, vunits b, vunits a)
+: position_output_line(d), nd(n), page_offset(po), before(b), after(a)
+{
+}
+
+node_output_line::~node_output_line()
+{
+ delete_node_list(nd);
+}
+
+void node_output_line::output(output_file *out, vunits pos)
+{
+ out->print_line(page_offset, pos, nd, before, after);
+ nd = 0;
+}
+
+vunits node_output_line::height()
+{
+ return after;
+}
+
+vunits node_output_line::extra_space()
+{
+ return after;
+}
+
+vjustify_output_line::vjustify_output_line(vunits d, symbol t)
+: position_output_line(d), typ(t)
+{
+}
+
+void vjustify_output_line::reset()
+{
+ current = V0;
+}
+
+vunits vjustify_output_line::height()
+{
+ return current;
+}
+
+vjustify_output_line *vjustify_output_line::as_vjustify_output_line()
+{
+ return this;
+}
+
+inline void vjustify_output_line::vary(vunits amount)
+{
+ current += amount;
+}
+
+transparent_output_line::transparent_output_line()
+{
+}
+
+transparent_output_line *transparent_output_line::as_transparent_output_line()
+{
+ return this;
+}
+
+void transparent_output_line::append_char(unsigned char c)
+{
+ assert(c != 0);
+ buf += c;
+}
+
+void transparent_output_line::output(output_file *out, vunits)
+{
+ int len = buf.length();
+ for (int i = 0; i < len; i++)
+ out->transparent_char(buf[i]);
+}
+
+copy_file_output_line::copy_file_output_line(vunits d, const char *f, hunits h)
+: position_output_line(d), hpos(h), filename(f)
+{
+}
+
+void copy_file_output_line::output(output_file *out, vunits pos)
+{
+ out->copy_file(hpos, pos, filename.contents());
+}
+
+column::column()
+: bottom(V0), col(0), tail(&col), out(0)
+{
+}
+
+column::~column()
+{
+ assert(out != 0);
+ error("automatically outputting column before exiting");
+ output();
+ delete the_output;
+}
+
+void column::start()
+{
+ assert(out == 0);
+ if (!the_output)
+ init_output();
+ assert(the_output != 0);
+ out = the_output;
+ the_output = this;
+}
+
+void column::begin_page(int pageno, vunits page_length)
+{
+ assert(out != 0);
+ if (col) {
+ error("automatically outputting column before beginning next page");
+ output();
+ the_output->begin_page(pageno, page_length);
+ }
+ else
+ out->begin_page(pageno, page_length);
+
+}
+
+void column::flush()
+{
+ assert(out != 0);
+ out->flush();
+}
+
+int column::is_printing()
+{
+ assert(out != 0);
+ return out->is_printing();
+}
+
+vunits column::get_bottom()
+{
+ return bottom;
+}
+
+void column::add_output_line(output_line *ln)
+{
+ *tail = ln;
+ bottom += ln->distance();
+ bottom += ln->height();
+ ln->next = 0;
+ tail = &(*tail)->next;
+}
+
+void column::print_line(hunits page_offset, vunits pos, node *nd,
+ vunits before, vunits after)
+{
+ assert(out != 0);
+ add_output_line(new node_output_line(pos - bottom, nd, page_offset, before, after));
+}
+
+void column::vjustify(vunits pos, symbol typ)
+{
+ assert(out != 0);
+ add_output_line(new vjustify_output_line(pos - bottom, typ));
+}
+
+void column::transparent_char(unsigned char c)
+{
+ assert(out != 0);
+ transparent_output_line *tl = 0;
+ if (*tail)
+ tl = (*tail)->as_transparent_output_line();
+ if (!tl) {
+ tl = new transparent_output_line;
+ add_output_line(tl);
+ }
+ tl->append_char(c);
+}
+
+void column::copy_file(hunits page_offset, vunits pos, const char *filename)
+{
+ assert(out != 0);
+ add_output_line(new copy_file_output_line(pos - bottom, filename, page_offset));
+}
+
+void column::trim()
+{
+ output_line **spp = 0;
+ for (output_line **pp = &col; *pp; pp = &(*pp)->next)
+ if ((*pp)->as_vjustify_output_line() == 0)
+ spp = 0;
+ else if (!spp)
+ spp = pp;
+ if (spp) {
+ output_line *ln = *spp;
+ *spp = 0;
+ tail = spp;
+ while (ln) {
+ output_line *tem = ln->next;
+ bottom -= ln->distance();
+ bottom -= ln->height();
+ delete ln;
+ ln = tem;
+ }
+ }
+}
+
+void column::reset()
+{
+ bottom = V0;
+ for (output_line *ln = col; ln; ln = ln->next) {
+ bottom += ln->distance();
+ ln->reset();
+ bottom += ln->height();
+ }
+}
+
+void column::check_bottom()
+{
+ vunits b;
+ for (output_line *ln = col; ln; ln = ln->next) {
+ b += ln->distance();
+ b += ln->height();
+ }
+ assert(b == bottom);
+}
+
+void column::output()
+{
+ assert(out != 0);
+ vunits vpos(V0);
+ output_line *ln = col;
+ while (ln) {
+ vpos += ln->distance();
+ ln->output(out, vpos);
+ vpos += ln->height();
+ output_line *tem = ln->next;
+ delete ln;
+ ln = tem;
+ }
+ tail = &col;
+ bottom = V0;
+ col = 0;
+ the_output = out;
+ out = 0;
+}
+
+vunits column::get_last_extra_space()
+{
+ if (!col)
+ return V0;
+ for (output_line *p = col; p->next; p = p->next)
+ ;
+ return p->extra_space();
+}
+
+class justification_spec {
+ vunits height;
+ symbol *type;
+ vunits *amount;
+ int n;
+ int maxn;
+public:
+ justification_spec(vunits);
+ ~justification_spec();
+ void append(symbol t, vunits v);
+ void justify(output_line *, vunits *bottomp) const;
+};
+
+justification_spec::justification_spec(vunits h)
+: height(h), n(0), maxn(10)
+{
+ type = new symbol[maxn];
+ amount = new vunits[maxn];
+}
+
+justification_spec::~justification_spec()
+{
+ delete[] type;
+ delete[] amount;
+}
+
+void justification_spec::append(symbol t, vunits v)
+{
+ if (v <= V0) {
+ if (v < V0)
+ warning(WARN_RANGE,
+ "maximum space for vertical justification must not be negative");
+ else
+ warning(WARN_RANGE,
+ "maximum space for vertical justification must not be zero");
+ return;
+ }
+ if (n >= maxn) {
+ maxn *= 2;
+ symbol *old_type = type;
+ type = new symbol[maxn];
+ int i;
+ for (i = 0; i < n; i++)
+ type[i] = old_type[i];
+ delete[] old_type;
+ vunits *old_amount = amount;
+ amount = new vunits[maxn];
+ for (i = 0; i < n; i++)
+ amount[i] = old_amount[i];
+ delete[] old_amount;
+ }
+ assert(n < maxn);
+ type[n] = t;
+ amount[n] = v;
+ n++;
+}
+
+void justification_spec::justify(output_line *col, vunits *bottomp) const
+{
+ if (*bottomp >= height)
+ return;
+ vunits total;
+ output_line *p;
+ for (p = col; p; p = p->next) {
+ vjustify_output_line *sp = p->as_vjustify_output_line();
+ if (sp) {
+ symbol t = sp->type();
+ for (int i = 0; i < n; i++) {
+ if (t == type[i])
+ total += amount[i];
+ }
+ }
+ }
+ vunits gap = height - *bottomp;
+ for (p = col; p; p = p->next) {
+ vjustify_output_line *sp = p->as_vjustify_output_line();
+ if (sp) {
+ symbol t = sp->type();
+ for (int i = 0; i < n; i++) {
+ if (t == type[i]) {
+ if (total <= gap) {
+ sp->vary(amount[i]);
+ gap -= amount[i];
+ }
+ else {
+ // gap < total
+ vunits v = scale(amount[i], gap, total);
+ sp->vary(v);
+ gap -= v;
+ }
+ total -= amount[i];
+ }
+ }
+ }
+ }
+ assert(total == V0);
+ *bottomp = height - gap;
+}
+
+void column::justify(const justification_spec &js)
+{
+ check_bottom();
+ js.justify(col, &bottom);
+ check_bottom();
+}
+
+void column_justify()
+{
+ vunits height;
+ if (!the_column->is_active())
+ error("can't justify column - column not active");
+ else if (get_vunits(&height, 'v')) {
+ justification_spec js(height);
+ symbol nm = get_long_name(true /* required */);
+ if (!nm.is_null()) {
+ vunits v;
+ if (get_vunits(&v, 'v')) {
+ js.append(nm, v);
+ int err = 0;
+ while (has_arg()) {
+ nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ err = 1;
+ break;
+ }
+ if (!get_vunits(&v, 'v')) {
+ err = 1;
+ break;
+ }
+ js.append(nm, v);
+ }
+ if (!err)
+ the_column->justify(js);
+ }
+ }
+ }
+ skip_line();
+}
+
+void column_start()
+{
+ if (the_column->is_active())
+ error("can't start column - column already active");
+ else
+ the_column->start();
+ skip_line();
+}
+
+void column_output()
+{
+ if (!the_column->is_active())
+ error("can't output column - column not active");
+ else
+ the_column->output();
+ skip_line();
+}
+
+void column_trim()
+{
+ if (!the_column->is_active())
+ error("can't trim column - column not active");
+ else
+ the_column->trim();
+ skip_line();
+}
+
+void column_reset()
+{
+ if (!the_column->is_active())
+ error("can't reset column - column not active");
+ else
+ the_column->reset();
+ skip_line();
+}
+
+class column_bottom_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *column_bottom_reg::get_string()
+{
+ return i_to_a(the_column->get_bottom().to_units());
+}
+
+class column_extra_space_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *column_extra_space_reg::get_string()
+{
+ return i_to_a(the_column->get_last_extra_space().to_units());
+}
+
+class column_active_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *column_active_reg::get_string()
+{
+ return the_column->is_active() ? "1" : "0";
+}
+
+static int no_vjustify_mode = 0;
+
+class vjustify_node : public node {
+ symbol typ;
+public:
+ vjustify_node(symbol);
+ int reread(int *);
+ const char *type();
+ int same(node *);
+ node *copy();
+};
+
+vjustify_node::vjustify_node(symbol t)
+: typ(t)
+{
+}
+
+node *vjustify_node::copy()
+{
+ return new vjustify_node(typ, div_nest_level);
+}
+
+const char *vjustify_node::type()
+{
+ return "vjustify_node";
+}
+
+int vjustify_node::same(node *nd)
+{
+ return typ == ((vjustify_node *)nd)->typ;
+}
+
+int vjustify_node::reread(int *bolp)
+{
+ curdiv->vjustify(typ);
+ *bolp = 1;
+ return 1;
+}
+
+void macro_diversion::vjustify(symbol type)
+{
+ if (!no_vjustify_mode)
+ mac->append(new vjustify_node(type));
+}
+
+void top_level_diversion::vjustify(symbol type)
+{
+ if (no_space_mode || no_vjustify_mode)
+ return;
+ assert(first_page_begun); // I'm not sure about this.
+ the_output->vjustify(vertical_position, type);
+}
+
+void no_vjustify()
+{
+ skip_line();
+ no_vjustify_mode = 1;
+}
+
+void restore_vjustify()
+{
+ skip_line();
+ no_vjustify_mode = 0;
+}
+
+void init_column_requests()
+{
+ the_column = new column;
+ init_request("cols", column_start);
+ init_request("colo", column_output);
+ init_request("colj", column_justify);
+ init_request("colr", column_reset);
+ init_request("colt", column_trim);
+ init_request("nvj", no_vjustify);
+ init_request("rvj", restore_vjustify);
+ register_dictionary.define(".colb", new column_bottom_reg);
+ register_dictionary.define(".colx", new column_extra_space_reg);
+ register_dictionary.define(".cola", new column_active_reg);
+ register_dictionary.define(".nvj",
+ new readonly_register(&no_vjustify_mode));
+}
+
+#endif /* COLUMN */
diff --git a/src/roff/troff/dictionary.cpp b/src/roff/troff/dictionary.cpp
new file mode 100644
index 0000000..08afbd3
--- /dev/null
+++ b/src/roff/troff/dictionary.cpp
@@ -0,0 +1,209 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include "troff.h"
+#include "dictionary.h"
+
+// is 'p' a good size for a hash table
+
+static int is_good_size(unsigned int p)
+{
+ const unsigned int SMALL = 10;
+ unsigned int i;
+ for (i = 2; i <= p/2; i++)
+ if (p % i == 0)
+ return 0;
+ for (i = 0x100; i != 0; i <<= 8)
+ if (i % p <= SMALL || i % p > p - SMALL)
+ return 0;
+ return 1;
+}
+
+dictionary::dictionary(int n) : size(n), used(0), threshold(0.5), factor(1.5)
+{
+ table = new association[n];
+}
+
+// see Knuth, Sorting and Searching, p518, Algorithm L
+// we can't use double-hashing because we want a remove function
+
+void *dictionary::lookup(symbol s, void *v)
+{
+ int i;
+ for (i = int(s.hash() % size);
+ table[i].v != 0;
+ i == 0 ? i = size - 1: --i)
+ if (s == table[i].s) {
+ if (v != 0) {
+ void *temp = table[i].v;
+ table[i].v = v;
+ return temp;
+ }
+ else
+ return table[i].v;
+ }
+ if (v == 0)
+ return 0;
+ ++used;
+ table[i].v = v;
+ table[i].s = s;
+ if ((double)used/(double)size >= threshold || used + 1 >= size) {
+ int old_size = size;
+ size = int(size*factor);
+ while (!is_good_size(size))
+ ++size;
+ association *old_table = table;
+ table = new association[size];
+ used = 0;
+ for (i = 0; i < old_size; i++)
+ if (old_table[i].v != 0)
+ (void)lookup(old_table[i].s, old_table[i].v);
+ delete[] old_table;
+ }
+ return 0;
+}
+
+void *dictionary::lookup(const char *p)
+{
+ symbol s(p, MUST_ALREADY_EXIST);
+ if (s.is_null())
+ return 0;
+ else
+ return lookup(s);
+}
+
+// see Knuth, Sorting and Searching, p527, Algorithm R
+
+void *dictionary::remove(symbol s)
+{
+ // this relies on the fact that we are using linear probing
+ int i;
+ for (i = int(s.hash() % size);
+ table[i].v != 0 && s != table[i].s;
+ i == 0 ? i = size - 1: --i)
+ ;
+ void *p = table[i].v;
+ while (table[i].v != 0) {
+ table[i].v = 0;
+ int j = i;
+ int r;
+ do {
+ --i;
+ if (i < 0)
+ i = size - 1;
+ if (table[i].v == 0)
+ break;
+ r = int(table[i].s.hash() % size);
+ } while ((i <= r && r < j) || (r < j && j < i) || (j < i && i <= r));
+ table[j] = table[i];
+ }
+ if (p != 0)
+ --used;
+ return p;
+}
+
+dictionary_iterator::dictionary_iterator(dictionary &d) : dict(&d), i(0)
+{
+}
+
+int dictionary_iterator::get(symbol *sp, void **vp)
+{
+ for (; i < dict->size; i++)
+ if (dict->table[i].v) {
+ *sp = dict->table[i].s;
+ *vp = dict->table[i].v;
+ i++;
+ return 1;
+ }
+ return 0;
+}
+
+object_dictionary_iterator::object_dictionary_iterator(object_dictionary &od)
+: di(od.d)
+{
+}
+
+object::object() : rcount(0)
+{
+}
+
+object::~object()
+{
+}
+
+void object::add_reference()
+{
+ rcount += 1;
+}
+
+void object::remove_reference()
+{
+ if (--rcount == 0)
+ delete this;
+}
+
+object_dictionary::object_dictionary(int n) : d(n)
+{
+}
+
+object *object_dictionary::lookup(symbol nm)
+{
+ return (object *)d.lookup(nm);
+}
+
+void object_dictionary::define(symbol nm, object *obj)
+{
+ obj->add_reference();
+ obj = (object *)d.lookup(nm, obj);
+ if (obj)
+ obj->remove_reference();
+}
+
+void object_dictionary::rename(symbol oldnm, symbol newnm)
+{
+ object *obj = (object *)d.remove(oldnm);
+ if (obj) {
+ obj = (object *)d.lookup(newnm, obj);
+ if (obj)
+ obj->remove_reference();
+ }
+}
+
+void object_dictionary::remove(symbol nm)
+{
+ object *obj = (object *)d.remove(nm);
+ if (obj)
+ obj->remove_reference();
+}
+
+// Return non-zero if oldnm was defined.
+
+int object_dictionary::alias(symbol newnm, symbol oldnm)
+{
+ object *obj = (object *)d.lookup(oldnm);
+ if (obj) {
+ obj->add_reference();
+ obj = (object *)d.lookup(newnm, obj);
+ if (obj)
+ obj->remove_reference();
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/roff/troff/dictionary.h b/src/roff/troff/dictionary.h
new file mode 100644
index 0000000..71c4f3f
--- /dev/null
+++ b/src/roff/troff/dictionary.h
@@ -0,0 +1,91 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+
+// there is no distinction between name with no value and name with NULL value
+// null names are not permitted (they will be ignored).
+
+struct association {
+ symbol s;
+ void *v;
+ association() : v(0) {}
+};
+
+class dictionary;
+
+class dictionary_iterator {
+ dictionary *dict;
+ int i;
+public:
+ dictionary_iterator(dictionary &);
+ int get(symbol *, void **);
+};
+
+class dictionary {
+ int size;
+ int used;
+ double threshold;
+ double factor;
+ association *table;
+ void rehash(int);
+public:
+ dictionary(int);
+ void *lookup(symbol s, void *v=0); // returns value associated with key
+ void *lookup(const char *);
+ // if second parameter not NULL, value will be replaced
+ void *remove(symbol);
+ friend class dictionary_iterator;
+};
+
+class object {
+ int rcount;
+ public:
+ object();
+ virtual ~object();
+ void add_reference();
+ void remove_reference();
+};
+
+class object_dictionary;
+
+class object_dictionary_iterator {
+ dictionary_iterator di;
+public:
+ object_dictionary_iterator(object_dictionary &);
+ int get(symbol *, object **);
+};
+
+class object_dictionary {
+ dictionary d;
+public:
+ object_dictionary(int);
+ object *lookup(symbol nm);
+ void define(symbol nm, object *obj);
+ void rename(symbol oldnm, symbol newnm);
+ void remove(symbol nm);
+ int alias(symbol newnm, symbol oldnm);
+ friend class object_dictionary_iterator;
+};
+
+
+inline int object_dictionary_iterator::get(symbol *sp, object **op)
+{
+ return di.get(sp, (void **)op);
+}
diff --git a/src/roff/troff/div.cpp b/src/roff/troff/div.cpp
new file mode 100644
index 0000000..d195baf
--- /dev/null
+++ b/src/roff/troff/div.cpp
@@ -0,0 +1,1212 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+// diversions
+
+#include "troff.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+
+#include "nonposix.h"
+
+bool is_exit_underway = false;
+bool is_eoi_macro_finished = false;
+bool seen_last_page_ejector = false;
+static bool began_page_in_eoi_macro = false;
+int last_page_number = 0; // if > 0, the number of the last page
+ // specified with -o
+
+static int last_post_line_extra_space = 0; // needed for \n(.a
+static int nl_reg_contents = -1;
+static int dl_reg_contents = 0;
+static int dn_reg_contents = 0;
+static int vertical_position_traps_flag = 1;
+static vunits truncated_space;
+static vunits needed_space;
+
+diversion::diversion(symbol s)
+: prev(0), nm(s), vertical_position(V0), high_water_mark(V0),
+ any_chars_added(0), no_space_mode(0), needs_push(0), saved_seen_break(0),
+ saved_seen_space(0), saved_seen_eol(0), saved_suppress_next_eol(0),
+ marked_place(V0)
+{
+}
+
+struct vertical_size {
+ vunits pre_extra, post_extra, pre, post;
+ vertical_size(vunits vs, vunits post_vs);
+};
+
+vertical_size::vertical_size(vunits vs, vunits post_vs)
+: pre_extra(V0), post_extra(V0), pre(vs), post(post_vs)
+{
+}
+
+void node::set_vertical_size(vertical_size *)
+{
+}
+
+void extra_size_node::set_vertical_size(vertical_size *v)
+{
+ if (n < V0) {
+ if (-n > v->pre_extra)
+ v->pre_extra = -n;
+ }
+ else if (n > v->post_extra)
+ v->post_extra = n;
+}
+
+void vertical_size_node::set_vertical_size(vertical_size *v)
+{
+ if (n < V0)
+ v->pre = -n;
+ else
+ v->post = n;
+}
+
+top_level_diversion *topdiv;
+
+diversion *curdiv;
+
+void do_divert(int append, int boxing)
+{
+ tok.skip();
+ symbol nm = get_name();
+ if (nm.is_null()) {
+ if (curdiv->prev) {
+ curenv->seen_break = curdiv->saved_seen_break;
+ curenv->seen_space = curdiv->saved_seen_space;
+ curenv->seen_eol = curdiv->saved_seen_eol;
+ curenv->suppress_next_eol = curdiv->saved_suppress_next_eol;
+ if (boxing) {
+ curenv->line = curdiv->saved_line;
+ curenv->width_total = curdiv->saved_width_total;
+ curenv->space_total = curdiv->saved_space_total;
+ curenv->saved_indent = curdiv->saved_saved_indent;
+ curenv->target_text_length = curdiv->saved_target_text_length;
+ curenv->prev_line_interrupted = curdiv->saved_prev_line_interrupted;
+ }
+ diversion *temp = curdiv;
+ curdiv = curdiv->prev;
+ delete temp;
+ }
+ else
+ warning(WARN_DI, "diversion stack underflow");
+ }
+ else {
+ macro_diversion *md = new macro_diversion(nm, append);
+ md->prev = curdiv;
+ curdiv = md;
+ curdiv->saved_seen_break = curenv->seen_break;
+ curdiv->saved_seen_space = curenv->seen_space;
+ curdiv->saved_seen_eol = curenv->seen_eol;
+ curdiv->saved_suppress_next_eol = curenv->suppress_next_eol;
+ curenv->seen_break = 0;
+ curenv->seen_space = 0;
+ curenv->seen_eol = 0;
+ if (boxing) {
+ curdiv->saved_line = curenv->line;
+ curdiv->saved_width_total = curenv->width_total;
+ curdiv->saved_space_total = curenv->space_total;
+ curdiv->saved_saved_indent = curenv->saved_indent;
+ curdiv->saved_target_text_length = curenv->target_text_length;
+ curdiv->saved_prev_line_interrupted = curenv->prev_line_interrupted;
+ curenv->line = 0;
+ curenv->start_line();
+ }
+ }
+ skip_line();
+}
+
+void divert()
+{
+ do_divert(0, 0);
+}
+
+void divert_append()
+{
+ do_divert(1, 0);
+}
+
+void box()
+{
+ do_divert(0, 1);
+}
+
+void box_append()
+{
+ do_divert(1, 1);
+}
+
+void diversion::need(vunits n)
+{
+ vunits d = distance_to_next_trap();
+ if (d < n) {
+ truncated_space = -d;
+ needed_space = n;
+ space(d, 1);
+ }
+}
+
+macro_diversion::macro_diversion(symbol s, int append)
+: diversion(s), max_width(H0)
+{
+#if 0
+ if (append) {
+ /* We don't allow recursive appends, e.g.:
+
+ .da a
+ .a
+ .di
+
+ This causes an infinite loop in troff anyway.
+ This is because the user could do
+
+ .as a foo
+
+ in the diversion, and this would mess things up royally,
+ since there would be two things appending to the same
+ macro_header.
+ To make it work, we would have to copy the _contents_
+ of the macro into which we were diverting; this doesn't
+ strike me as worthwhile.
+ However,
+
+ .di a
+ .a
+ .a
+ .di
+
+ will work and will make 'a' contain two copies of what it contained
+ before; in troff, 'a' would contain nothing. */
+ request_or_macro *rm
+ = (request_or_macro *)request_dictionary.remove(s);
+ if (!rm || (mac = rm->to_macro()) == 0)
+ mac = new macro;
+ }
+ else
+ mac = new macro;
+#endif
+ // We can now catch the situation described above by comparing
+ // the length of the charlist in the macro_header with the length
+ // stored in the macro. When we detect this, we copy the contents.
+ mac = new macro(1);
+ if (append) {
+ request_or_macro *rm
+ = (request_or_macro *)request_dictionary.lookup(s);
+ if (rm) {
+ macro *m = rm->to_macro();
+ if (m)
+ *mac = *m;
+ }
+ }
+}
+
+macro_diversion::~macro_diversion()
+{
+ request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
+ macro *m = rm ? rm->to_macro() : 0;
+ if (m) {
+ *m = *mac;
+ delete mac;
+ }
+ else
+ request_dictionary.define(nm, mac);
+ mac = 0;
+ dl_reg_contents = max_width.to_units();
+ dn_reg_contents = vertical_position.to_units();
+}
+
+vunits macro_diversion::distance_to_next_trap()
+{
+ if (!diversion_trap.is_null() && diversion_trap_pos > vertical_position)
+ return diversion_trap_pos - vertical_position;
+ else
+ // Subtract vresolution so that vunits::vunits does not overflow.
+ return vunits(INT_MAX - vresolution);
+}
+
+void macro_diversion::transparent_output(unsigned char c)
+{
+ mac->append(c);
+}
+
+void macro_diversion::transparent_output(node *n)
+{
+ mac->append(n);
+}
+
+void macro_diversion::output(node *nd, int retain_size,
+ vunits vs, vunits post_vs, hunits width)
+{
+ no_space_mode = 0;
+ vertical_size v(vs, post_vs);
+ while (nd != 0) {
+ nd->set_vertical_size(&v);
+ node *temp = nd;
+ nd = nd->next;
+ if (temp->interpret(mac))
+ delete temp;
+ else {
+#if 1
+ temp->freeze_space();
+#endif
+ mac->append(temp);
+ }
+ }
+ last_post_line_extra_space = v.post_extra.to_units();
+ if (!retain_size) {
+ v.pre = vs;
+ v.post = post_vs;
+ }
+ if (width > max_width)
+ max_width = width;
+ vunits x = v.pre + v.pre_extra + v.post + v.post_extra;
+ if (vertical_position_traps_flag
+ && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
+ && diversion_trap_pos <= vertical_position + x) {
+ vunits trunc = vertical_position + x - diversion_trap_pos;
+ if (trunc > v.post)
+ trunc = v.post;
+ v.post -= trunc;
+ x -= trunc;
+ truncated_space = trunc;
+ spring_trap(diversion_trap);
+ }
+ mac->append(new vertical_size_node(-v.pre));
+ mac->append(new vertical_size_node(v.post));
+ mac->append('\n');
+ vertical_position += x;
+ if (vertical_position - v.post > high_water_mark)
+ high_water_mark = vertical_position - v.post;
+}
+
+void macro_diversion::space(vunits n, int)
+{
+ if (vertical_position_traps_flag
+ && !diversion_trap.is_null() && diversion_trap_pos > vertical_position
+ && diversion_trap_pos <= vertical_position + n) {
+ truncated_space = vertical_position + n - diversion_trap_pos;
+ n = diversion_trap_pos - vertical_position;
+ spring_trap(diversion_trap);
+ }
+ else if (n + vertical_position < V0)
+ n = -vertical_position;
+ mac->append(new diverted_space_node(n));
+ vertical_position += n;
+}
+
+void macro_diversion::copy_file(const char *filename)
+{
+ mac->append(new diverted_copy_file_node(filename));
+}
+
+top_level_diversion::top_level_diversion()
+: page_number(0), page_count(0), last_page_count(-1),
+ page_length(units_per_inch*11),
+ prev_page_offset(units_per_inch), page_offset(units_per_inch),
+ page_trap_list(0), have_next_page_number(0),
+ ejecting_page(0), before_first_page(1)
+{
+}
+
+// find the next trap after pos
+
+trap *top_level_diversion::find_next_trap(vunits *next_trap_pos)
+{
+ trap *next_trap = 0;
+ for (trap *pt = page_trap_list; pt != 0; pt = pt->next)
+ if (!pt->nm.is_null()) {
+ if (pt->position >= V0) {
+ if (pt->position > vertical_position
+ && pt->position < page_length
+ && (next_trap == 0 || pt->position < *next_trap_pos)) {
+ next_trap = pt;
+ *next_trap_pos = pt->position;
+ }
+ }
+ else {
+ vunits pos = pt->position;
+ pos += page_length;
+ if (pos > 0
+ && pos > vertical_position
+ && (next_trap == 0 || pos < *next_trap_pos)) {
+ next_trap = pt;
+ *next_trap_pos = pos;
+ }
+ }
+ }
+ return next_trap;
+}
+
+vunits top_level_diversion::distance_to_next_trap()
+{
+ vunits d;
+ if (!find_next_trap(&d))
+ return page_length - vertical_position;
+ else
+ return d - vertical_position;
+}
+
+void top_level_diversion::output(node *nd, int retain_size,
+ vunits vs, vunits post_vs, hunits width)
+{
+ no_space_mode = 0;
+ vunits next_trap_pos;
+ trap *next_trap = find_next_trap(&next_trap_pos);
+ if (before_first_page && begin_page())
+ fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
+ vertical_size v(vs, post_vs);
+ for (node *tem = nd; tem != 0; tem = tem->next)
+ tem->set_vertical_size(&v);
+ last_post_line_extra_space = v.post_extra.to_units();
+ if (!retain_size) {
+ v.pre = vs;
+ v.post = post_vs;
+ }
+ vertical_position += v.pre;
+ vertical_position += v.pre_extra;
+ the_output->print_line(page_offset, vertical_position, nd,
+ v.pre + v.pre_extra, v.post_extra, width);
+ vertical_position += v.post_extra;
+ if (vertical_position > high_water_mark)
+ high_water_mark = vertical_position;
+ if (vertical_position_traps_flag && vertical_position >= page_length)
+ begin_page();
+ else if (vertical_position_traps_flag
+ && next_trap != 0 && vertical_position >= next_trap_pos) {
+ nl_reg_contents = vertical_position.to_units();
+ truncated_space = v.post;
+ spring_trap(next_trap->nm);
+ }
+ else if (v.post > V0) {
+ vertical_position += v.post;
+ if (vertical_position_traps_flag
+ && next_trap != 0 && vertical_position >= next_trap_pos) {
+ truncated_space = vertical_position - next_trap_pos;
+ vertical_position = next_trap_pos;
+ nl_reg_contents = vertical_position.to_units();
+ spring_trap(next_trap->nm);
+ }
+ else if (vertical_position_traps_flag && vertical_position >= page_length)
+ begin_page();
+ else
+ nl_reg_contents = vertical_position.to_units();
+ }
+ else
+ nl_reg_contents = vertical_position.to_units();
+}
+
+void top_level_diversion::transparent_output(unsigned char c)
+{
+ if (before_first_page && begin_page())
+ // This can only happen with the .output request.
+ fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
+ const char *s = asciify(c);
+ while (*s)
+ the_output->transparent_char(*s++);
+}
+
+void top_level_diversion::transparent_output(node * /*n*/)
+{
+ if (getenv("GROFF_ENABLE_TRANSPARENCY_WARNINGS") != 0 /* nullptr */)
+ error("can't transparently output node at top level");
+}
+
+void top_level_diversion::copy_file(const char *filename)
+{
+ if (before_first_page && begin_page())
+ fatal("sorry, I didn't manage to begin the first page in time: use an explicit .br request");
+ the_output->copy_file(page_offset, vertical_position, filename);
+}
+
+void top_level_diversion::space(vunits n, int forced)
+{
+ if (no_space_mode) {
+ if (!forced)
+ return;
+ else
+ no_space_mode = 0;
+ }
+ if (before_first_page) {
+ begin_page(n);
+ return;
+ }
+ vunits next_trap_pos;
+ trap *next_trap = find_next_trap(&next_trap_pos);
+ vunits y = vertical_position + n;
+ if (curenv->get_vertical_spacing().to_units())
+ curenv->seen_space += n.to_units()
+ / curenv->get_vertical_spacing().to_units();
+ if (vertical_position_traps_flag && next_trap != 0 && y >= next_trap_pos) {
+ vertical_position = next_trap_pos;
+ nl_reg_contents = vertical_position.to_units();
+ truncated_space = y - vertical_position;
+ spring_trap(next_trap->nm);
+ }
+ else if (y < V0) {
+ vertical_position = V0;
+ nl_reg_contents = vertical_position.to_units();
+ }
+ else if (vertical_position_traps_flag && y >= page_length && n >= V0)
+ begin_page(y - page_length);
+ else {
+ vertical_position = y;
+ nl_reg_contents = vertical_position.to_units();
+ }
+}
+
+trap::trap(symbol s, vunits n, trap *p)
+: next(p), position(n), nm(s)
+{
+}
+
+void top_level_diversion::add_trap(symbol nam, vunits pos)
+{
+ trap *first_free_slot = 0;
+ trap **p;
+ for (p = &page_trap_list; *p; p = &(*p)->next) {
+ if ((*p)->nm.is_null()) {
+ if (first_free_slot == 0)
+ first_free_slot = *p;
+ }
+ else if ((*p)->position == pos) {
+ (*p)->nm = nam;
+ return;
+ }
+ }
+ if (first_free_slot) {
+ first_free_slot->nm = nam;
+ first_free_slot->position = pos;
+ }
+ else
+ *p = new trap(nam, pos, 0);
+}
+
+void top_level_diversion::remove_trap(symbol nam)
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->nm == nam) {
+ p->nm = NULL_SYMBOL;
+ return;
+ }
+}
+
+void top_level_diversion::remove_trap_at(vunits pos)
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->position == pos) {
+ p->nm = NULL_SYMBOL;
+ return;
+ }
+}
+
+void top_level_diversion::change_trap(symbol nam, vunits pos)
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->nm == nam) {
+ p->position = pos;
+ return;
+ }
+ warning(WARN_MAC, "cannot move unplanted trap macro '%1'",
+ nam.contents());
+}
+
+void top_level_diversion::print_traps()
+{
+ for (trap *p = page_trap_list; p; p = p->next)
+ if (p->nm.is_null())
+ fprintf(stderr, " empty\n");
+ else
+ fprintf(stderr, "%s\t%d\n", p->nm.contents(), p->position.to_units());
+ fflush(stderr);
+}
+
+void end_diversions()
+{
+ while (curdiv != topdiv) {
+ error("automatically ending diversion '%1' on exit",
+ curdiv->nm.contents());
+ diversion *tem = curdiv;
+ curdiv = curdiv->prev;
+ delete tem;
+ }
+}
+
+void cleanup_and_exit(int exit_code)
+{
+ if (the_output) {
+ the_output->trailer(topdiv->get_page_length());
+ // If we're already dying, don't call the_output's destructor. See
+ // node.cpp:real_output_file::~real_output_file().
+ if (!the_output->is_dying)
+ delete the_output;
+ }
+ FLUSH_INPUT_PIPE(STDIN_FILENO);
+ exit(exit_code);
+}
+
+// Returns non-zero if it sprung a top-of-page trap.
+// The optional parameter is for the .trunc register.
+int top_level_diversion::begin_page(vunits n)
+{
+ if (is_exit_underway) {
+ if (page_count == last_page_count
+ ? curenv->is_empty()
+ : (is_eoi_macro_finished && (seen_last_page_ejector
+ || began_page_in_eoi_macro)))
+ cleanup_and_exit(EXIT_SUCCESS);
+ if (!is_eoi_macro_finished)
+ began_page_in_eoi_macro = true;
+ }
+ if (last_page_number > 0 && page_number == last_page_number)
+ cleanup_and_exit(EXIT_SUCCESS);
+ if (!the_output)
+ init_output();
+ ++page_count;
+ if (have_next_page_number) {
+ page_number = next_page_number;
+ have_next_page_number = 0;
+ }
+ else if (before_first_page == 1)
+ page_number = 1;
+ else
+ page_number++;
+ // spring the top of page trap if there is one
+ vunits next_trap_pos;
+ vertical_position = -vresolution;
+ trap *next_trap = find_next_trap(&next_trap_pos);
+ vertical_position = V0;
+ high_water_mark = V0;
+ ejecting_page = 0;
+ // If before_first_page was 2, then the top of page transition was undone
+ // using eg .nr nl 0-1. See nl_reg::set_value.
+ if (before_first_page != 2)
+ the_output->begin_page(page_number, page_length);
+ before_first_page = 0;
+ nl_reg_contents = vertical_position.to_units();
+ if (vertical_position_traps_flag && next_trap != 0 && next_trap_pos == V0) {
+ truncated_space = n;
+ spring_trap(next_trap->nm);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void continue_page_eject()
+{
+ if (topdiv->get_ejecting()) {
+ if (curdiv != topdiv)
+ error("can't continue page ejection because of current diversion");
+ else if (!vertical_position_traps_flag)
+ error("can't continue page ejection because vertical position traps disabled");
+ else {
+ push_page_ejector();
+ topdiv->space(topdiv->get_page_length(), 1);
+ }
+ }
+}
+
+void top_level_diversion::set_next_page_number(int n)
+{
+ next_page_number= n;
+ have_next_page_number = 1;
+}
+
+int top_level_diversion::get_next_page_number()
+{
+ return have_next_page_number ? next_page_number : page_number + 1;
+}
+
+void top_level_diversion::set_page_length(vunits n)
+{
+ page_length = n;
+}
+
+diversion::~diversion()
+{
+}
+
+void page_offset()
+{
+ hunits n;
+ // The troff manual says that the default scaling indicator is v,
+ // but it is in fact m: v wouldn't make sense for a horizontally
+ // oriented request.
+ if (!has_arg() || !get_hunits(&n, 'm', topdiv->page_offset))
+ n = topdiv->prev_page_offset;
+ topdiv->prev_page_offset = topdiv->page_offset;
+ topdiv->page_offset = n;
+ topdiv->modified_tag.incl(MTSM_PO);
+ skip_line();
+}
+
+void page_length()
+{
+ vunits n;
+ if (has_arg() && get_vunits(&n, 'v', topdiv->get_page_length()))
+ topdiv->set_page_length(n);
+ else
+ topdiv->set_page_length(11*units_per_inch);
+ skip_line();
+}
+
+void when_request()
+{
+ vunits n;
+ if (get_vunits(&n, 'v')) {
+ symbol s = get_name();
+ if (s.is_null())
+ topdiv->remove_trap_at(n);
+ else
+ topdiv->add_trap(s, n);
+ }
+ skip_line();
+}
+
+void begin_page()
+{
+ int got_arg = 0;
+ int n = 0; /* pacify compiler */
+ if (has_arg() && get_integer(&n, topdiv->get_page_number()))
+ got_arg = 1;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (curdiv == topdiv) {
+ if (topdiv->before_first_page) {
+ if (!break_flag) {
+ if (got_arg)
+ topdiv->set_next_page_number(n);
+ if (got_arg || !topdiv->no_space_mode)
+ topdiv->begin_page();
+ }
+ else if (topdiv->no_space_mode && !got_arg)
+ topdiv->begin_page();
+ else {
+ /* Given this
+
+ .wh 0 x
+ .de x
+ .tm \\n%
+ ..
+ .bp 3
+
+ troff prints
+
+ 1
+ 3
+
+ This code makes groff do the same. */
+
+ push_page_ejector();
+ topdiv->begin_page();
+ if (got_arg)
+ topdiv->set_next_page_number(n);
+ topdiv->set_ejecting();
+ }
+ }
+ else {
+ push_page_ejector();
+ if (break_flag)
+ curenv->do_break();
+ if (got_arg)
+ topdiv->set_next_page_number(n);
+ if (!(topdiv->no_space_mode && !got_arg))
+ topdiv->set_ejecting();
+ }
+ }
+ tok.next();
+}
+
+void no_space()
+{
+ curdiv->no_space_mode = 1;
+ skip_line();
+}
+
+void restore_spacing()
+{
+ curdiv->no_space_mode = 0;
+ skip_line();
+}
+
+/* It is necessary to generate a break before reading the argument,
+because otherwise arguments using | will be wrong. But if we just
+generate a break as usual, then the line forced out may spring a trap
+and thus push a macro onto the input stack before we have had a chance
+to read the argument to the sp request. We resolve this dilemma by
+setting, before generating the break, a flag which will postpone the
+actual pushing of the macro associated with the trap sprung by the
+outputting of the line forced out by the break till after we have read
+the argument to the request. If the break did cause a trap to be
+sprung, then we don't actually do the space. */
+
+void space_request()
+{
+ postpone_traps();
+ if (break_flag)
+ curenv->do_break();
+ vunits n;
+ if (!has_arg() || !get_vunits(&n, 'v'))
+ n = curenv->get_vertical_spacing();
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (!unpostpone_traps() && !curdiv->no_space_mode)
+ curdiv->space(n);
+ else
+ // The line might have had line spacing that was truncated.
+ truncated_space += n;
+
+ tok.next();
+}
+
+void blank_line()
+{
+ curenv->do_break();
+ if (!trap_sprung_flag && !curdiv->no_space_mode)
+ curdiv->space(curenv->get_vertical_spacing());
+ else
+ truncated_space += curenv->get_vertical_spacing();
+}
+
+/* need_space might spring a trap and so we must be careful that the
+BEGIN_TRAP token is not skipped over. */
+
+void need_space()
+{
+ vunits n;
+ if (!has_arg() || !get_vunits(&n, 'v'))
+ n = curenv->get_vertical_spacing();
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ curdiv->need(n);
+ tok.next();
+}
+
+void page_number()
+{
+ int n;
+
+ // the ps4html register is set if we are using -Tps
+ // to generate images for html
+ // XXX: Yuck! Get rid of this; macro packages already test the
+ // register before invoking .pn.
+ reg *r = (reg *)register_dictionary.lookup("ps4html");
+ if (r == NULL)
+ if (has_arg() && get_integer(&n, topdiv->get_page_number()))
+ topdiv->set_next_page_number(n);
+ skip_line();
+}
+
+vunits saved_space;
+
+void save_vertical_space()
+{
+ vunits x;
+ if (!has_arg() || !get_vunits(&x, 'v'))
+ x = curenv->get_vertical_spacing();
+ if (curdiv->distance_to_next_trap() > x)
+ curdiv->space(x, 1);
+ else
+ saved_space = x;
+ skip_line();
+}
+
+void output_saved_vertical_space()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (saved_space > V0)
+ curdiv->space(saved_space, 1);
+ saved_space = V0;
+ tok.next();
+}
+
+void flush_output()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (the_output)
+ the_output->flush();
+ tok.next();
+}
+
+void macro_diversion::set_diversion_trap(symbol s, vunits n)
+{
+ diversion_trap = s;
+ diversion_trap_pos = n;
+}
+
+void macro_diversion::clear_diversion_trap()
+{
+ diversion_trap = NULL_SYMBOL;
+}
+
+void top_level_diversion::set_diversion_trap(symbol, vunits)
+{
+ error("can't set diversion trap when no current diversion");
+}
+
+void top_level_diversion::clear_diversion_trap()
+{
+ error("can't clear diversion trap when no current diversion");
+}
+
+void diversion_trap()
+{
+ vunits n;
+ if (has_arg() && get_vunits(&n, 'v')) {
+ symbol s = get_name();
+ if (!s.is_null())
+ curdiv->set_diversion_trap(s, n);
+ else
+ curdiv->clear_diversion_trap();
+ }
+ else
+ curdiv->clear_diversion_trap();
+ skip_line();
+}
+
+void change_trap()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ vunits x;
+ if (has_arg() && get_vunits(&x, 'v'))
+ topdiv->change_trap(s, x);
+ else
+ topdiv->remove_trap(s);
+ }
+ skip_line();
+}
+
+void print_traps()
+{
+ topdiv->print_traps();
+ skip_line();
+}
+
+void mark()
+{
+ symbol s = get_name();
+ if (s.is_null())
+ curdiv->marked_place = curdiv->get_vertical_position();
+ else if (curdiv == topdiv)
+ set_number_reg(s, nl_reg_contents);
+ else
+ set_number_reg(s, curdiv->get_vertical_position().to_units());
+ skip_line();
+}
+
+// This is truly bizarre. It is documented in the SQ manual.
+
+void return_request()
+{
+ vunits dist = curdiv->marked_place - curdiv->get_vertical_position();
+ if (has_arg()) {
+ if (tok.ch() == '-') {
+ tok.next();
+ vunits x;
+ if (get_vunits(&x, 'v'))
+ dist = -x;
+ }
+ else {
+ vunits x;
+ if (get_vunits(&x, 'v'))
+ dist = x >= V0 ? x - curdiv->get_vertical_position() : V0;
+ }
+ }
+ if (dist < V0)
+ curdiv->space(dist);
+ skip_line();
+}
+
+void vertical_position_traps()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ vertical_position_traps_flag = (n != 0);
+ else
+ vertical_position_traps_flag = 1;
+ skip_line();
+}
+
+class page_offset_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool page_offset_reg::get_value(units *res)
+{
+ *res = topdiv->get_page_offset().to_units();
+ return true;
+}
+
+const char *page_offset_reg::get_string()
+{
+ return i_to_a(topdiv->get_page_offset().to_units());
+}
+
+class page_length_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool page_length_reg::get_value(units *res)
+{
+ *res = topdiv->get_page_length().to_units();
+ return true;
+}
+
+const char *page_length_reg::get_string()
+{
+ return i_to_a(topdiv->get_page_length().to_units());
+}
+
+class vertical_position_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool vertical_position_reg::get_value(units *res)
+{
+ if (curdiv == topdiv && topdiv->before_first_page)
+ *res = -1;
+ else
+ *res = curdiv->get_vertical_position().to_units();
+ return true;
+}
+
+const char *vertical_position_reg::get_string()
+{
+ if (curdiv == topdiv && topdiv->before_first_page)
+ return "-1";
+ else
+ return i_to_a(curdiv->get_vertical_position().to_units());
+}
+
+class high_water_mark_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool high_water_mark_reg::get_value(units *res)
+{
+ *res = curdiv->get_high_water_mark().to_units();
+ return true;
+}
+
+const char *high_water_mark_reg::get_string()
+{
+ return i_to_a(curdiv->get_high_water_mark().to_units());
+}
+
+class distance_to_next_trap_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool distance_to_next_trap_reg::get_value(units *res)
+{
+ *res = curdiv->distance_to_next_trap().to_units();
+ return true;
+}
+
+const char *distance_to_next_trap_reg::get_string()
+{
+ return i_to_a(curdiv->distance_to_next_trap().to_units());
+}
+
+class diversion_name_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *diversion_name_reg::get_string()
+{
+ return curdiv->get_diversion_name();
+}
+
+class page_number_reg : public general_reg {
+public:
+ page_number_reg();
+ bool get_value(units *);
+ void set_value(units);
+};
+
+page_number_reg::page_number_reg()
+{
+}
+
+void page_number_reg::set_value(units n)
+{
+ topdiv->set_page_number(n);
+}
+
+bool page_number_reg::get_value(units *res)
+{
+ *res = topdiv->get_page_number();
+ return true;
+}
+
+class next_page_number_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *next_page_number_reg::get_string()
+{
+ return i_to_a(topdiv->get_next_page_number());
+}
+
+class page_ejecting_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *page_ejecting_reg::get_string()
+{
+ return i_to_a(topdiv->get_ejecting());
+}
+
+class constant_vunits_reg : public reg {
+ vunits *p;
+public:
+ constant_vunits_reg(vunits *);
+ const char *get_string();
+};
+
+constant_vunits_reg::constant_vunits_reg(vunits *q) : p(q)
+{
+}
+
+const char *constant_vunits_reg::get_string()
+{
+ return i_to_a(p->to_units());
+}
+
+class nl_reg : public variable_reg {
+public:
+ nl_reg();
+ void set_value(units);
+};
+
+nl_reg::nl_reg() : variable_reg(&nl_reg_contents)
+{
+}
+
+void nl_reg::set_value(units n)
+{
+ variable_reg::set_value(n);
+ // Setting nl to a negative value when the vertical position in
+ // the top-level diversion is 0 undoes the top of page transition,
+ // so that the header macro will be called as if the top of page
+ // transition hasn't happened. This is used by Larry Wall's
+ // wrapman program. Setting before_first_page to 2 rather than 1,
+ // tells top_level_diversion::begin_page not to call
+ // output_file::begin_page again.
+ if (n < 0 && topdiv->get_vertical_position() == V0)
+ topdiv->before_first_page = 2;
+}
+
+class no_space_mode_reg : public reg {
+public:
+ bool get_value(units *);
+ const char *get_string();
+};
+
+bool no_space_mode_reg::get_value(units *val)
+{
+ *val = curdiv->no_space_mode;
+ return true;
+}
+
+const char *no_space_mode_reg::get_string()
+{
+ return curdiv->no_space_mode ? "1" : "0";
+}
+
+void init_div_requests()
+{
+ init_request("box", box);
+ init_request("boxa", box_append);
+ init_request("bp", begin_page);
+ init_request("ch", change_trap);
+ init_request("da", divert_append);
+ init_request("di", divert);
+ init_request("dt", diversion_trap);
+ init_request("fl", flush_output);
+ init_request("mk", mark);
+ init_request("ne", need_space);
+ init_request("ns", no_space);
+ init_request("os", output_saved_vertical_space);
+ init_request("pl", page_length);
+ init_request("pn", page_number);
+ init_request("po", page_offset);
+ init_request("ptr", print_traps);
+ init_request("rs", restore_spacing);
+ init_request("rt", return_request);
+ init_request("sp", space_request);
+ init_request("sv", save_vertical_space);
+ init_request("vpt", vertical_position_traps);
+ init_request("wh", when_request);
+ register_dictionary.define(".a",
+ new readonly_register(&last_post_line_extra_space));
+ register_dictionary.define(".d", new vertical_position_reg);
+ register_dictionary.define(".h", new high_water_mark_reg);
+ register_dictionary.define(".ne",
+ new constant_vunits_reg(&needed_space));
+ register_dictionary.define(".ns", new no_space_mode_reg);
+ register_dictionary.define(".o", new page_offset_reg);
+ register_dictionary.define(".p", new page_length_reg);
+ register_dictionary.define(".pe", new page_ejecting_reg);
+ register_dictionary.define(".pn", new next_page_number_reg);
+ register_dictionary.define(".t", new distance_to_next_trap_reg);
+ register_dictionary.define(".trunc",
+ new constant_vunits_reg(&truncated_space));
+ register_dictionary.define(".vpt",
+ new readonly_register(&vertical_position_traps_flag));
+ register_dictionary.define(".z", new diversion_name_reg);
+ register_dictionary.define("dl", new variable_reg(&dl_reg_contents));
+ register_dictionary.define("dn", new variable_reg(&dn_reg_contents));
+ register_dictionary.define("nl", new nl_reg);
+ register_dictionary.define("%", new page_number_reg);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/div.h b/src/roff/troff/div.h
new file mode 100644
index 0000000..15c7807
--- /dev/null
+++ b/src/roff/troff/div.h
@@ -0,0 +1,175 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+void do_divert(int append, int boxing);
+void end_diversions();
+void page_offset();
+
+class diversion {
+ friend void do_divert(int append, int boxing);
+ friend void end_diversions();
+ diversion *prev;
+ node *saved_line;
+ hunits saved_width_total;
+ int saved_space_total;
+ hunits saved_saved_indent;
+ hunits saved_target_text_length;
+ int saved_prev_line_interrupted;
+protected:
+ symbol nm;
+ vunits vertical_position;
+ vunits high_water_mark;
+public:
+ int any_chars_added;
+ int no_space_mode;
+ int needs_push;
+ int saved_seen_break;
+ int saved_seen_space;
+ int saved_seen_eol;
+ int saved_suppress_next_eol;
+ state_set modified_tag;
+ vunits marked_place;
+ diversion(symbol s = NULL_SYMBOL);
+ virtual ~diversion();
+ virtual void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width) = 0;
+ virtual void transparent_output(unsigned char) = 0;
+ virtual void transparent_output(node *) = 0;
+ virtual void space(vunits distance, int forced = 0) = 0;
+#ifdef COLUMN
+ virtual void vjustify(symbol) = 0;
+#endif /* COLUMN */
+ vunits get_vertical_position() { return vertical_position; }
+ vunits get_high_water_mark() { return high_water_mark; }
+ virtual vunits distance_to_next_trap() = 0;
+ void need(vunits);
+ const char *get_diversion_name() { return nm.contents(); }
+ virtual void set_diversion_trap(symbol, vunits) = 0;
+ virtual void clear_diversion_trap() = 0;
+ virtual void copy_file(const char *filename) = 0;
+ virtual int is_diversion() = 0;
+};
+
+class macro;
+
+class macro_diversion : public diversion {
+ macro *mac;
+ hunits max_width;
+ symbol diversion_trap;
+ vunits diversion_trap_pos;
+public:
+ macro_diversion(symbol, int);
+ ~macro_diversion();
+ void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width);
+ void transparent_output(unsigned char);
+ void transparent_output(node *);
+ void space(vunits distance, int forced = 0);
+#ifdef COLUMN
+ void vjustify(symbol);
+#endif /* COLUMN */
+ vunits distance_to_next_trap();
+ void set_diversion_trap(symbol, vunits);
+ void clear_diversion_trap();
+ void copy_file(const char *filename);
+ int is_diversion() { return 1; }
+};
+
+struct trap {
+ trap *next;
+ vunits position;
+ symbol nm;
+ trap(symbol, vunits, trap *);
+};
+
+class output_file;
+
+class top_level_diversion : public diversion {
+ int page_number;
+ int page_count;
+ int last_page_count;
+ vunits page_length;
+ hunits prev_page_offset;
+ hunits page_offset;
+ trap *page_trap_list;
+ trap *find_next_trap(vunits *);
+ int have_next_page_number;
+ int next_page_number;
+ int ejecting_page; // Is the current page being ejected?
+public:
+ int before_first_page;
+ top_level_diversion();
+ void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width);
+ void transparent_output(unsigned char);
+ void transparent_output(node *);
+ void space(vunits distance, int forced = 0);
+#ifdef COLUMN
+ void vjustify(symbol);
+#endif /* COLUMN */
+ hunits get_page_offset() { return page_offset; }
+ vunits get_page_length() { return page_length; }
+ vunits distance_to_next_trap();
+ void add_trap(symbol nm, vunits pos);
+ void change_trap(symbol nm, vunits pos);
+ void remove_trap(symbol);
+ void remove_trap_at(vunits pos);
+ void print_traps();
+ int get_page_count() { return page_count; }
+ int get_page_number() { return page_number; }
+ int get_next_page_number();
+ void set_page_number(int n) { page_number = n; }
+ int begin_page(vunits = V0);
+ void set_next_page_number(int);
+ void set_page_length(vunits);
+ void copy_file(const char *filename);
+ int get_ejecting() { return ejecting_page; }
+ void set_ejecting() { ejecting_page = 1; }
+ friend void page_offset();
+ void set_diversion_trap(symbol, vunits);
+ void clear_diversion_trap();
+ void set_last_page() { last_page_count = page_count; }
+ int is_diversion() { return 0; }
+};
+
+extern top_level_diversion *topdiv;
+extern diversion *curdiv;
+
+extern bool is_exit_underway;
+extern bool is_eoi_macro_finished;
+extern bool seen_last_page_ejector;
+extern int last_page_number;
+
+void spring_trap(symbol); // implemented by input.c
+extern int trap_sprung_flag;
+void postpone_traps();
+int unpostpone_traps();
+
+void push_page_ejector();
+void continue_page_eject();
+void handle_first_page_transition();
+void blank_line();
+void begin_page();
+
+extern void cleanup_and_exit(int);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/env.cpp b/src/roff/troff/env.cpp
new file mode 100644
index 0000000..9f00284
--- /dev/null
+++ b/src/roff/troff/env.cpp
@@ -0,0 +1,4138 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "troff.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "font.h"
+#include "charinfo.h"
+#include "macropath.h"
+#include "input.h"
+#include <math.h>
+
+symbol default_family("T");
+
+enum { ADJUST_LEFT = 0,
+ ADJUST_BOTH = 1,
+ ADJUST_CENTER = 3,
+ ADJUST_RIGHT = 5,
+ ADJUST_MAX = 5
+};
+
+enum {
+ // Not all combinations are valid; see hyphenate_request() below.
+ HYPHEN_NONE = 0,
+ HYPHEN_DEFAULT = 1,
+ HYPHEN_NOT_LAST_LINE = 2,
+ HYPHEN_NOT_LAST_CHARS = 4,
+ HYPHEN_NOT_FIRST_CHARS = 8,
+ HYPHEN_LAST_CHAR = 16,
+ HYPHEN_FIRST_CHAR = 32,
+ HYPHEN_MAX = 63
+};
+
+struct env_list_node {
+ environment *env;
+ env_list_node *next;
+ env_list_node(environment *e, env_list_node *p) : env(e), next(p) {}
+};
+
+env_list_node *env_stack;
+dictionary env_dictionary(10);
+environment *curenv;
+static int next_line_number = 0;
+extern int suppress_push;
+extern statem *get_diversion_state();
+
+charinfo *field_delimiter_char;
+charinfo *padding_indicator_char;
+
+int translate_space_to_dummy = 0;
+
+class pending_output_line {
+ node *nd;
+ int no_fill;
+ int was_centered;
+ vunits vs;
+ vunits post_vs;
+ hunits width;
+#ifdef WIDOW_CONTROL
+ int last_line; // Is it the last line of the paragraph?
+#endif /* WIDOW_CONTROL */
+public:
+ pending_output_line *next;
+
+ pending_output_line(node *, int, vunits, vunits, hunits, int,
+ pending_output_line * = 0);
+ ~pending_output_line();
+ int output();
+
+#ifdef WIDOW_CONTROL
+ friend void environment::mark_last_line();
+ friend void environment::output(node *, int, vunits, vunits, hunits, int);
+#endif /* WIDOW_CONTROL */
+};
+
+pending_output_line::pending_output_line(node *n, int nf, vunits v, vunits pv,
+ hunits w, int ce,
+ pending_output_line *p)
+: nd(n), no_fill(nf), was_centered(ce), vs(v), post_vs(pv), width(w),
+#ifdef WIDOW_CONTROL
+ last_line(0),
+#endif /* WIDOW_CONTROL */
+ next(p)
+{
+}
+
+pending_output_line::~pending_output_line()
+{
+ delete_node_list(nd);
+}
+
+int pending_output_line::output()
+{
+ if (trap_sprung_flag)
+ return 0;
+#ifdef WIDOW_CONTROL
+ if (next && next->last_line && !no_fill) {
+ curdiv->need(vs + post_vs + vunits(vresolution));
+ if (trap_sprung_flag) {
+ next->last_line = 0; // Try to avoid infinite loops.
+ return 0;
+ }
+ }
+#endif
+ curenv->construct_format_state(nd, was_centered, !no_fill);
+ curdiv->output(nd, no_fill, vs, post_vs, width);
+ nd = 0;
+ return 1;
+}
+
+void environment::output(node *nd, int no_fill_flag,
+ vunits vs, vunits post_vs,
+ hunits width, int was_centered)
+{
+#ifdef WIDOW_CONTROL
+ while (pending_lines) {
+ if (widow_control && !pending_lines->no_fill && !pending_lines->next)
+ break;
+ if (!pending_lines->output())
+ break;
+ pending_output_line *tem = pending_lines;
+ pending_lines = pending_lines->next;
+ delete tem;
+ }
+#else /* WIDOW_CONTROL */
+ output_pending_lines();
+#endif /* WIDOW_CONTROL */
+ if (!trap_sprung_flag && !pending_lines
+#ifdef WIDOW_CONTROL
+ && (!widow_control || no_fill_flag)
+#endif /* WIDOW_CONTROL */
+ ) {
+ curenv->construct_format_state(nd, was_centered, !no_fill_flag);
+ curdiv->output(nd, no_fill_flag, vs, post_vs, width);
+ } else {
+ pending_output_line **p;
+ for (p = &pending_lines; *p; p = &(*p)->next)
+ ;
+ *p = new pending_output_line(nd, no_fill_flag, vs, post_vs, width,
+ was_centered);
+ }
+}
+
+// a line from .tl goes at the head of the queue
+
+void environment::output_title(node *nd, int no_fill_flag,
+ vunits vs, vunits post_vs,
+ hunits width)
+{
+ if (!trap_sprung_flag)
+ curdiv->output(nd, no_fill_flag, vs, post_vs, width);
+ else
+ pending_lines = new pending_output_line(nd, no_fill_flag, vs, post_vs,
+ width, 0, pending_lines);
+}
+
+void environment::output_pending_lines()
+{
+ while (pending_lines && pending_lines->output()) {
+ pending_output_line *tem = pending_lines;
+ pending_lines = pending_lines->next;
+ delete tem;
+ }
+}
+
+#ifdef WIDOW_CONTROL
+
+void environment::mark_last_line()
+{
+ if (!widow_control || !pending_lines)
+ return;
+ pending_output_line *p;
+ for (p = pending_lines; p->next; p = p->next)
+ ;
+ if (!p->no_fill)
+ p->last_line = 1;
+}
+
+void widow_control_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->widow_control = n != 0;
+ else
+ curenv->widow_control = 1;
+ skip_line();
+}
+
+#endif /* WIDOW_CONTROL */
+
+/* font_size functions */
+
+size_range *font_size::size_table = 0;
+int font_size::nranges = 0;
+
+extern "C" {
+
+int compare_ranges(const void *p1, const void *p2)
+{
+ return ((size_range *)p1)->min - ((size_range *)p2)->min;
+}
+
+}
+
+void font_size::init_size_table(int *sizes)
+{
+ nranges = 0;
+ while (sizes[nranges*2] != 0)
+ nranges++;
+ assert(nranges > 0);
+ size_table = new size_range[nranges];
+ for (int i = 0; i < nranges; i++) {
+ size_table[i].min = sizes[i*2];
+ size_table[i].max = sizes[i*2 + 1];
+ }
+ qsort(size_table, nranges, sizeof(size_range), compare_ranges);
+}
+
+font_size::font_size(int sp)
+{
+ for (int i = 0; i < nranges; i++) {
+ if (sp < size_table[i].min) {
+ if (i > 0 && size_table[i].min - sp >= sp - size_table[i - 1].max)
+ p = size_table[i - 1].max;
+ else
+ p = size_table[i].min;
+ return;
+ }
+ if (sp <= size_table[i].max) {
+ p = sp;
+ return;
+ }
+ }
+ p = size_table[nranges - 1].max;
+}
+
+int font_size::to_units()
+{
+ return scale(p, units_per_inch, sizescale*72);
+}
+
+// we can't do this in a static constructor because various dictionaries
+// have to get initialized first
+
+static symbol default_environment_name("0");
+
+void init_environments()
+{
+ curenv = new environment(default_environment_name);
+ (void)env_dictionary.lookup(default_environment_name, curenv);
+}
+
+void tab_character()
+{
+ curenv->tab_char = get_optional_char();
+ skip_line();
+}
+
+void leader_character()
+{
+ curenv->leader_char = get_optional_char();
+ skip_line();
+}
+
+void environment::add_char(charinfo *ci)
+{
+ int s;
+ node *gc_np = 0;
+ if (interrupted)
+ ;
+ // don't allow fields in dummy environments
+ else if (ci == field_delimiter_char && !dummy) {
+ if (current_field)
+ wrap_up_field();
+ else
+ start_field();
+ }
+ else if (current_field && ci == padding_indicator_char)
+ add_padding();
+ else if (current_tab) {
+ if (tab_contents == 0)
+ tab_contents = new line_start_node;
+ if (ci != hyphen_indicator_char)
+ tab_contents = tab_contents->add_char(ci, this, &tab_width, &s, &gc_np);
+ else
+ tab_contents = tab_contents->add_discretionary_hyphen();
+ }
+ else {
+ if (line == 0)
+ start_line();
+#if 0
+ fprintf(stderr, "current line is\n");
+ line->debug_node_list();
+#endif
+ if (ci != hyphen_indicator_char)
+ line = line->add_char(ci, this, &width_total, &space_total, &gc_np);
+ else
+ line = line->add_discretionary_hyphen();
+ }
+#if 0
+ fprintf(stderr, "now after we have added character the line is\n");
+ line->debug_node_list();
+#endif
+ if ((!suppress_push) && gc_np) {
+ if (gc_np && (gc_np->state == 0)) {
+ gc_np->state = construct_state(0);
+ gc_np->push_state = get_diversion_state();
+ }
+ else if (line && (line->state == 0)) {
+ line->state = construct_state(0);
+ line->push_state = get_diversion_state();
+ }
+ }
+#if 0
+ fprintf(stderr, "now we have possibly added the state the line is\n");
+ line->debug_node_list();
+#endif
+}
+
+node *environment::make_char_node(charinfo *ci)
+{
+ return make_node(ci, this);
+}
+
+void environment::add_node(node *n)
+{
+ if (n == 0)
+ return;
+ if (!suppress_push) {
+ if (n->is_special && n->state == NULL)
+ n->state = construct_state(0);
+ n->push_state = get_diversion_state();
+ }
+
+ if (current_tab || current_field)
+ n->freeze_space();
+ if (interrupted) {
+ delete n;
+ }
+ else if (current_tab) {
+ n->next = tab_contents;
+ tab_contents = n;
+ tab_width += n->width();
+ }
+ else {
+ if (line == 0) {
+ if (discarding && n->discardable()) {
+ // XXX possibly: input_line_start -= n->width();
+ delete n;
+ return;
+ }
+ start_line();
+ }
+ width_total += n->width();
+ space_total += n->nspaces();
+ n->next = line;
+ line = n;
+ construct_new_line_state(line);
+ }
+}
+
+void environment::add_hyphen_indicator()
+{
+ if (current_tab || interrupted || current_field
+ || hyphen_indicator_char != 0)
+ return;
+ if (line == 0)
+ start_line();
+ line = line->add_discretionary_hyphen();
+}
+
+int environment::get_hyphenation_flags()
+{
+ return hyphenation_flags;
+}
+
+int environment::get_hyphen_line_max()
+{
+ return hyphen_line_max;
+}
+
+int environment::get_hyphen_line_count()
+{
+ return hyphen_line_count;
+}
+
+int environment::get_center_lines()
+{
+ return center_lines;
+}
+
+int environment::get_right_justify_lines()
+{
+ return right_justify_lines;
+}
+
+int environment::get_no_number_count()
+{
+ return no_number_count;
+}
+
+void environment::add_italic_correction()
+{
+ if (current_tab) {
+ if (tab_contents)
+ tab_contents = tab_contents->add_italic_correction(&tab_width);
+ }
+ else if (line)
+ line = line->add_italic_correction(&width_total);
+}
+
+void environment::space_newline()
+{
+ assert(!current_tab && !current_field);
+ if (interrupted)
+ return;
+ hunits x = H0;
+ hunits sw = env_space_width(this);
+ hunits ssw = env_sentence_space_width(this);
+ if (!translate_space_to_dummy) {
+ x = sw;
+ if (node_list_ends_sentence(line) == 1)
+ x += ssw;
+ }
+ width_list *w = new width_list(sw, ssw);
+ if (node_list_ends_sentence(line) == 1)
+ w->next = new width_list(sw, ssw);
+ if (line != 0 && line->merge_space(x, sw, ssw)) {
+ width_total += x;
+ return;
+ }
+ add_node(new word_space_node(x, get_fill_color(), w));
+ possibly_break_line(0, spread_flag);
+ spread_flag = 0;
+}
+
+void environment::space()
+{
+ space(env_space_width(this), env_sentence_space_width(this));
+}
+
+void environment::space(hunits space_width, hunits sentence_space_width)
+{
+ if (interrupted)
+ return;
+ if (current_field && padding_indicator_char == 0) {
+ add_padding();
+ return;
+ }
+ hunits x = translate_space_to_dummy ? H0 : space_width;
+ node *p = current_tab ? tab_contents : line;
+ hunits *tp = current_tab ? &tab_width : &width_total;
+ if (p && p->nspaces() == 1 && p->width() == x
+ && node_list_ends_sentence(p->next) == 1) {
+ hunits xx = translate_space_to_dummy ? H0 : sentence_space_width;
+ if (p->merge_space(xx, space_width, sentence_space_width)) {
+ *tp += xx;
+ return;
+ }
+ }
+ if (p && p->merge_space(x, space_width, sentence_space_width)) {
+ *tp += x;
+ return;
+ }
+ add_node(new word_space_node(x,
+ get_fill_color(),
+ new width_list(space_width,
+ sentence_space_width)));
+ possibly_break_line(0, spread_flag);
+ spread_flag = 0;
+}
+
+static node *do_underline_special(bool do_underline_spaces)
+{
+ macro m;
+ m.append_str("x u ");
+ m.append(do_underline_spaces ? '1' : '0');
+ return new special_node(m, 1);
+}
+
+bool environment::set_font(symbol nm)
+{
+ if (interrupted) {
+ warning(WARN_FONT, "ignoring font selection on interrupted line");
+ return true; // "no operation" is successful
+ }
+ if (nm == symbol("P") || nm.is_empty()) {
+ if (family->make_definite(prev_fontno) < 0)
+ return false;
+ int tem = fontno;
+ fontno = prev_fontno;
+ prev_fontno = tem;
+ }
+ else {
+ prev_fontno = fontno;
+ int n = symbol_fontno(nm);
+ if (n < 0) {
+ n = next_available_font_position();
+ if (!mount_font(n, nm))
+ return false;
+ }
+ if (family->make_definite(n) < 0)
+ return false;
+ fontno = n;
+ }
+ if (underline_spaces && fontno != prev_fontno) {
+ if (fontno == get_underline_fontno())
+ add_node(do_underline_special(true));
+ if (prev_fontno == get_underline_fontno())
+ add_node(do_underline_special(false));
+ }
+ return true;
+}
+
+bool environment::set_font(int n)
+{
+ if (interrupted)
+ return false;
+ if (is_good_fontno(n)) {
+ prev_fontno = fontno;
+ fontno = n;
+ }
+ else {
+ warning(WARN_FONT, "no font mounted at position %1", n);
+ return false;
+ }
+ return true;
+}
+
+void environment::set_family(symbol fam)
+{
+ if (interrupted)
+ return;
+ if (fam.is_null() || fam.is_empty()) {
+ int previous_mounting_position = prev_family->make_definite(fontno);
+ assert(previous_mounting_position >= 0);
+ if (previous_mounting_position < 0)
+ return;
+ font_family *tem = family;
+ family = prev_family;
+ prev_family = tem;
+ }
+ else {
+ font_family *f = lookup_family(fam);
+ // If the family isn't already in the dictionary, looking it up will
+ // create an entry for it. That doesn't mean that it will be
+ // resolvable to a real font when combined with a style name.
+ assert((f != 0 /* nullptr */) &&
+ (0 != "font family dictionary lookup"));
+ if (0 /* nullptr */ == f)
+ return;
+ if (f->make_definite(fontno) < 0) {
+ error("no font family named '%1' exists", fam.contents());
+ return;
+ }
+ prev_family = family;
+ family = f;
+ }
+}
+
+void environment::set_size(int n)
+{
+ if (interrupted)
+ return;
+ if (n == 0) {
+ font_size temp = prev_size;
+ prev_size = size;
+ size = temp;
+ int temp2 = prev_requested_size;
+ prev_requested_size = requested_size;
+ requested_size = temp2;
+ }
+ else {
+ prev_size = size;
+ size = font_size(n);
+ prev_requested_size = requested_size;
+ requested_size = n;
+ }
+}
+
+void environment::set_char_height(int n)
+{
+ if (interrupted)
+ return;
+ if (n == requested_size || n <= 0)
+ char_height = 0;
+ else
+ char_height = n;
+}
+
+void environment::set_char_slant(int n)
+{
+ if (interrupted)
+ return;
+ char_slant = n;
+}
+
+color *environment::get_prev_glyph_color()
+{
+ return prev_glyph_color;
+}
+
+color *environment::get_glyph_color()
+{
+ return glyph_color;
+}
+
+color *environment::get_prev_fill_color()
+{
+ return prev_fill_color;
+}
+
+color *environment::get_fill_color()
+{
+ return fill_color;
+}
+
+void environment::set_glyph_color(color *c)
+{
+ if (interrupted)
+ return;
+ curenv->prev_glyph_color = curenv->glyph_color;
+ curenv->glyph_color = c;
+}
+
+void environment::set_fill_color(color *c)
+{
+ if (interrupted)
+ return;
+ curenv->prev_fill_color = curenv->fill_color;
+ curenv->fill_color = c;
+}
+
+environment::environment(symbol nm)
+: dummy(0),
+ prev_line_length((units_per_inch*13)/2),
+ line_length((units_per_inch*13)/2),
+ prev_title_length((units_per_inch*13)/2),
+ title_length((units_per_inch*13)/2),
+ prev_size(sizescale*10),
+ size(sizescale*10),
+ requested_size(sizescale*10),
+ prev_requested_size(sizescale*10),
+ char_height(0),
+ char_slant(0),
+ space_size(12),
+ sentence_space_size(12),
+ adjust_mode(ADJUST_BOTH),
+ fill(1),
+ interrupted(0),
+ prev_line_interrupted(0),
+ center_lines(0),
+ right_justify_lines(0),
+ prev_vertical_spacing(points_to_units(12)),
+ vertical_spacing(points_to_units(12)),
+ prev_post_vertical_spacing(0),
+ post_vertical_spacing(0),
+ prev_line_spacing(1),
+ line_spacing(1),
+ prev_indent(0),
+ indent(0),
+ temporary_indent(0),
+ have_temporary_indent(0),
+ underline_lines(0),
+ underline_spaces(0),
+ input_trap_count(0),
+ continued_input_trap(0),
+ line(0),
+ prev_text_length(0),
+ width_total(0),
+ space_total(0),
+ input_line_start(0),
+ line_tabs(0),
+ current_tab(TAB_NONE),
+ leader_node(0),
+ tab_char(0),
+ leader_char(charset_table['.']),
+ current_field(0),
+ discarding(0),
+ spread_flag(0),
+ margin_character_flags(0),
+ margin_character_node(0),
+ margin_character_distance(points_to_units(10)),
+ numbering_nodes(0),
+ number_text_separation(1),
+ line_number_indent(0),
+ line_number_multiple(1),
+ no_number_count(0),
+ hyphenation_flags(1),
+ hyphen_line_count(0),
+ hyphen_line_max(-1),
+ hyphenation_space(H0),
+ hyphenation_margin(H0),
+ composite(0),
+ pending_lines(0),
+#ifdef WIDOW_CONTROL
+ widow_control(0),
+#endif /* WIDOW_CONTROL */
+ glyph_color(&default_color),
+ prev_glyph_color(&default_color),
+ fill_color(&default_color),
+ prev_fill_color(&default_color),
+ seen_space(0),
+ seen_eol(0),
+ suppress_next_eol(0),
+ seen_break(0),
+ tabs(units_per_inch/2, TAB_LEFT),
+ name(nm),
+ control_char('.'),
+ no_break_control_char('\''),
+ hyphen_indicator_char(0)
+{
+ prev_family = family = lookup_family(default_family);
+ prev_fontno = fontno = 1;
+ if (!is_good_fontno(1))
+ fatal("font mounted at position 1 is not valid");
+ if (family->make_definite(1) < 0)
+ fatal("invalid default font family '%1'",
+ default_family.contents());
+ prev_fontno = fontno;
+}
+
+environment::environment(const environment *e)
+: dummy(1),
+ prev_line_length(e->prev_line_length),
+ line_length(e->line_length),
+ prev_title_length(e->prev_title_length),
+ title_length(e->title_length),
+ prev_size(e->prev_size),
+ size(e->size),
+ requested_size(e->requested_size),
+ prev_requested_size(e->prev_requested_size),
+ char_height(e->char_height),
+ char_slant(e->char_slant),
+ prev_fontno(e->prev_fontno),
+ fontno(e->fontno),
+ prev_family(e->prev_family),
+ family(e->family),
+ space_size(e->space_size),
+ sentence_space_size(e->sentence_space_size),
+ adjust_mode(e->adjust_mode),
+ fill(e->fill),
+ interrupted(0),
+ prev_line_interrupted(0),
+ center_lines(0),
+ right_justify_lines(0),
+ prev_vertical_spacing(e->prev_vertical_spacing),
+ vertical_spacing(e->vertical_spacing),
+ prev_post_vertical_spacing(e->prev_post_vertical_spacing),
+ post_vertical_spacing(e->post_vertical_spacing),
+ prev_line_spacing(e->prev_line_spacing),
+ line_spacing(e->line_spacing),
+ prev_indent(e->prev_indent),
+ indent(e->indent),
+ temporary_indent(0),
+ have_temporary_indent(0),
+ underline_lines(0),
+ underline_spaces(0),
+ input_trap_count(0),
+ continued_input_trap(0),
+ line(0),
+ prev_text_length(e->prev_text_length),
+ width_total(0),
+ space_total(0),
+ input_line_start(0),
+ line_tabs(e->line_tabs),
+ current_tab(TAB_NONE),
+ leader_node(0),
+ tab_char(e->tab_char),
+ leader_char(e->leader_char),
+ current_field(0),
+ discarding(0),
+ spread_flag(0),
+ margin_character_flags(e->margin_character_flags),
+ margin_character_node(e->margin_character_node),
+ margin_character_distance(e->margin_character_distance),
+ numbering_nodes(0),
+ number_text_separation(e->number_text_separation),
+ line_number_indent(e->line_number_indent),
+ line_number_multiple(e->line_number_multiple),
+ no_number_count(e->no_number_count),
+ hyphenation_flags(e->hyphenation_flags),
+ hyphen_line_count(0),
+ hyphen_line_max(e->hyphen_line_max),
+ hyphenation_space(e->hyphenation_space),
+ hyphenation_margin(e->hyphenation_margin),
+ composite(0),
+ pending_lines(0),
+#ifdef WIDOW_CONTROL
+ widow_control(e->widow_control),
+#endif /* WIDOW_CONTROL */
+ glyph_color(e->glyph_color),
+ prev_glyph_color(e->prev_glyph_color),
+ fill_color(e->fill_color),
+ prev_fill_color(e->prev_fill_color),
+ seen_space(e->seen_space),
+ seen_eol(e->seen_eol),
+ suppress_next_eol(e->suppress_next_eol),
+ seen_break(e->seen_break),
+ tabs(e->tabs),
+ name(e->name), // so that, e.g., '.if "\n[.ev]"0"' works
+ control_char(e->control_char),
+ no_break_control_char(e->no_break_control_char),
+ hyphen_indicator_char(e->hyphen_indicator_char)
+{
+}
+
+void environment::copy(const environment *e)
+{
+ prev_line_length = e->prev_line_length;
+ line_length = e->line_length;
+ prev_title_length = e->prev_title_length;
+ title_length = e->title_length;
+ prev_size = e->prev_size;
+ size = e->size;
+ prev_requested_size = e->prev_requested_size;
+ requested_size = e->requested_size;
+ char_height = e->char_height;
+ char_slant = e->char_slant;
+ space_size = e->space_size;
+ sentence_space_size = e->sentence_space_size;
+ adjust_mode = e->adjust_mode;
+ fill = e->fill;
+ interrupted = 0;
+ prev_line_interrupted = 0;
+ center_lines = 0;
+ right_justify_lines = 0;
+ prev_vertical_spacing = e->prev_vertical_spacing;
+ vertical_spacing = e->vertical_spacing;
+ prev_post_vertical_spacing = e->prev_post_vertical_spacing,
+ post_vertical_spacing = e->post_vertical_spacing,
+ prev_line_spacing = e->prev_line_spacing;
+ line_spacing = e->line_spacing;
+ prev_indent = e->prev_indent;
+ indent = e->indent;
+ have_temporary_indent = 0;
+ temporary_indent = 0;
+ underline_lines = 0;
+ underline_spaces = 0;
+ input_trap_count = 0;
+ continued_input_trap = 0;
+ prev_text_length = e->prev_text_length;
+ width_total = 0;
+ space_total = 0;
+ input_line_start = 0;
+ control_char = e->control_char;
+ no_break_control_char = e->no_break_control_char;
+ hyphen_indicator_char = e->hyphen_indicator_char;
+ spread_flag = 0;
+ line = 0;
+ pending_lines = 0;
+ discarding = 0;
+ tabs = e->tabs;
+ line_tabs = e->line_tabs;
+ current_tab = TAB_NONE;
+ current_field = 0;
+ margin_character_flags = e->margin_character_flags;
+ if (e->margin_character_node)
+ margin_character_node = e->margin_character_node->copy();
+ margin_character_distance = e->margin_character_distance;
+ numbering_nodes = 0;
+ number_text_separation = e->number_text_separation;
+ line_number_multiple = e->line_number_multiple;
+ line_number_indent = e->line_number_indent;
+ no_number_count = e->no_number_count;
+ tab_char = e->tab_char;
+ leader_char = e->leader_char;
+ hyphenation_flags = e->hyphenation_flags;
+ fontno = e->fontno;
+ prev_fontno = e->prev_fontno;
+ dummy = e->dummy;
+ family = e->family;
+ prev_family = e->prev_family;
+ leader_node = 0;
+#ifdef WIDOW_CONTROL
+ widow_control = e->widow_control;
+#endif /* WIDOW_CONTROL */
+ hyphen_line_max = e->hyphen_line_max;
+ hyphen_line_count = 0;
+ hyphenation_space = e->hyphenation_space;
+ hyphenation_margin = e->hyphenation_margin;
+ composite = 0;
+ glyph_color= e->glyph_color;
+ prev_glyph_color = e->prev_glyph_color;
+ fill_color = e->fill_color;
+ prev_fill_color = e->prev_fill_color;
+}
+
+environment::~environment()
+{
+ delete leader_node;
+ delete_node_list(line);
+ delete_node_list(numbering_nodes);
+}
+
+hunits environment::get_input_line_position()
+{
+ hunits n;
+ if (line == 0)
+ n = -input_line_start;
+ else
+ n = width_total - input_line_start;
+ if (current_tab)
+ n += tab_width;
+ return n;
+}
+
+void environment::set_input_line_position(hunits n)
+{
+ input_line_start = line == 0 ? -n : width_total - n;
+ if (current_tab)
+ input_line_start += tab_width;
+}
+
+hunits environment::get_line_length()
+{
+ return line_length;
+}
+
+hunits environment::get_saved_line_length()
+{
+ if (line)
+ return target_text_length + saved_indent;
+ else
+ return line_length;
+}
+
+vunits environment::get_vertical_spacing()
+{
+ return vertical_spacing;
+}
+
+vunits environment::get_post_vertical_spacing()
+{
+ return post_vertical_spacing;
+}
+
+int environment::get_line_spacing()
+{
+ return line_spacing;
+}
+
+vunits environment::total_post_vertical_spacing()
+{
+ vunits tem(post_vertical_spacing);
+ if (line_spacing > 1)
+ tem += (line_spacing - 1)*vertical_spacing;
+ return tem;
+}
+
+int environment::get_bold()
+{
+ return get_bold_fontno(fontno);
+}
+
+hunits environment::get_digit_width()
+{
+ return env_digit_width(this);
+}
+
+int environment::get_adjust_mode()
+{
+ return adjust_mode;
+}
+
+int environment::get_fill()
+{
+ return fill;
+}
+
+hunits environment::get_indent()
+{
+ return indent;
+}
+
+hunits environment::get_saved_indent()
+{
+ if (line)
+ return saved_indent;
+ else if (have_temporary_indent)
+ return temporary_indent;
+ else
+ return indent;
+}
+
+hunits environment::get_temporary_indent()
+{
+ return temporary_indent;
+}
+
+hunits environment::get_title_length()
+{
+ return title_length;
+}
+
+node *environment::get_prev_char()
+{
+ for (node *n = current_tab ? tab_contents : line; n; n = n->next) {
+ node *last = n->last_char_node();
+ if (last)
+ return last;
+ }
+ return 0;
+}
+
+hunits environment::get_prev_char_width()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return H0;
+ return last->width();
+}
+
+hunits environment::get_prev_char_skew()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return H0;
+ return last->skew();
+}
+
+vunits environment::get_prev_char_height()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return V0;
+ vunits min, max;
+ last->vertical_extent(&min, &max);
+ return -min;
+}
+
+vunits environment::get_prev_char_depth()
+{
+ node *last = get_prev_char();
+ if (!last)
+ return V0;
+ vunits min, max;
+ last->vertical_extent(&min, &max);
+ return max;
+}
+
+hunits environment::get_text_length()
+{
+ hunits n = line == 0 ? H0 : width_total;
+ if (current_tab)
+ n += tab_width;
+ return n;
+}
+
+hunits environment::get_prev_text_length()
+{
+ return prev_text_length;
+}
+
+
+static int sb_reg_contents = 0;
+static int st_reg_contents = 0;
+static int ct_reg_contents = 0;
+static int rsb_reg_contents = 0;
+static int rst_reg_contents = 0;
+static int skw_reg_contents = 0;
+static int ssc_reg_contents = 0;
+
+void environment::width_registers()
+{
+ // this is used to implement \w; it sets the st, sb, ct registers
+ vunits min = 0, max = 0, cur = 0;
+ int character_type = 0;
+ ssc_reg_contents = line ? line->subscript_correction().to_units() : 0;
+ skw_reg_contents = line ? line->skew().to_units() : 0;
+ line = reverse_node_list(line);
+ vunits real_min = V0;
+ vunits real_max = V0;
+ vunits v1, v2;
+ for (node *tem = line; tem; tem = tem->next) {
+ tem->vertical_extent(&v1, &v2);
+ v1 += cur;
+ if (v1 < real_min)
+ real_min = v1;
+ v2 += cur;
+ if (v2 > real_max)
+ real_max = v2;
+ if ((cur += tem->vertical_width()) < min)
+ min = cur;
+ else if (cur > max)
+ max = cur;
+ character_type |= tem->character_type();
+ }
+ line = reverse_node_list(line);
+ st_reg_contents = -min.to_units();
+ sb_reg_contents = -max.to_units();
+ rst_reg_contents = -real_min.to_units();
+ rsb_reg_contents = -real_max.to_units();
+ ct_reg_contents = character_type;
+}
+
+node *environment::extract_output_line()
+{
+ if (current_tab)
+ wrap_up_tab();
+ node *n = line;
+ line = 0;
+ return n;
+}
+
+/* environment related requests */
+
+void environment_switch()
+{
+ if (curenv->is_dummy()) {
+ error("cannot switch out of dummy environment");
+ }
+ else {
+ symbol nm = get_long_name();
+ if (nm.is_null()) {
+ if (env_stack == 0)
+ error("environment stack underflow");
+ else {
+ int seen_space = curenv->seen_space;
+ int seen_eol = curenv->seen_eol;
+ int suppress_next_eol = curenv->suppress_next_eol;
+ curenv = env_stack->env;
+ curenv->seen_space = seen_space;
+ curenv->seen_eol = seen_eol;
+ curenv->suppress_next_eol = suppress_next_eol;
+ env_list_node *tem = env_stack;
+ env_stack = env_stack->next;
+ delete tem;
+ }
+ }
+ else {
+ environment *e = (environment *)env_dictionary.lookup(nm);
+ if (!e) {
+ e = new environment(nm);
+ (void)env_dictionary.lookup(nm, e);
+ }
+ env_stack = new env_list_node(curenv, env_stack);
+ curenv = e;
+ }
+ }
+ skip_line();
+}
+
+void environment_copy()
+{
+ environment *e=0;
+ tok.skip();
+ symbol nm = get_long_name();
+ if (nm.is_null()) {
+ error("no environment specified to copy from");
+ }
+ else {
+ e = (environment *)env_dictionary.lookup(nm);
+ if (e)
+ curenv->copy(e);
+ else
+ error("cannot copy from nonexistent environment '%1'",
+ nm.contents());
+ }
+ skip_line();
+}
+
+void fill_color_change()
+{
+ symbol s = get_name();
+ if (s.is_null())
+ curenv->set_fill_color(curenv->get_prev_fill_color());
+ else
+ do_fill_color(s);
+ skip_line();
+}
+
+void glyph_color_change()
+{
+ symbol s = get_name();
+ if (s.is_null())
+ curenv->set_glyph_color(curenv->get_prev_glyph_color());
+ else
+ do_glyph_color(s);
+ skip_line();
+}
+
+static symbol P_symbol("P");
+
+void font_change()
+{
+ symbol s = get_name();
+ bool is_number = true;
+ if (s.is_null() || s == P_symbol) {
+ s = P_symbol;
+ is_number = false;
+ }
+ else {
+ for (const char *p = s.contents(); p != 0 && *p != 0; p++)
+ if (!csdigit(*p)) {
+ is_number = false;
+ break;
+ }
+ }
+ // environment::set_font warns if a bogus mounting position is
+ // requested. We must warn here if a bogus font name is selected.
+ if (is_number)
+ (void) curenv->set_font(atoi(s.contents()));
+ else
+ if (!curenv->set_font(s))
+ warning(WARN_FONT, "cannot select font '%1'", s.contents());
+ skip_line();
+}
+
+void family_change()
+{
+ symbol s = get_name();
+ curenv->set_family(s);
+ skip_line();
+}
+
+void point_size()
+{
+ int n;
+ if (has_arg() && get_number(&n, 'z', curenv->get_requested_point_size())) {
+ if (n <= 0)
+ n = 1;
+ curenv->set_size(n);
+ }
+ else
+ curenv->set_size(0);
+ skip_line();
+}
+
+void override_sizes()
+{
+ int n = 16;
+ int *sizes = new int[n];
+ int i = 0;
+ char *buf = read_string();
+ if (!buf)
+ return;
+ char *p = strtok(buf, " \t");
+ for (;;) {
+ if (!p)
+ break;
+ int lower, upper;
+ switch (sscanf(p, "%d-%d", &lower, &upper)) {
+ case 1:
+ upper = lower;
+ // fall through
+ case 2:
+ if (lower <= upper && lower >= 0)
+ break;
+ // fall through
+ default:
+ warning(WARN_RANGE, "bad size range '%1'", p);
+ return;
+ }
+ if (i + 2 > n) {
+ int *old_sizes = sizes;
+ sizes = new int[n*2];
+ memcpy(sizes, old_sizes, n*sizeof(int));
+ n *= 2;
+ delete[] old_sizes;
+ }
+ sizes[i++] = lower;
+ if (lower == 0)
+ break;
+ sizes[i++] = upper;
+ p = strtok(0, " \t");
+ }
+ font_size::init_size_table(sizes);
+}
+
+void space_size()
+{
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ warning(WARN_RANGE, "negative word space size ignored: '%1'", n);
+ else
+ curenv->space_size = n;
+ if (has_arg() && get_integer(&n))
+ if (n < 0)
+ warning(WARN_RANGE, "negative sentence space size ignored: "
+ "'%1'", n);
+ else
+ curenv->sentence_space_size = n;
+ else
+ curenv->sentence_space_size = curenv->space_size;
+ }
+ skip_line();
+}
+
+void fill()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->fill = 1;
+ tok.next();
+}
+
+void no_fill()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->fill = 0;
+ curenv->suppress_next_eol = 1;
+ tok.next();
+}
+
+void center()
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ else if (n < 0)
+ n = 0;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->right_justify_lines = 0;
+ curenv->center_lines = n;
+ curdiv->modified_tag.incl(MTSM_CE);
+ tok.next();
+}
+
+void right_justify()
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ else if (n < 0)
+ n = 0;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->center_lines = 0;
+ curenv->right_justify_lines = n;
+ curdiv->modified_tag.incl(MTSM_RJ);
+ tok.next();
+}
+
+void line_length()
+{
+ hunits temp;
+ hunits minimum_length = font::hor;
+ if (has_arg() && get_hunits(&temp, 'm', curenv->line_length)) {
+ if (temp < minimum_length) {
+ warning(WARN_RANGE, "invalid line length %1u rounded to device"
+ " horizontal motion quantum", temp.to_units());
+ temp = minimum_length;
+ }
+ }
+ else
+ temp = curenv->prev_line_length;
+ curenv->prev_line_length = curenv->line_length;
+ curenv->line_length = temp;
+ curdiv->modified_tag.incl(MTSM_LL);
+ skip_line();
+}
+
+void title_length()
+{
+ hunits temp;
+ hunits minimum_length = font::hor;
+ if (has_arg() && get_hunits(&temp, 'm', curenv->title_length)) {
+ if (temp < minimum_length) {
+ warning(WARN_RANGE, "invalid title length %1u rounded to device"
+ " horizontal motion quantum", temp.to_units());
+ temp = minimum_length;
+ }
+ }
+ else
+ temp = curenv->prev_title_length;
+ curenv->prev_title_length = curenv->title_length;
+ curenv->title_length = temp;
+ skip_line();
+}
+
+void vertical_spacing()
+{
+ vunits temp;
+ if (has_arg() && get_vunits(&temp, 'p', curenv->vertical_spacing)) {
+ if (temp < V0) {
+ warning(WARN_RANGE, "vertical spacing must not be negative");
+ temp = vresolution;
+ }
+ }
+ else
+ temp = curenv->prev_vertical_spacing;
+ curenv->prev_vertical_spacing = curenv->vertical_spacing;
+ curenv->vertical_spacing = temp;
+ skip_line();
+}
+
+void post_vertical_spacing()
+{
+ vunits temp;
+ if (has_arg() && get_vunits(&temp, 'p', curenv->post_vertical_spacing)) {
+ if (temp < V0) {
+ warning(WARN_RANGE,
+ "post vertical spacing must be greater than or equal to 0");
+ temp = V0;
+ }
+ }
+ else
+ temp = curenv->prev_post_vertical_spacing;
+ curenv->prev_post_vertical_spacing = curenv->post_vertical_spacing;
+ curenv->post_vertical_spacing = temp;
+ skip_line();
+}
+
+void line_spacing()
+{
+ int temp;
+ if (has_arg() && get_integer(&temp)) {
+ if (temp < 1) {
+ warning(WARN_RANGE, "value %1 out of range: interpreted as 1", temp);
+ temp = 1;
+ }
+ }
+ else
+ temp = curenv->prev_line_spacing;
+ curenv->prev_line_spacing = curenv->line_spacing;
+ curenv->line_spacing = temp;
+ skip_line();
+}
+
+void indent()
+{
+ hunits temp;
+ if (has_arg() && get_hunits(&temp, 'm', curenv->indent)) {
+ if (temp < H0) {
+ warning(WARN_RANGE, "indent cannot be negative");
+ temp = H0;
+ }
+ }
+ else
+ temp = curenv->prev_indent;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ curenv->have_temporary_indent = 0;
+ curenv->prev_indent = curenv->indent;
+ curenv->indent = temp;
+ curdiv->modified_tag.incl(MTSM_IN);
+ tok.next();
+}
+
+void temporary_indent()
+{
+ int err = 0;
+ hunits temp;
+ if (!get_hunits(&temp, 'm', curenv->get_indent()))
+ err = 1;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (temp < H0) {
+ warning(WARN_RANGE, "total indent cannot be negative");
+ temp = H0;
+ }
+ if (!err) {
+ curenv->temporary_indent = temp;
+ curenv->have_temporary_indent = 1;
+ curdiv->modified_tag.incl(MTSM_TI);
+ }
+ tok.next();
+}
+
+void do_underline(int underline_spaces)
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ if (n <= 0) {
+ if (curenv->underline_lines > 0) {
+ curenv->prev_fontno = curenv->fontno;
+ curenv->fontno = curenv->pre_underline_fontno;
+ if (underline_spaces) {
+ curenv->underline_spaces = 0;
+ curenv->add_node(do_underline_special(false));
+ }
+ }
+ curenv->underline_lines = 0;
+ }
+ else {
+ curenv->underline_lines = n;
+ curenv->pre_underline_fontno = curenv->fontno;
+ curenv->fontno = get_underline_fontno();
+ if (underline_spaces) {
+ curenv->underline_spaces = 1;
+ curenv->add_node(do_underline_special(true));
+ }
+ }
+ skip_line();
+}
+
+void continuous_underline()
+{
+ do_underline(1);
+}
+
+void underline()
+{
+ do_underline(0);
+}
+
+void control_char()
+{
+ curenv->control_char = '.';
+ if (has_arg()) {
+ if (tok.ch() == 0)
+ error("bad control character");
+ else
+ curenv->control_char = tok.ch();
+ }
+ skip_line();
+}
+
+void no_break_control_char()
+{
+ curenv->no_break_control_char = '\'';
+ if (has_arg()) {
+ if (tok.ch() == 0)
+ error("bad control character");
+ else
+ curenv->no_break_control_char = tok.ch();
+ }
+ skip_line();
+}
+
+void margin_character()
+{
+ while (tok.is_space())
+ tok.next();
+ charinfo *ci = tok.get_char();
+ if (ci) {
+ // Call tok.next() only after making the node so that
+ // .mc \s+9\(br\s0 works.
+ node *nd = curenv->make_char_node(ci);
+ tok.next();
+ if (nd) {
+ delete curenv->margin_character_node;
+ curenv->margin_character_node = nd;
+ curenv->margin_character_flags = MARGIN_CHARACTER_ON
+ | MARGIN_CHARACTER_NEXT;
+ hunits d;
+ if (has_arg() && get_hunits(&d, 'm'))
+ curenv->margin_character_distance = d;
+ }
+ }
+ else {
+ check_missing_character();
+ curenv->margin_character_flags &= ~MARGIN_CHARACTER_ON;
+ if (curenv->margin_character_flags == 0) {
+ delete curenv->margin_character_node;
+ curenv->margin_character_node = 0;
+ }
+ }
+ skip_line();
+}
+
+void number_lines()
+{
+ delete_node_list(curenv->numbering_nodes);
+ curenv->numbering_nodes = 0;
+ if (has_arg()) {
+ node *nd = 0;
+ for (int i = '9'; i >= '0'; i--) {
+ node *tem = make_node(charset_table[i], curenv);
+ if (!tem) {
+ skip_line();
+ return;
+ }
+ tem->next = nd;
+ nd = tem;
+ }
+ curenv->numbering_nodes = nd;
+ curenv->line_number_digit_width = env_digit_width(curenv);
+ int n;
+ if (!tok.usable_as_delimiter()) {
+ if (get_integer(&n, next_line_number)) {
+ next_line_number = n;
+ if (next_line_number < 0) {
+ warning(WARN_RANGE, "negative line number");
+ next_line_number = 0;
+ }
+ }
+ }
+ else
+ while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (has_arg()) {
+ if (!tok.usable_as_delimiter()) {
+ if (get_integer(&n)) {
+ if (n <= 0) {
+ warning(WARN_RANGE, "negative or zero line number multiple");
+ }
+ else
+ curenv->line_number_multiple = n;
+ }
+ }
+ else
+ while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (has_arg()) {
+ if (!tok.usable_as_delimiter()) {
+ if (get_integer(&n))
+ curenv->number_text_separation = n;
+ }
+ else
+ while (!tok.is_space() && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (has_arg() && !tok.usable_as_delimiter() && get_integer(&n))
+ curenv->line_number_indent = n;
+ }
+ }
+ }
+ skip_line();
+}
+
+void no_number()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->no_number_count = n > 0 ? n : 0;
+ else
+ curenv->no_number_count = 1;
+ skip_line();
+}
+
+void no_hyphenate()
+{
+ curenv->hyphenation_flags = 0;
+ skip_line();
+}
+
+void hyphenate_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n)) {
+ if (n < HYPHEN_NONE) {
+ warning(WARN_RANGE, "negative hyphenation flags ignored: %1", n);
+ } else if (n > HYPHEN_MAX) {
+ warning(WARN_RANGE, "unknown hyphenation flags ignored (maximum "
+ "%1): %2", HYPHEN_MAX, n);
+ } else if (((n & HYPHEN_DEFAULT) && (n & ~HYPHEN_DEFAULT))
+ || ((n & HYPHEN_FIRST_CHAR) && (n & HYPHEN_NOT_FIRST_CHARS))
+ || ((n & HYPHEN_LAST_CHAR) && (n & HYPHEN_NOT_LAST_CHARS)))
+ warning(WARN_SYNTAX, "contradictory hyphenation flags ignored: "
+ "%1", n);
+ else
+ curenv->hyphenation_flags = n;
+ }
+ else
+ curenv->hyphenation_flags = 1;
+ skip_line();
+}
+
+void hyphen_char()
+{
+ curenv->hyphen_indicator_char = get_optional_char();
+ skip_line();
+}
+
+void hyphen_line_max_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->hyphen_line_max = n;
+ else
+ curenv->hyphen_line_max = -1;
+ skip_line();
+}
+
+void environment::interrupt()
+{
+ if (!dummy) {
+ add_node(new transparent_dummy_node);
+ interrupted = 1;
+ }
+}
+
+void environment::newline()
+{
+ int was_centered = 0;
+ if (underline_lines > 0) {
+ if (--underline_lines == 0) {
+ prev_fontno = fontno;
+ fontno = pre_underline_fontno;
+ if (underline_spaces) {
+ underline_spaces = 0;
+ add_node(do_underline_special(false));
+ }
+ }
+ }
+ if (current_field)
+ wrap_up_field();
+ if (current_tab)
+ wrap_up_tab();
+ // strip trailing spaces
+ while (line != 0 && line->discardable()) {
+ width_total -= line->width();
+ space_total -= line->nspaces();
+ node *tem = line;
+ line = line->next;
+ delete tem;
+ }
+ node *to_be_output = 0;
+ hunits to_be_output_width;
+ prev_line_interrupted = 0;
+ if (dummy)
+ space_newline();
+ else if (interrupted) {
+ interrupted = 0;
+ // see environment::final_break
+ prev_line_interrupted = is_exit_underway ? 2 : 1;
+ }
+ else if (center_lines > 0) {
+ --center_lines;
+ hunits x = target_text_length - width_total;
+ if (x > H0)
+ saved_indent += x/2;
+ to_be_output = line;
+ was_centered = 1;
+ to_be_output_width = width_total;
+ line = 0;
+ }
+ else if (right_justify_lines > 0) {
+ --right_justify_lines;
+ hunits x = target_text_length - width_total;
+ if (x > H0)
+ saved_indent += x;
+ to_be_output = line;
+ to_be_output_width = width_total;
+ line = 0;
+ }
+ else if (fill)
+ space_newline();
+ else {
+ to_be_output = line;
+ to_be_output_width = width_total;
+ line = 0;
+ }
+ input_line_start = line == 0 ? H0 : width_total;
+ if (to_be_output) {
+ if (is_html && !fill) {
+ curdiv->modified_tag.incl(MTSM_EOL);
+ if (suppress_next_eol)
+ suppress_next_eol = 0;
+ else
+ seen_eol = 1;
+ }
+
+ output_line(to_be_output, to_be_output_width, was_centered);
+ hyphen_line_count = 0;
+ }
+ if (input_trap_count > 0) {
+ if (!(continued_input_trap && prev_line_interrupted))
+ if (--input_trap_count == 0)
+ spring_trap(input_trap);
+ }
+}
+
+void environment::output_line(node *n, hunits width, int was_centered)
+{
+ prev_text_length = width;
+ if (margin_character_flags) {
+ hunits d = line_length + margin_character_distance - saved_indent - width;
+ if (d > 0) {
+ n = new hmotion_node(d, get_fill_color(), n);
+ width += d;
+ }
+ margin_character_flags &= ~MARGIN_CHARACTER_NEXT;
+ node *tem;
+ if (!margin_character_flags) {
+ tem = margin_character_node;
+ margin_character_node = 0;
+ }
+ else
+ tem = margin_character_node->copy();
+ tem->next = n;
+ n = tem;
+ width += tem->width();
+ }
+ node *nn = 0;
+ while (n != 0) {
+ node *tem = n->next;
+ n->next = nn;
+ nn = n;
+ n = tem;
+ }
+ if (!saved_indent.is_zero())
+ nn = new hmotion_node(saved_indent, get_fill_color(), nn);
+ width += saved_indent;
+ if (no_number_count > 0)
+ --no_number_count;
+ else if (numbering_nodes) {
+ hunits w = (line_number_digit_width
+ *(3+line_number_indent+number_text_separation));
+ if (next_line_number % line_number_multiple != 0)
+ nn = new hmotion_node(w, get_fill_color(), nn);
+ else {
+ hunits x = w;
+ nn = new hmotion_node(number_text_separation * line_number_digit_width,
+ get_fill_color(), nn);
+ x -= number_text_separation*line_number_digit_width;
+ char buf[30];
+ sprintf(buf, "%3d", next_line_number);
+ for (char *p = strchr(buf, '\0') - 1; p >= buf && *p != ' '; --p) {
+ node *gn = numbering_nodes;
+ for (int count = *p - '0'; count > 0; count--)
+ gn = gn->next;
+ gn = gn->copy();
+ x -= gn->width();
+ gn->next = nn;
+ nn = gn;
+ }
+ nn = new hmotion_node(x, get_fill_color(), nn);
+ }
+ width += w;
+ ++next_line_number;
+ }
+ output(nn, !fill, vertical_spacing, total_post_vertical_spacing(), width,
+ was_centered);
+}
+
+void environment::start_line()
+{
+ assert(line == 0);
+ discarding = 0;
+ line = new line_start_node;
+ if (have_temporary_indent) {
+ saved_indent = temporary_indent;
+ have_temporary_indent = 0;
+ }
+ else
+ saved_indent = indent;
+ target_text_length = line_length - saved_indent;
+ width_total = H0;
+ space_total = 0;
+}
+
+hunits environment::get_hyphenation_space()
+{
+ return hyphenation_space;
+}
+
+void hyphenation_space_request()
+{
+ hunits n;
+ if (get_hunits(&n, 'm')) {
+ if (n < H0) {
+ warning(WARN_RANGE, "hyphenation space cannot be negative");
+ n = H0;
+ }
+ curenv->hyphenation_space = n;
+ }
+ skip_line();
+}
+
+hunits environment::get_hyphenation_margin()
+{
+ return hyphenation_margin;
+}
+
+void hyphenation_margin_request()
+{
+ hunits n;
+ if (get_hunits(&n, 'm')) {
+ if (n < H0) {
+ warning(WARN_RANGE, "hyphenation margin cannot be negative");
+ n = H0;
+ }
+ curenv->hyphenation_margin = n;
+ }
+ skip_line();
+}
+
+breakpoint *environment::choose_breakpoint()
+{
+ hunits x = width_total;
+ int s = space_total;
+ node *n = line;
+ breakpoint *best_bp = 0; // the best breakpoint so far
+ int best_bp_fits = 0;
+ while (n != 0) {
+ x -= n->width();
+ s -= n->nspaces();
+ breakpoint *bp = n->get_breakpoints(x, s);
+ while (bp != 0) {
+ if (bp->width <= target_text_length) {
+ if (!bp->hyphenated) {
+ breakpoint *tem = bp->next;
+ bp->next = 0;
+ while (tem != 0) {
+ breakpoint *tem1 = tem;
+ tem = tem->next;
+ delete tem1;
+ }
+ if (best_bp_fits
+ // Decide whether to use the hyphenated breakpoint.
+ && (hyphen_line_max < 0
+ // Only choose the hyphenated breakpoint if it would not
+ // exceed the maximum number of consecutive hyphenated
+ // lines.
+ || hyphen_line_count + 1 <= hyphen_line_max)
+ && !(adjust_mode == ADJUST_BOTH
+ // Don't choose the hyphenated breakpoint if the line
+ // can be justified by adding no more than
+ // hyphenation_space to any word space.
+ ? (bp->nspaces > 0
+ && (((target_text_length - bp->width
+ + (bp->nspaces - 1)*hresolution)/bp->nspaces)
+ <= hyphenation_space))
+ // Don't choose the hyphenated breakpoint if the line
+ // is no more than hyphenation_margin short.
+ : target_text_length - bp->width <= hyphenation_margin)) {
+ delete bp;
+ return best_bp;
+ }
+ if (best_bp)
+ delete best_bp;
+ return bp;
+ }
+ else {
+ if ((adjust_mode == ADJUST_BOTH
+ ? hyphenation_space == H0
+ : hyphenation_margin == H0)
+ && (hyphen_line_max < 0
+ || hyphen_line_count + 1 <= hyphen_line_max)) {
+ // No need to consider a non-hyphenated breakpoint.
+ if (best_bp)
+ delete best_bp;
+ breakpoint *tem = bp->next;
+ bp->next = 0;
+ while (tem != 0) {
+ breakpoint *tem1 = tem;
+ tem = tem->next;
+ delete tem1;
+ }
+ return bp;
+ }
+ // It fits but it's hyphenated.
+ if (!best_bp_fits) {
+ if (best_bp)
+ delete best_bp;
+ best_bp = bp;
+ bp = bp->next;
+ best_bp_fits = 1;
+ }
+ else {
+ breakpoint *tem = bp;
+ bp = bp->next;
+ delete tem;
+ }
+ }
+ }
+ else {
+ if (best_bp)
+ delete best_bp;
+ best_bp = bp;
+ bp = bp->next;
+ }
+ }
+ n = n->next;
+ }
+ if (best_bp) {
+ if (!best_bp_fits)
+ output_warning(WARN_BREAK, "cannot break line");
+ return best_bp;
+ }
+ return 0;
+}
+
+void environment::hyphenate_line(int start_here)
+{
+ assert(line != 0);
+ hyphenation_type prev_type = line->get_hyphenation_type();
+ node **startp;
+ if (start_here)
+ startp = &line;
+ else
+ for (startp = &line->next; *startp != 0; startp = &(*startp)->next) {
+ hyphenation_type this_type = (*startp)->get_hyphenation_type();
+ if (prev_type == HYPHEN_BOUNDARY && this_type == HYPHEN_MIDDLE)
+ break;
+ prev_type = this_type;
+ }
+ if (*startp == 0)
+ return;
+ node *tem = *startp;
+ do {
+ tem = tem->next;
+ } while (tem != 0 && tem->get_hyphenation_type() == HYPHEN_MIDDLE);
+ int inhibit = (tem != 0 && tem->get_hyphenation_type() == HYPHEN_INHIBIT);
+ node *end = tem;
+ hyphen_list *sl = 0;
+ tem = *startp;
+ node *forward = 0;
+ int i = 0;
+ while (tem != end) {
+ sl = tem->get_hyphen_list(sl, &i);
+ node *tem1 = tem;
+ tem = tem->next;
+ tem1->next = forward;
+ forward = tem1;
+ }
+ if (!inhibit) {
+ // this is for characters like hyphen and emdash
+ int prev_code = 0;
+ for (hyphen_list *h = sl; h; h = h->next) {
+ h->breakable = (prev_code != 0
+ && h->next != 0
+ && h->next->hyphenation_code != 0);
+ prev_code = h->hyphenation_code;
+ }
+ }
+ if (hyphenation_flags != 0
+ && !inhibit
+ // this may not be right if we have extra space on this line
+ && !((hyphenation_flags & HYPHEN_NOT_LAST_LINE)
+ && (curdiv->distance_to_next_trap()
+ <= vertical_spacing + total_post_vertical_spacing()))
+ && i >= (4
+ - (hyphenation_flags & HYPHEN_FIRST_CHAR ? 1 : 0)
+ - (hyphenation_flags & HYPHEN_LAST_CHAR ? 1 : 0)
+ + (hyphenation_flags & HYPHEN_NOT_FIRST_CHARS ? 1 : 0)
+ + (hyphenation_flags & HYPHEN_NOT_LAST_CHARS ? 1 : 0)))
+ hyphenate(sl, hyphenation_flags);
+ while (forward != 0) {
+ node *tem1 = forward;
+ forward = forward->next;
+ tem1->next = 0;
+ tem = tem1->add_self(tem, &sl);
+ }
+ *startp = tem;
+}
+
+static node *node_list_reverse(node *n)
+{
+ node *res = 0;
+ while (n) {
+ node *tem = n;
+ n = n->next;
+ tem->next = res;
+ res = tem;
+ }
+ return res;
+}
+
+static void distribute_space(node *n, int nspaces, hunits desired_space,
+ bool force_reverse_node_list = false)
+{
+ if (desired_space.is_zero() || nspaces == 0)
+ return;
+ // Positive desired space is the typical case. Negative desired space
+ // is possible if we have overrun an unbreakable line. But we should
+ // not get here if there are no adjustable space nodes to adjust.
+ assert(nspaces > 0);
+ // Space cannot always be distributed evenly among all of the space
+ // nodes in the node list: there are limits to device resolution. We
+ // add space until we run out, which might happen before the end of
+ // the line. To achieve uniform typographical grayness and avoid
+ // rivers, we switch the end from which space is initially distributed
+ // with each line requiring it, unless compelled to reverse it. The
+ // node list's natural ordering is in the direction of text flow, so
+ // we distribute space initially from the left, unlike AT&T troff.
+ static bool do_reverse_node_list = false;
+ if (force_reverse_node_list || do_reverse_node_list)
+ n = node_list_reverse(n);
+ if (!force_reverse_node_list && spread_limit >= 0
+ && desired_space.to_units() > 0) {
+ hunits em = curenv->get_size();
+ double Ems = (double)desired_space.to_units() / nspaces
+ / (em.is_zero() ? hresolution : em.to_units());
+ if (Ems > spread_limit)
+ output_warning(WARN_BREAK, "spreading %1m per space", Ems);
+ }
+ for (node *tem = n; tem; tem = tem->next)
+ tem->spread_space(&nspaces, &desired_space);
+ if (force_reverse_node_list || do_reverse_node_list)
+ (void)node_list_reverse(n);
+ if (!force_reverse_node_list)
+ do_reverse_node_list = !do_reverse_node_list;
+}
+
+void environment::possibly_break_line(int start_here, int forced)
+{
+ int was_centered = center_lines > 0;
+ if (!fill || current_tab || current_field || dummy)
+ return;
+ while (line != 0
+ && (forced
+ // When a macro follows a paragraph in fill mode, the
+ // current line should not be empty.
+ || (width_total - line->width()) > target_text_length)) {
+ hyphenate_line(start_here);
+ breakpoint *bp = choose_breakpoint();
+ if (bp == 0)
+ // we'll find one eventually
+ return;
+ node *pre, *post;
+ node **ndp = &line;
+ while (*ndp != bp->nd)
+ ndp = &(*ndp)->next;
+ bp->nd->split(bp->index, &pre, &post);
+ *ndp = post;
+ hunits extra_space_width = H0;
+ switch(adjust_mode) {
+ case ADJUST_BOTH:
+ if (bp->nspaces != 0)
+ extra_space_width = target_text_length - bp->width;
+ else if (bp->width > 0 && target_text_length > 0
+ && target_text_length > bp->width)
+ output_warning(WARN_BREAK, "cannot adjust line");
+ break;
+ case ADJUST_CENTER:
+ saved_indent += (target_text_length - bp->width)/2;
+ was_centered = 1;
+ break;
+ case ADJUST_RIGHT:
+ saved_indent += target_text_length - bp->width;
+ break;
+ }
+ distribute_space(pre, bp->nspaces, extra_space_width);
+ hunits output_width = bp->width + extra_space_width;
+ // This should become an assert() when we can get reliable width
+ // data from CJK glyphs. See Savannah #44018.
+ if (output_width <= 0) {
+ double output_width_in_ems = output_width.to_units();
+ output_warning(WARN_BREAK, "line has non-positive width %1m",
+ output_width_in_ems);
+ return;
+ }
+ input_line_start -= output_width;
+ if (bp->hyphenated)
+ hyphen_line_count++;
+ else
+ hyphen_line_count = 0;
+ delete bp;
+ space_total = 0;
+ width_total = 0;
+ node *first_non_discardable = 0;
+ node *tem;
+ for (tem = line; tem != 0; tem = tem->next)
+ if (!tem->discardable())
+ first_non_discardable = tem;
+ node *to_be_discarded;
+ if (first_non_discardable) {
+ to_be_discarded = first_non_discardable->next;
+ first_non_discardable->next = 0;
+ for (tem = line; tem != 0; tem = tem->next) {
+ width_total += tem->width();
+ space_total += tem->nspaces();
+ }
+ discarding = 0;
+ }
+ else {
+ discarding = 1;
+ to_be_discarded = line;
+ line = 0;
+ }
+ // Do output_line() here so that line will be 0 iff the
+ // the environment will be empty.
+ output_line(pre, output_width, was_centered);
+ while (to_be_discarded != 0) {
+ tem = to_be_discarded;
+ to_be_discarded = to_be_discarded->next;
+ input_line_start -= tem->width();
+ delete tem;
+ }
+ if (line != 0) {
+ if (have_temporary_indent) {
+ saved_indent = temporary_indent;
+ have_temporary_indent = 0;
+ }
+ else
+ saved_indent = indent;
+ target_text_length = line_length - saved_indent;
+ }
+ }
+}
+
+/*
+Do the break at the end of input after the end macro (if any).
+
+Unix troff behaves as follows: if the last line is
+
+foo bar\c
+
+it will output foo on the current page, and bar on the next page;
+if the last line is
+
+foo\c
+
+or
+
+foo bar
+
+everything will be output on the current page. This behaviour must be
+considered a bug.
+
+The problem is that some macro packages rely on this. For example,
+the ATK macros have an end macro that emits \c if it needs to print a
+table of contents but doesn't do a 'bp in the end macro; instead the
+'bp is done in the bottom of page trap. This works with Unix troff,
+provided that the current environment is not empty at the end of the
+input file.
+
+The following will make macro packages that do that sort of thing work
+even if the current environment is empty at the end of the input file.
+If the last input line used \c and this line occurred in the end macro,
+then we'll force everything out on the current page, but we'll make
+sure that the environment isn't empty so that we won't exit at the
+bottom of this page.
+*/
+
+void environment::final_break()
+{
+ if (prev_line_interrupted == 2) {
+ do_break();
+ add_node(new transparent_dummy_node);
+ }
+ else
+ do_break();
+}
+
+node *environment::make_tag(const char *nm, int i)
+{
+ if (is_html) {
+ /*
+ * need to emit tag for post-grohtml
+ * but we check to see whether we can emit specials
+ */
+ if (curdiv == topdiv && topdiv->before_first_page)
+ topdiv->begin_page();
+
+ macro m;
+ m.append_str("devtag:");
+ for (const char *p = nm; *p; p++)
+ if (!is_invalid_input_char((unsigned char)*p))
+ m.append(*p);
+ m.append(' ');
+ m.append_int(i);
+ return new special_node(m);
+ }
+ return 0;
+}
+
+void environment::dump_troff_state()
+{
+#define SPACES " "
+ fprintf(stderr, SPACES "register 'in' = %d\n", curenv->indent.to_units());
+ if (curenv->have_temporary_indent)
+ fprintf(stderr, SPACES "register 'ti' = %d\n",
+ curenv->temporary_indent.to_units());
+ fprintf(stderr, SPACES "centered lines 'ce' = %d\n", curenv->center_lines);
+ fprintf(stderr, SPACES "register 'll' = %d\n",
+ curenv->line_length.to_units());
+ fprintf(stderr, SPACES "fill 'fi=1/nf=0' = %d\n", curenv->fill);
+ fprintf(stderr, SPACES "page offset 'po' = %d\n",
+ topdiv->get_page_offset().to_units());
+ fprintf(stderr, SPACES "seen_break = %d\n", curenv->seen_break);
+ fprintf(stderr, SPACES "seen_space = %d\n", curenv->seen_space);
+ fflush(stderr);
+#undef SPACES
+}
+
+statem *environment::construct_state(int only_eol)
+{
+ if (is_html) {
+ statem *s = new statem();
+ if (!only_eol) {
+ s->add_tag(MTSM_IN, indent);
+ s->add_tag(MTSM_LL, line_length);
+ s->add_tag(MTSM_PO, topdiv->get_page_offset().to_units());
+ s->add_tag(MTSM_RJ, right_justify_lines);
+ if (have_temporary_indent)
+ s->add_tag(MTSM_TI, temporary_indent);
+ s->add_tag_ta();
+ if (seen_break)
+ s->add_tag(MTSM_BR);
+ if (seen_space != 0)
+ s->add_tag(MTSM_SP, seen_space);
+ seen_break = 0;
+ seen_space = 0;
+ }
+ if (seen_eol) {
+ s->add_tag(MTSM_EOL);
+ s->add_tag(MTSM_CE, center_lines);
+ }
+ seen_eol = 0;
+ return s;
+ }
+ else
+ return NULL;
+}
+
+void environment::construct_format_state(node *n, int was_centered,
+ int filling)
+{
+ if (is_html) {
+ // find first glyph node which has a state.
+ while (n != 0 && n->state == 0)
+ n = n->next;
+ if (n == 0 || (n->state == 0))
+ return;
+ if (seen_space != 0)
+ n->state->add_tag(MTSM_SP, seen_space);
+ if (seen_eol && topdiv == curdiv)
+ n->state->add_tag(MTSM_EOL);
+ seen_space = 0;
+ seen_eol = 0;
+ if (was_centered)
+ n->state->add_tag(MTSM_CE, center_lines+1);
+ else
+ n->state->add_tag_if_unknown(MTSM_CE, 0);
+ n->state->add_tag_if_unknown(MTSM_FI, filling);
+ n = n->next;
+ while (n != 0) {
+ if (n->state != 0) {
+ n->state->sub_tag_ce();
+ n->state->add_tag_if_unknown(MTSM_FI, filling);
+ }
+ n = n->next;
+ }
+ }
+}
+
+void environment::construct_new_line_state(node *n)
+{
+ if (is_html) {
+ // find first glyph node which has a state.
+ while (n != 0 && n->state == 0)
+ n = n->next;
+ if (n == 0 || n->state == 0)
+ return;
+ if (seen_space != 0)
+ n->state->add_tag(MTSM_SP, seen_space);
+ if (seen_eol && topdiv == curdiv)
+ n->state->add_tag(MTSM_EOL);
+ seen_space = 0;
+ seen_eol = 0;
+ }
+}
+
+extern int global_diverted_space;
+
+void environment::do_break(int do_spread)
+{
+ int was_centered = 0;
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ topdiv->begin_page();
+ return;
+ }
+ if (current_tab)
+ wrap_up_tab();
+ if (line) {
+ // this is so that hyphenation works
+ if (line->nspaces() == 0) {
+ line = new space_node(H0, get_fill_color(), line);
+ space_total++;
+ }
+ possibly_break_line(0, do_spread);
+ }
+ while (line != 0 && line->discardable()) {
+ width_total -= line->width();
+ space_total -= line->nspaces();
+ node *tem = line;
+ line = line->next;
+ delete tem;
+ }
+ discarding = 0;
+ input_line_start = H0;
+ if (line != 0) {
+ if (fill) {
+ switch (adjust_mode) {
+ case ADJUST_CENTER:
+ saved_indent += (target_text_length - width_total)/2;
+ was_centered = 1;
+ break;
+ case ADJUST_RIGHT:
+ saved_indent += target_text_length - width_total;
+ break;
+ }
+ }
+ node *tem = line;
+ line = 0;
+ output_line(tem, width_total, was_centered);
+ hyphen_line_count = 0;
+ }
+ prev_line_interrupted = 0;
+#ifdef WIDOW_CONTROL
+ mark_last_line();
+ output_pending_lines();
+#endif /* WIDOW_CONTROL */
+ if (!global_diverted_space) {
+ curdiv->modified_tag.incl(MTSM_BR);
+ seen_break = 1;
+ }
+}
+
+int environment::is_empty()
+{
+ return !current_tab && line == 0 && pending_lines == 0;
+}
+
+void do_break_request(int spread)
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break(spread);
+ tok.next();
+}
+
+void break_request()
+{
+ do_break_request(0);
+}
+
+void break_spread_request()
+{
+ do_break_request(1);
+}
+
+void title()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_title();
+ return;
+ }
+ node *part[3];
+ hunits part_width[3];
+ part[0] = part[1] = part[2] = 0;
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ read_title_parts(part, part_width);
+ curenv = oldenv;
+ curenv->size = env.size;
+ curenv->prev_size = env.prev_size;
+ curenv->requested_size = env.requested_size;
+ curenv->prev_requested_size = env.prev_requested_size;
+ curenv->char_height = env.char_height;
+ curenv->char_slant = env.char_slant;
+ curenv->fontno = env.fontno;
+ curenv->prev_fontno = env.prev_fontno;
+ curenv->glyph_color = env.glyph_color;
+ curenv->prev_glyph_color = env.prev_glyph_color;
+ curenv->fill_color = env.fill_color;
+ curenv->prev_fill_color = env.prev_fill_color;
+ node *n = 0;
+ node *p = part[2];
+ while (p != 0) {
+ node *tem = p;
+ p = p->next;
+ tem->next = n;
+ n = tem;
+ }
+ hunits length_title(curenv->title_length);
+ hunits f = length_title - part_width[1];
+ hunits f2 = f/2;
+ n = new hmotion_node(f2 - part_width[2], curenv->get_fill_color(), n);
+ p = part[1];
+ while (p != 0) {
+ node *tem = p;
+ p = p->next;
+ tem->next = n;
+ n = tem;
+ }
+ n = new hmotion_node(f - f2 - part_width[0], curenv->get_fill_color(), n);
+ p = part[0];
+ while (p != 0) {
+ node *tem = p;
+ p = p->next;
+ tem->next = n;
+ n = tem;
+ }
+ curenv->output_title(n, !curenv->fill, curenv->vertical_spacing,
+ curenv->total_post_vertical_spacing(), length_title);
+ curenv->hyphen_line_count = 0;
+ tok.next();
+}
+
+void adjust()
+{
+ curenv->adjust_mode |= 1;
+ if (has_arg()) {
+ switch (tok.ch()) {
+ case 'l':
+ curenv->adjust_mode = ADJUST_LEFT;
+ break;
+ case 'r':
+ curenv->adjust_mode = ADJUST_RIGHT;
+ break;
+ case 'c':
+ curenv->adjust_mode = ADJUST_CENTER;
+ break;
+ case 'b':
+ case 'n':
+ curenv->adjust_mode = ADJUST_BOTH;
+ break;
+ default:
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ warning(WARN_RANGE, "negative adjustment mode");
+ else if (n > ADJUST_MAX)
+ warning(WARN_RANGE, "out-of-range adjustment mode ignored: "
+ "%1", n);
+ else
+ curenv->adjust_mode = n;
+ }
+ }
+ }
+ skip_line();
+}
+
+void no_adjust()
+{
+ curenv->adjust_mode &= ~1;
+ skip_line();
+}
+
+void do_input_trap(int continued)
+{
+ curenv->input_trap_count = 0;
+ if (continued)
+ curenv->continued_input_trap = 1;
+ else
+ curenv->continued_input_trap = 0;
+ int n;
+ if (has_arg() && get_integer(&n)) {
+ if (n <= 0)
+ warning(WARN_RANGE,
+ "number of lines for input trap must be greater than zero");
+ else {
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ curenv->input_trap_count = n;
+ curenv->input_trap = s;
+ }
+ }
+ }
+ skip_line();
+}
+
+void input_trap()
+{
+ do_input_trap(0);
+}
+
+void input_trap_continued()
+{
+ do_input_trap(1);
+}
+
+/* tabs */
+
+// must not be R or C or L or a legitimate part of a number expression
+const char TAB_REPEAT_CHAR = 'T';
+
+struct tab {
+ tab *next;
+ hunits pos;
+ tab_type type;
+ tab(hunits, tab_type);
+ enum { BLOCK = 1024 };
+};
+
+tab::tab(hunits x, tab_type t) : next(0), pos(x), type(t)
+{
+}
+
+tab_stops::tab_stops(hunits distance, tab_type type)
+: initial_list(0)
+{
+ repeated_list = new tab(distance, type);
+}
+
+tab_stops::~tab_stops()
+{
+ clear();
+}
+
+tab_type tab_stops::distance_to_next_tab(hunits curpos, hunits *distance)
+{
+ hunits nextpos;
+
+ return distance_to_next_tab(curpos, distance, &nextpos);
+}
+
+tab_type tab_stops::distance_to_next_tab(hunits curpos,
+ hunits *distance,
+ hunits *nextpos)
+{
+ hunits lastpos = 0;
+ tab *tem;
+ for (tem = initial_list; tem && tem->pos <= curpos; tem = tem->next)
+ lastpos = tem->pos;
+ if (tem) {
+ *distance = tem->pos - curpos;
+ *nextpos = tem->pos;
+ return tem->type;
+ }
+ if (repeated_list == 0)
+ return TAB_NONE;
+ hunits base = lastpos;
+ for (;;) {
+ for (tem = repeated_list; tem && tem->pos + base <= curpos;
+ tem = tem->next)
+ lastpos = tem->pos;
+ if (tem) {
+ *distance = tem->pos + base - curpos;
+ *nextpos = tem->pos + base;
+ return tem->type;
+ }
+ if (lastpos < 0)
+ lastpos = 0;
+ base += lastpos;
+ }
+ return TAB_NONE;
+}
+
+const char *tab_stops::to_string()
+{
+ static char *buf = 0;
+ static int buf_size = 0;
+ // figure out a maximum on the amount of space we can need
+ int count = 0;
+ tab *p;
+ for (p = initial_list; p; p = p->next)
+ ++count;
+ for (p = repeated_list; p; p = p->next)
+ ++count;
+ // (10 for digits + 1 for u + 1 for 'C' or 'R') + 2 for ' &' + 1 for '\0'
+ int need = count*12 + 3;
+ if (buf == 0 || need > buf_size) {
+ if (buf)
+ delete[] buf;
+ buf_size = need;
+ buf = new char[buf_size];
+ }
+ char *ptr = buf;
+ for (p = initial_list; p; p = p->next) {
+ strcpy(ptr, i_to_a(p->pos.to_units()));
+ ptr = strchr(ptr, '\0');
+ *ptr++ = 'u';
+ *ptr = '\0';
+ switch (p->type) {
+ case TAB_LEFT:
+ break;
+ case TAB_RIGHT:
+ *ptr++ = 'R';
+ break;
+ case TAB_CENTER:
+ *ptr++ = 'C';
+ break;
+ case TAB_NONE:
+ default:
+ assert(0);
+ }
+ }
+ if (repeated_list)
+ *ptr++ = TAB_REPEAT_CHAR;
+ for (p = repeated_list; p; p = p->next) {
+ strcpy(ptr, i_to_a(p->pos.to_units()));
+ ptr = strchr(ptr, '\0');
+ *ptr++ = 'u';
+ *ptr = '\0';
+ switch (p->type) {
+ case TAB_LEFT:
+ break;
+ case TAB_RIGHT:
+ *ptr++ = 'R';
+ break;
+ case TAB_CENTER:
+ *ptr++ = 'C';
+ break;
+ case TAB_NONE:
+ default:
+ assert(0);
+ }
+ }
+ *ptr++ = '\0';
+ return buf;
+}
+
+tab_stops::tab_stops() : initial_list(0), repeated_list(0)
+{
+}
+
+tab_stops::tab_stops(const tab_stops &ts)
+: initial_list(0), repeated_list(0)
+{
+ tab **p = &initial_list;
+ tab *t = ts.initial_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+ p = &repeated_list;
+ t = ts.repeated_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+}
+
+void tab_stops::clear()
+{
+ while (initial_list) {
+ tab *tem = initial_list;
+ initial_list = initial_list->next;
+ delete tem;
+ }
+ while (repeated_list) {
+ tab *tem = repeated_list;
+ repeated_list = repeated_list->next;
+ delete tem;
+ }
+}
+
+void tab_stops::add_tab(hunits pos, tab_type type, int repeated)
+{
+ tab **p;
+ for (p = repeated ? &repeated_list : &initial_list; *p; p = &(*p)->next)
+ ;
+ *p = new tab(pos, type);
+}
+
+
+void tab_stops::operator=(const tab_stops &ts)
+{
+ clear();
+ tab **p = &initial_list;
+ tab *t = ts.initial_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+ p = &repeated_list;
+ t = ts.repeated_list;
+ while (t) {
+ *p = new tab(t->pos, t->type);
+ t = t->next;
+ p = &(*p)->next;
+ }
+}
+
+void set_tabs()
+{
+ hunits pos;
+ hunits prev_pos = 0;
+ bool is_first_stop = true;
+ bool is_repeating_stop = false;
+ tab_stops tabs;
+ while (has_arg()) {
+ if (tok.ch() == TAB_REPEAT_CHAR) {
+ tok.next();
+ is_repeating_stop = true;
+ prev_pos = 0;
+ }
+ if (!get_hunits(&pos, 'm', prev_pos))
+ break;
+ tab_type type = TAB_LEFT;
+ if (tok.ch() == 'C') {
+ tok.next();
+ type = TAB_CENTER;
+ }
+ else if (tok.ch() == 'R') {
+ tok.next();
+ type = TAB_RIGHT;
+ }
+ else if (tok.ch() == 'L') {
+ tok.next();
+ }
+ if (pos <= prev_pos && ((!is_first_stop) || is_repeating_stop))
+ warning(WARN_RANGE,
+ "positions of tab stops must be strictly increasing");
+ else {
+ tabs.add_tab(pos, type, is_repeating_stop);
+ prev_pos = pos;
+ is_first_stop = false;
+ }
+ }
+ curenv->tabs = tabs;
+ curdiv->modified_tag.incl(MTSM_TA);
+ skip_line();
+}
+
+const char *environment::get_tabs()
+{
+ return tabs.to_string();
+}
+
+tab_type environment::distance_to_next_tab(hunits *distance)
+{
+ return line_tabs
+ ? curenv->tabs.distance_to_next_tab(get_text_length(), distance)
+ : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance);
+}
+
+tab_type environment::distance_to_next_tab(hunits *distance, hunits *leftpos)
+{
+ return line_tabs
+ ? curenv->tabs.distance_to_next_tab(get_text_length(), distance, leftpos)
+ : curenv->tabs.distance_to_next_tab(get_input_line_position(), distance,
+ leftpos);
+}
+
+void field_characters()
+{
+ field_delimiter_char = get_optional_char();
+ if (field_delimiter_char)
+ padding_indicator_char = get_optional_char();
+ else
+ padding_indicator_char = 0;
+ skip_line();
+}
+
+void line_tabs_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ curenv->line_tabs = n != 0;
+ else
+ curenv->line_tabs = 1;
+ skip_line();
+}
+
+int environment::get_line_tabs()
+{
+ return line_tabs;
+}
+
+void environment::wrap_up_tab()
+{
+ if (!current_tab)
+ return;
+ if (line == 0)
+ start_line();
+ hunits tab_amount;
+ switch (current_tab) {
+ case TAB_RIGHT:
+ tab_amount = tab_distance - tab_width;
+ line = make_tab_node(tab_amount, line);
+ break;
+ case TAB_CENTER:
+ tab_amount = tab_distance - tab_width/2;
+ line = make_tab_node(tab_amount, line);
+ break;
+ case TAB_NONE:
+ case TAB_LEFT:
+ default:
+ assert(0);
+ }
+ width_total += tab_amount;
+ width_total += tab_width;
+ if (current_field) {
+ if (tab_precedes_field) {
+ pre_field_width += tab_amount;
+ tab_precedes_field = 0;
+ }
+ field_distance -= tab_amount;
+ field_spaces += tab_field_spaces;
+ }
+ if (tab_contents != 0) {
+ node *tem;
+ for (tem = tab_contents; tem->next != 0; tem = tem->next)
+ ;
+ tem->next = line;
+ line = tab_contents;
+ }
+ tab_field_spaces = 0;
+ tab_contents = 0;
+ tab_width = H0;
+ tab_distance = H0;
+ current_tab = TAB_NONE;
+}
+
+node *environment::make_tab_node(hunits d, node *next)
+{
+ if (leader_node != 0 && d < 0) {
+ error("motion generated by leader cannot be negative");
+ delete leader_node;
+ leader_node = 0;
+ }
+ if (!leader_node)
+ return new hmotion_node(d, 1, 0, get_fill_color(), next);
+ node *n = new hline_node(d, leader_node, next);
+ leader_node = 0;
+ return n;
+}
+
+void environment::handle_tab(int is_leader)
+{
+ hunits d;
+ hunits absolute;
+ if (current_tab)
+ wrap_up_tab();
+ charinfo *ci = is_leader ? leader_char : tab_char;
+ delete leader_node;
+ leader_node = ci ? make_char_node(ci) : 0;
+ tab_type t = distance_to_next_tab(&d, &absolute);
+ switch (t) {
+ case TAB_NONE:
+ return;
+ case TAB_LEFT:
+ add_node(make_tag("tab L", absolute.to_units()));
+ add_node(make_tab_node(d));
+ return;
+ case TAB_RIGHT:
+ add_node(make_tag("tab R", absolute.to_units()));
+ break;
+ case TAB_CENTER:
+ add_node(make_tag("tab C", absolute.to_units()));
+ break;
+ default:
+ assert(0);
+ }
+ tab_width = 0;
+ tab_distance = d;
+ tab_contents = 0;
+ current_tab = t;
+ tab_field_spaces = 0;
+}
+
+void environment::start_field()
+{
+ assert(!current_field);
+ hunits d;
+ if (distance_to_next_tab(&d) != TAB_NONE) {
+ pre_field_width = get_text_length();
+ field_distance = d;
+ current_field = 1;
+ field_spaces = 0;
+ tab_field_spaces = 0;
+ for (node *p = line; p; p = p->next)
+ if (p->nspaces()) {
+ p->freeze_space();
+ space_total--;
+ }
+ tab_precedes_field = current_tab != TAB_NONE;
+ }
+ else
+ error("zero field width");
+}
+
+void environment::wrap_up_field()
+{
+ if (!current_tab && field_spaces == 0)
+ add_padding();
+ hunits padding = field_distance - (get_text_length() - pre_field_width);
+ if (current_tab && tab_field_spaces != 0) {
+ hunits tab_padding = scale(padding,
+ tab_field_spaces,
+ field_spaces + tab_field_spaces);
+ padding -= tab_padding;
+ distribute_space(tab_contents, tab_field_spaces, tab_padding,
+ true /* force reversal of node list */);
+ tab_field_spaces = 0;
+ tab_width += tab_padding;
+ }
+ if (field_spaces != 0) {
+ distribute_space(line, field_spaces, padding,
+ true /* force reversal of node list */);
+ width_total += padding;
+ if (current_tab) {
+ // the start of the tab has been moved to the right by padding, so
+ tab_distance -= padding;
+ if (tab_distance <= H0) {
+ // use the next tab stop instead
+ current_tab = tabs.distance_to_next_tab(get_input_line_position()
+ - tab_width,
+ &tab_distance);
+ if (current_tab == TAB_NONE || current_tab == TAB_LEFT) {
+ width_total += tab_width;
+ if (current_tab == TAB_LEFT) {
+ line = make_tab_node(tab_distance, line);
+ width_total += tab_distance;
+ current_tab = TAB_NONE;
+ }
+ if (tab_contents != 0) {
+ node *tem;
+ for (tem = tab_contents; tem->next != 0; tem = tem->next)
+ ;
+ tem->next = line;
+ line = tab_contents;
+ tab_contents = 0;
+ }
+ tab_width = H0;
+ tab_distance = H0;
+ }
+ }
+ }
+ }
+ current_field = 0;
+}
+
+void environment::add_padding()
+{
+ if (current_tab) {
+ tab_contents = new space_node(H0, get_fill_color(), tab_contents);
+ tab_field_spaces++;
+ }
+ else {
+ if (line == 0)
+ start_line();
+ line = new space_node(H0, get_fill_color(), line);
+ field_spaces++;
+ }
+}
+
+typedef int (environment::*INT_FUNCP)();
+typedef vunits (environment::*VUNITS_FUNCP)();
+typedef hunits (environment::*HUNITS_FUNCP)();
+typedef const char *(environment::*STRING_FUNCP)();
+
+class int_env_reg : public reg {
+ INT_FUNCP func;
+ public:
+ int_env_reg(INT_FUNCP);
+ const char *get_string();
+ bool get_value(units *val);
+};
+
+class vunits_env_reg : public reg {
+ VUNITS_FUNCP func;
+ public:
+ vunits_env_reg(VUNITS_FUNCP f);
+ const char *get_string();
+ bool get_value(units *val);
+};
+
+
+class hunits_env_reg : public reg {
+ HUNITS_FUNCP func;
+ public:
+ hunits_env_reg(HUNITS_FUNCP f);
+ const char *get_string();
+ bool get_value(units *val);
+};
+
+class string_env_reg : public reg {
+ STRING_FUNCP func;
+public:
+ string_env_reg(STRING_FUNCP);
+ const char *get_string();
+};
+
+int_env_reg::int_env_reg(INT_FUNCP f) : func(f)
+{
+}
+
+bool int_env_reg::get_value(units *val)
+{
+ *val = (curenv->*func)();
+ return true;
+}
+
+const char *int_env_reg::get_string()
+{
+ return i_to_a((curenv->*func)());
+}
+
+vunits_env_reg::vunits_env_reg(VUNITS_FUNCP f) : func(f)
+{
+}
+
+bool vunits_env_reg::get_value(units *val)
+{
+ *val = (curenv->*func)().to_units();
+ return true;
+}
+
+const char *vunits_env_reg::get_string()
+{
+ return i_to_a((curenv->*func)().to_units());
+}
+
+hunits_env_reg::hunits_env_reg(HUNITS_FUNCP f) : func(f)
+{
+}
+
+bool hunits_env_reg::get_value(units *val)
+{
+ *val = (curenv->*func)().to_units();
+ return true;
+}
+
+const char *hunits_env_reg::get_string()
+{
+ return i_to_a((curenv->*func)().to_units());
+}
+
+string_env_reg::string_env_reg(STRING_FUNCP f) : func(f)
+{
+}
+
+const char *string_env_reg::get_string()
+{
+ return (curenv->*func)();
+}
+
+class horizontal_place_reg : public general_reg {
+public:
+ horizontal_place_reg();
+ bool get_value(units *);
+ void set_value(units);
+};
+
+horizontal_place_reg::horizontal_place_reg()
+{
+}
+
+bool horizontal_place_reg::get_value(units *res)
+{
+ *res = curenv->get_input_line_position().to_units();
+ return true;
+}
+
+void horizontal_place_reg::set_value(units n)
+{
+ curenv->set_input_line_position(hunits(n));
+}
+
+int environment::get_zoom()
+{
+ return env_get_zoom(this);
+}
+
+int environment::get_numbering_nodes()
+{
+ return (curenv->numbering_nodes ? 1 : 0);
+}
+
+const char *environment::get_font_family_string()
+{
+ return family->nm.contents();
+}
+
+const char *environment::get_glyph_color_string()
+{
+ return glyph_color->nm.contents();
+}
+
+const char *environment::get_fill_color_string()
+{
+ return fill_color->nm.contents();
+}
+
+const char *environment::get_font_name_string()
+{
+ symbol f = get_font_name(fontno, this);
+ return f.contents();
+}
+
+const char *environment::get_style_name_string()
+{
+ symbol f = get_style_name(fontno);
+ return f.contents();
+}
+
+const char *environment::get_name_string()
+{
+ return name.contents();
+}
+
+// Convert a quantity in scaled points to ascii decimal fraction.
+
+const char *sptoa(int sp)
+{
+ assert(sp > 0);
+ assert(sizescale > 0);
+ if (sizescale == 1)
+ return i_to_a(sp);
+ if (sp % sizescale == 0)
+ return i_to_a(sp/sizescale);
+ // See if 1/sizescale is exactly representable as a decimal fraction,
+ // ie its only prime factors are 2 and 5.
+ int n = sizescale;
+ int power2 = 0;
+ while ((n & 1) == 0) {
+ n >>= 1;
+ power2++;
+ }
+ int power5 = 0;
+ while ((n % 5) == 0) {
+ n /= 5;
+ power5++;
+ }
+ if (n == 1) {
+ int decimal_point = power5 > power2 ? power5 : power2;
+ if (decimal_point <= 10) {
+ int factor = 1;
+ int t;
+ for (t = decimal_point - power2; --t >= 0;)
+ factor *= 2;
+ for (t = decimal_point - power5; --t >= 0;)
+ factor *= 5;
+ if (factor == 1 || sp <= INT_MAX/factor)
+ return if_to_a(sp*factor, decimal_point);
+ }
+ }
+ double s = double(sp)/double(sizescale);
+ double factor = 10.0;
+ double val = s;
+ int decimal_point = 0;
+ do {
+ double v = ceil(s*factor);
+ if (v > INT_MAX)
+ break;
+ val = v;
+ factor *= 10.0;
+ } while (++decimal_point < 10);
+ return if_to_a(int(val), decimal_point);
+}
+
+const char *environment::get_point_size_string()
+{
+ return sptoa(curenv->get_point_size());
+}
+
+const char *environment::get_requested_point_size_string()
+{
+ return sptoa(curenv->get_requested_point_size());
+}
+
+void environment::print_env()
+{
+ // at the time of calling .pev, those values are always zero or
+ // meaningless:
+ //
+ // char_height, char_slant,
+ // interrupted
+ // current_tab, tab_width, tab_distance
+ // current_field, field_distance, pre_field_width, field_spaces,
+ // tab_field_spaces, tab_precedes_field
+ // composite
+ //
+ errprint(" previous line length: %1u\n", prev_line_length.to_units());
+ errprint(" line length: %1u\n", line_length.to_units());
+ errprint(" previous title length: %1u\n", prev_title_length.to_units());
+ errprint(" title length: %1u\n", title_length.to_units());
+ errprint(" previous size: %1p (%2s)\n",
+ prev_size.to_points(), prev_size.to_scaled_points());
+ errprint(" size: %1p (%2s)\n",
+ size.to_points(), size.to_scaled_points());
+ errprint(" previous requested size: %1s\n", prev_requested_size);
+ errprint(" requested size: %1s\n", requested_size);
+ errprint(" previous font number: %1\n", prev_fontno);
+ errprint(" font number: %1\n", fontno);
+ errprint(" previous family: '%1'\n", prev_family->nm.contents());
+ errprint(" family: '%1'\n", family->nm.contents());
+ errprint(" space size: %1/36 em\n", space_size);
+ errprint(" sentence space size: %1/36 em\n", sentence_space_size);
+ errprint(" previous line interrupted: %1\n",
+ prev_line_interrupted ? "yes" : "no");
+ errprint(" fill mode: %1\n", fill ? "on" : "off");
+ errprint(" adjust mode: %1\n",
+ adjust_mode == ADJUST_LEFT
+ ? "left"
+ : adjust_mode == ADJUST_BOTH
+ ? "both"
+ : adjust_mode == ADJUST_CENTER
+ ? "center"
+ : "right");
+ if (center_lines)
+ errprint(" lines to center: %1\n", center_lines);
+ if (right_justify_lines)
+ errprint(" lines to right justify: %1\n", right_justify_lines);
+ errprint(" previous vertical spacing: %1u\n",
+ prev_vertical_spacing.to_units());
+ errprint(" vertical spacing: %1u\n", vertical_spacing.to_units());
+ errprint(" previous post-vertical spacing: %1u\n",
+ prev_post_vertical_spacing.to_units());
+ errprint(" post-vertical spacing: %1u\n",
+ post_vertical_spacing.to_units());
+ errprint(" previous line spacing: %1\n", prev_line_spacing);
+ errprint(" line spacing: %1\n", line_spacing);
+ errprint(" previous indentation: %1u\n", prev_indent.to_units());
+ errprint(" indentation: %1u\n", indent.to_units());
+ errprint(" temporary indentation: %1u\n", temporary_indent.to_units());
+ errprint(" have temporary indentation: %1\n",
+ have_temporary_indent ? "yes" : "no");
+ errprint(" currently used indentation: %1u\n", saved_indent.to_units());
+ errprint(" target text length: %1u\n", target_text_length.to_units());
+ if (underline_lines) {
+ errprint(" lines to underline: %1\n", underline_lines);
+ errprint(" font number before underlining: %1\n", pre_underline_fontno);
+ errprint(" underline spaces: %1\n", underline_spaces ? "yes" : "no");
+ }
+ if (input_trap.contents()) {
+ errprint(" input trap macro: '%1'\n", input_trap.contents());
+ errprint(" input trap line counter: %1\n", input_trap_count);
+ errprint(" continued input trap: %1\n",
+ continued_input_trap ? "yes" : "no");
+ }
+ errprint(" previous text length: %1u\n", prev_text_length.to_units());
+ errprint(" total width: %1u\n", width_total.to_units());
+ errprint(" total number of spaces: %1\n", space_total);
+ errprint(" input line start: %1u\n", input_line_start.to_units());
+ errprint(" line tabs: %1\n", line_tabs ? "yes" : "no");
+ errprint(" discarding: %1\n", discarding ? "yes" : "no");
+ errprint(" spread flag set: %1\n", spread_flag ? "yes" : "no"); // \p
+ if (margin_character_node) {
+ errprint(" margin character flags: %1\n",
+ margin_character_flags == MARGIN_CHARACTER_ON
+ ? "on"
+ : margin_character_flags == MARGIN_CHARACTER_NEXT
+ ? "next"
+ : margin_character_flags == (MARGIN_CHARACTER_ON
+ | MARGIN_CHARACTER_NEXT)
+ ? "on, next"
+ : "none");
+ errprint(" margin character distance: %1u\n",
+ margin_character_distance.to_units());
+ }
+ if (numbering_nodes) {
+ errprint(" line number digit width: %1u\n",
+ line_number_digit_width.to_units());
+ errprint(" separation between number and text: %1 digit spaces\n",
+ number_text_separation);
+ errprint(" line number indentation: %1 digit spaces\n",
+ line_number_indent);
+ errprint(" print line numbers every %1line%1\n",
+ line_number_multiple > 1 ? i_to_a(line_number_multiple) : "",
+ line_number_multiple > 1 ? "s" : "");
+ errprint(" lines not to enumerate: %1\n", no_number_count);
+ }
+ string hf = hyphenation_flags ? "on" : "off";
+ if (hyphenation_flags & HYPHEN_NOT_LAST_LINE)
+ hf += ", not last line";
+ if (hyphenation_flags & HYPHEN_LAST_CHAR)
+ hf += ", last char";
+ if (hyphenation_flags & HYPHEN_NOT_LAST_CHARS)
+ hf += ", not last two chars";
+ if (hyphenation_flags & HYPHEN_FIRST_CHAR)
+ hf += ", first char";
+ if (hyphenation_flags & HYPHEN_NOT_FIRST_CHARS)
+ hf += ", not first two chars";
+ hf += '\0';
+ errprint(" hyphenation_flags: %1\n", hf.contents());
+ errprint(" number of consecutive hyphenated lines: %1\n",
+ hyphen_line_count);
+ errprint(" maximum number of consecutive hyphenated lines: %1\n",
+ hyphen_line_max);
+ errprint(" hyphenation space: %1u\n", hyphenation_space.to_units());
+ errprint(" hyphenation margin: %1u\n", hyphenation_margin.to_units());
+#ifdef WIDOW_CONTROL
+ errprint(" widow control: %1\n", widow_control ? "yes" : "no");
+#endif /* WIDOW_CONTROL */
+}
+
+void print_env()
+{
+ errprint("Current Environment:\n");
+ curenv->print_env();
+ dictionary_iterator iter(env_dictionary);
+ symbol s;
+ environment *e;
+ while (iter.get(&s, (void **)&e)) {
+ assert(!s.is_null());
+ errprint("Environment %1:\n", s.contents());
+ if (e != curenv)
+ e->print_env();
+ else
+ errprint(" current\n");
+ }
+ fflush(stderr);
+ skip_line();
+}
+
+#define init_int_env_reg(name, func) \
+ register_dictionary.define(name, new int_env_reg(&environment::func))
+
+#define init_vunits_env_reg(name, func) \
+ register_dictionary.define(name, new vunits_env_reg(&environment::func))
+
+#define init_hunits_env_reg(name, func) \
+ register_dictionary.define(name, new hunits_env_reg(&environment::func))
+
+#define init_string_env_reg(name, func) \
+ register_dictionary.define(name, new string_env_reg(&environment::func))
+
+void init_env_requests()
+{
+ init_request("ad", adjust);
+ init_request("br", break_request);
+ init_request("brp", break_spread_request);
+ init_request("c2", no_break_control_char);
+ init_request("cc", control_char);
+ init_request("ce", center);
+ init_request("cu", continuous_underline);
+ init_request("ev", environment_switch);
+ init_request("evc", environment_copy);
+ init_request("fam", family_change);
+ init_request("fc", field_characters);
+ init_request("fi", fill);
+ init_request("fcolor", fill_color_change);
+ init_request("ft", font_change);
+ init_request("gcolor", glyph_color_change);
+ init_request("hc", hyphen_char);
+ init_request("hlm", hyphen_line_max_request);
+ init_request("hy", hyphenate_request);
+ init_request("hym", hyphenation_margin_request);
+ init_request("hys", hyphenation_space_request);
+ init_request("in", indent);
+ init_request("it", input_trap);
+ init_request("itc", input_trap_continued);
+ init_request("lc", leader_character);
+ init_request("linetabs", line_tabs_request);
+ init_request("ll", line_length);
+ init_request("ls", line_spacing);
+ init_request("lt", title_length);
+ init_request("mc", margin_character);
+ init_request("na", no_adjust);
+ init_request("nf", no_fill);
+ init_request("nh", no_hyphenate);
+ init_request("nm", number_lines);
+ init_request("nn", no_number);
+ init_request("pev", print_env);
+ init_request("ps", point_size);
+ init_request("pvs", post_vertical_spacing);
+ init_request("rj", right_justify);
+ init_request("sizes", override_sizes);
+ init_request("ss", space_size);
+ init_request("ta", set_tabs);
+ init_request("ti", temporary_indent);
+ init_request("tc", tab_character);
+ init_request("tl", title);
+ init_request("ul", underline);
+ init_request("vs", vertical_spacing);
+#ifdef WIDOW_CONTROL
+ init_request("wdc", widow_control_request);
+#endif /* WIDOW_CONTROL */
+ init_int_env_reg(".b", get_bold);
+ init_vunits_env_reg(".cdp", get_prev_char_depth);
+ init_int_env_reg(".ce", get_center_lines);
+ init_vunits_env_reg(".cht", get_prev_char_height);
+ init_hunits_env_reg(".csk", get_prev_char_skew);
+ init_string_env_reg(".ev", get_name_string);
+ init_int_env_reg(".f", get_font);
+ init_string_env_reg(".fam", get_font_family_string);
+ init_string_env_reg(".fn", get_font_name_string);
+ init_int_env_reg(".height", get_char_height);
+ init_int_env_reg(".hlc", get_hyphen_line_count);
+ init_int_env_reg(".hlm", get_hyphen_line_max);
+ init_int_env_reg(".hy", get_hyphenation_flags);
+ init_hunits_env_reg(".hym", get_hyphenation_margin);
+ init_hunits_env_reg(".hys", get_hyphenation_space);
+ init_hunits_env_reg(".i", get_indent);
+ init_hunits_env_reg(".in", get_saved_indent);
+ init_int_env_reg(".int", get_prev_line_interrupted);
+ init_int_env_reg(".linetabs", get_line_tabs);
+ init_hunits_env_reg(".lt", get_title_length);
+ init_int_env_reg(".j", get_adjust_mode);
+ init_hunits_env_reg(".k", get_text_length);
+ init_int_env_reg(".L", get_line_spacing);
+ init_hunits_env_reg(".l", get_line_length);
+ init_hunits_env_reg(".ll", get_saved_line_length);
+ init_string_env_reg(".M", get_fill_color_string);
+ init_string_env_reg(".m", get_glyph_color_string);
+ init_hunits_env_reg(".n", get_prev_text_length);
+ init_int_env_reg(".nm", get_numbering_nodes);
+ init_int_env_reg(".nn", get_no_number_count);
+ init_int_env_reg(".ps", get_point_size);
+ init_int_env_reg(".psr", get_requested_point_size);
+ init_vunits_env_reg(".pvs", get_post_vertical_spacing);
+ init_int_env_reg(".rj", get_right_justify_lines);
+ init_string_env_reg(".s", get_point_size_string);
+ init_int_env_reg(".slant", get_char_slant);
+ init_int_env_reg(".ss", get_space_size);
+ init_int_env_reg(".sss", get_sentence_space_size);
+ init_string_env_reg(".sr", get_requested_point_size_string);
+ init_string_env_reg(".sty", get_style_name_string);
+ init_string_env_reg(".tabs", get_tabs);
+ init_int_env_reg(".u", get_fill);
+ init_vunits_env_reg(".v", get_vertical_spacing);
+ init_hunits_env_reg(".w", get_prev_char_width);
+ init_int_env_reg(".zoom", get_zoom);
+ register_dictionary.define("ct", new variable_reg(&ct_reg_contents));
+ register_dictionary.define("hp", new horizontal_place_reg);
+ register_dictionary.define("ln", new variable_reg(&next_line_number));
+ register_dictionary.define("rsb", new variable_reg(&rsb_reg_contents));
+ register_dictionary.define("rst", new variable_reg(&rst_reg_contents));
+ register_dictionary.define("sb", new variable_reg(&sb_reg_contents));
+ register_dictionary.define("skw", new variable_reg(&skw_reg_contents));
+ register_dictionary.define("ssc", new variable_reg(&ssc_reg_contents));
+ register_dictionary.define("st", new variable_reg(&st_reg_contents));
+}
+
+// Hyphenation - TeX's hyphenation algorithm with a less fancy implementation.
+
+struct trie_node;
+
+class trie {
+ trie_node *tp;
+ virtual void do_match(int len, void *val) = 0;
+ virtual void do_delete(void *) = 0;
+ void delete_trie_node(trie_node *);
+public:
+ trie() : tp(0) {}
+ virtual ~trie(); // virtual to shut up g++
+ void insert(const char *, int, void *);
+ // find calls do_match for each match it finds
+ void find(const char *pat, int patlen);
+ void clear();
+};
+
+class hyphen_trie : private trie {
+ int *h;
+ void do_match(int i, void *v);
+ void do_delete(void *v);
+ void insert_pattern(const char *pat, int patlen, int *num);
+ void insert_hyphenation(dictionary *ex, const char *pat, int patlen);
+ int hpf_getc(FILE *f);
+public:
+ hyphen_trie() {}
+ ~hyphen_trie() {}
+ void hyphenate(const char *word, int len, int *hyphens);
+ void read_patterns_file(const char *name, int append, dictionary *ex);
+};
+
+struct hyphenation_language {
+ symbol name;
+ dictionary exceptions;
+ hyphen_trie patterns;
+ hyphenation_language(symbol nm) : name(nm), exceptions(501) {}
+ ~hyphenation_language() { }
+};
+
+dictionary language_dictionary(5);
+hyphenation_language *current_language = 0;
+
+static void set_hyphenation_language()
+{
+ symbol nm = get_name(true /* required */);
+ if (!nm.is_null()) {
+ current_language = (hyphenation_language *)language_dictionary.lookup(nm);
+ if (!current_language) {
+ current_language = new hyphenation_language(nm);
+ (void)language_dictionary.lookup(nm, (void *)current_language);
+ }
+ }
+ skip_line();
+}
+
+const int WORD_MAX = 256; // we use unsigned char for offsets in
+ // hyphenation exceptions
+
+static void hyphen_word()
+{
+ if (!current_language) {
+ error("no current hyphenation language");
+ skip_line();
+ return;
+ }
+ char buf[WORD_MAX + 1];
+ unsigned char pos[WORD_MAX + 2];
+ for (;;) {
+ tok.skip();
+ if (tok.is_newline() || tok.is_eof())
+ break;
+ int i = 0;
+ int npos = 0;
+ while (i < WORD_MAX && !tok.is_space() && !tok.is_newline()
+ && !tok.is_eof()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_line();
+ return;
+ }
+ tok.next();
+ if (ci->get_ascii_code() == '-') {
+ if (i > 0 && (npos == 0 || pos[npos - 1] != i))
+ pos[npos++] = i;
+ }
+ else {
+ unsigned char c = ci->get_hyphenation_code();
+ if (c == 0)
+ break;
+ buf[i++] = c;
+ }
+ }
+ if (i > 0) {
+ pos[npos] = 0;
+ buf[i] = 0;
+ unsigned char *tem = new unsigned char[npos + 1];
+ memcpy(tem, pos, npos + 1);
+ tem = (unsigned char *)current_language->exceptions.lookup(symbol(buf),
+ tem);
+ if (tem)
+ delete[] tem;
+ }
+ }
+ skip_line();
+}
+
+struct trie_node {
+ char c;
+ trie_node *down;
+ trie_node *right;
+ void *val;
+ trie_node(char, trie_node *);
+};
+
+trie_node::trie_node(char ch, trie_node *p)
+: c(ch), down(0), right(p), val(0)
+{
+}
+
+trie::~trie()
+{
+ clear();
+}
+
+void trie::clear()
+{
+ delete_trie_node(tp);
+ tp = 0;
+}
+
+
+void trie::delete_trie_node(trie_node *p)
+{
+ if (p) {
+ delete_trie_node(p->down);
+ delete_trie_node(p->right);
+ if (p->val)
+ do_delete(p->val);
+ delete p;
+ }
+}
+
+void trie::insert(const char *pat, int patlen, void *val)
+{
+ trie_node **p = &tp;
+ assert(patlen > 0 && pat != 0);
+ for (;;) {
+ while (*p != 0 && (*p)->c < pat[0])
+ p = &((*p)->right);
+ if (*p == 0 || (*p)->c != pat[0])
+ *p = new trie_node(pat[0], *p);
+ if (--patlen == 0) {
+ (*p)->val = val;
+ break;
+ }
+ ++pat;
+ p = &((*p)->down);
+ }
+}
+
+void trie::find(const char *pat, int patlen)
+{
+ trie_node *p = tp;
+ for (int i = 0; p != 0 && i < patlen; i++) {
+ while (p != 0 && p->c < pat[i])
+ p = p->right;
+ if (p != 0 && p->c == pat[i]) {
+ if (p->val != 0)
+ do_match(i+1, p->val);
+ p = p->down;
+ }
+ else
+ break;
+ }
+}
+
+struct operation {
+ operation *next;
+ short distance;
+ short num;
+ operation(int, int, operation *);
+};
+
+operation::operation(int i, int j, operation *op)
+: next(op), distance(j), num(i)
+{
+}
+
+void hyphen_trie::insert_pattern(const char *pat, int patlen, int *num)
+{
+ operation *op = 0;
+ for (int i = 0; i < patlen+1; i++)
+ if (num[i] != 0)
+ op = new operation(num[i], patlen - i, op);
+ insert(pat, patlen, op);
+}
+
+void hyphen_trie::insert_hyphenation(dictionary *ex, const char *pat,
+ int patlen)
+{
+ char buf[WORD_MAX + 2];
+ unsigned char pos[WORD_MAX + 2];
+ int i = 0, j = 0;
+ int npos = 0;
+ while (j < patlen) {
+ unsigned char c = pat[j++];
+ if (c == '-') {
+ if (i > 0 && (npos == 0 || pos[npos - 1] != i))
+ pos[npos++] = i;
+ }
+ else if (c == ' ')
+ buf[i++] = ' ';
+ else
+ buf[i++] = hpf_code_table[c];
+ }
+ if (i > 0) {
+ pos[npos] = 0;
+ buf[i] = 0;
+ unsigned char *tem = new unsigned char[npos + 1];
+ memcpy(tem, pos, npos + 1);
+ tem = (unsigned char *)ex->lookup(symbol(buf), tem);
+ if (tem)
+ delete[] tem;
+ }
+}
+
+void hyphen_trie::hyphenate(const char *word, int len, int *hyphens)
+{
+ int j;
+ for (j = 0; j < len + 1; j++)
+ hyphens[j] = 0;
+ for (j = 0; j < len - 1; j++) {
+ h = hyphens + j;
+ find(word + j, len - j);
+ }
+}
+
+inline int max(int m, int n)
+{
+ return m > n ? m : n;
+}
+
+void hyphen_trie::do_match(int i, void *v)
+{
+ operation *op = (operation *)v;
+ while (op != 0) {
+ h[i - op->distance] = max(h[i - op->distance], op->num);
+ op = op->next;
+ }
+}
+
+void hyphen_trie::do_delete(void *v)
+{
+ operation *op = (operation *)v;
+ while (op) {
+ operation *tem = op;
+ op = tem->next;
+ delete tem;
+ }
+}
+
+/* We use very simple rules to parse TeX's hyphenation patterns.
+
+ . '%' starts a comment even if preceded by '\'.
+
+ . No support for digraphs and like '\$'.
+
+ . '^^xx' ('x' is 0-9 or a-f), and '^^x' (character code of 'x' in the
+ range 0-127) are recognized; other use of '^' causes an error.
+
+ . No macro expansion.
+
+ . We check for the expression '\patterns{...}' (possibly with
+ whitespace before and after the braces). Everything between the
+ braces is taken as hyphenation patterns. Consequently, '{' and '}'
+ are not allowed in patterns.
+
+ . Similarly, '\hyphenation{...}' gives a list of hyphenation
+ exceptions.
+
+ . '\endinput' is recognized also.
+
+ . For backwards compatibility, if '\patterns' is missing, the
+ whole file is treated as a list of hyphenation patterns (only
+ recognizing '%' as the start of a comment.
+
+*/
+
+int hyphen_trie::hpf_getc(FILE *f)
+{
+ int c = getc(f);
+ int c1;
+ int cc = 0;
+ if (c != '^')
+ return c;
+ c = getc(f);
+ if (c != '^')
+ goto fail;
+ c = getc(f);
+ c1 = getc(f);
+ if (((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))
+ && ((c1 >= '0' && c1 <= '9') || (c1 >= 'a' && c1 <= 'f'))) {
+ if (c >= '0' && c <= '9')
+ c -= '0';
+ else
+ c = c - 'a' + 10;
+ if (c1 >= '0' && c1 <= '9')
+ c1 -= '0';
+ else
+ c1 = c1 - 'a' + 10;
+ cc = c * 16 + c1;
+ }
+ else {
+ ungetc(c1, f);
+ if (c >= 0 && c <= 63)
+ cc = c + 64;
+ else if (c >= 64 && c <= 127)
+ cc = c - 64;
+ else
+ goto fail;
+ }
+ return cc;
+fail:
+ error("invalid ^, ^^x, or ^^xx character in hyphenation patterns file");
+ return c;
+}
+
+void hyphen_trie::read_patterns_file(const char *name, int append,
+ dictionary *ex)
+{
+ if (!append)
+ clear();
+ char buf[WORD_MAX + 1];
+ for (int i = 0; i < WORD_MAX + 1; i++)
+ buf[i] = 0;
+ int num[WORD_MAX + 1];
+ errno = 0;
+ char *path = 0;
+ FILE *fp = mac_path->open_file(name, &path);
+ if (fp == 0) {
+ error("can't find hyphenation patterns file '%1'", name);
+ return;
+ }
+ int c = hpf_getc(fp);
+ int have_patterns = 0; // we've seen \patterns
+ int final_pattern = 0; // 1 if we have a trailing closing brace
+ int have_hyphenation = 0; // we've seen \hyphenation
+ int final_hyphenation = 0; // 1 if we have a trailing closing brace
+ int have_keyword = 0; // we've seen either \patterns or \hyphenation
+ int traditional = 0; // don't handle \patterns
+ for (;;) {
+ for (;;) {
+ if (c == '%') { // skip comments
+ do {
+ c = getc(fp);
+ } while (c != EOF && c != '\n');
+ }
+ if (c == EOF || !csspace(c))
+ break;
+ c = hpf_getc(fp);
+ }
+ if (c == EOF) {
+ if (have_keyword || traditional) // we are done
+ break;
+ else { // rescan file in 'traditional' mode
+ rewind(fp);
+ traditional = 1;
+ c = hpf_getc(fp);
+ continue;
+ }
+ }
+ int i = 0;
+ num[0] = 0;
+ if (!(c == '{' || c == '}')) { // skip braces at line start
+ do { // scan patterns
+ if (csdigit(c))
+ num[i] = c - '0';
+ else {
+ buf[i++] = c;
+ num[i] = 0;
+ }
+ c = hpf_getc(fp);
+ } while (i < WORD_MAX && c != EOF && !csspace(c)
+ && c != '%' && c != '{' && c != '}');
+ }
+ if (!traditional) {
+ if (i >= 9 && !strncmp(buf + i - 9, "\\patterns", 9)) {
+ while (csspace(c))
+ c = hpf_getc(fp);
+ if (c == '{') {
+ if (have_patterns || have_hyphenation)
+ error("\\patterns is not allowed inside of %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ else {
+ have_patterns = 1;
+ have_keyword = 1;
+ }
+ c = hpf_getc(fp);
+ continue;
+ }
+ }
+ else if (i >= 12 && !strncmp(buf + i - 12, "\\hyphenation", 12)) {
+ while (csspace(c))
+ c = hpf_getc(fp);
+ if (c == '{') {
+ if (have_patterns || have_hyphenation)
+ error("\\hyphenation is not allowed inside of %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ else {
+ have_hyphenation = 1;
+ have_keyword = 1;
+ }
+ c = hpf_getc(fp);
+ continue;
+ }
+ }
+ else if (strstr(buf, "\\endinput")) {
+ if (have_patterns || have_hyphenation)
+ error("found \\endinput inside of %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ break;
+ }
+ else if (c == '}') {
+ if (have_patterns) {
+ have_patterns = 0;
+ if (i > 0)
+ final_pattern = 1;
+ }
+ else if (have_hyphenation) {
+ have_hyphenation = 0;
+ if (i > 0)
+ final_hyphenation = 1;
+ }
+ c = hpf_getc(fp);
+ }
+ else if (c == '{') {
+ if (have_patterns || have_hyphenation)
+ error("'{' is not allowed within %1 group",
+ have_patterns ? "\\patterns" : "\\hyphenation");
+ c = hpf_getc(fp); // skipped if not starting \patterns
+ // or \hyphenation
+ }
+ }
+ else {
+ if (c == '{' || c == '}')
+ c = hpf_getc(fp);
+ }
+ if (i > 0) {
+ if (have_patterns || final_pattern || traditional) {
+ for (int j = 0; j < i; j++)
+ buf[j] = hpf_code_table[(unsigned char)buf[j]];
+ insert_pattern(buf, i, num);
+ final_pattern = 0;
+ }
+ else if (have_hyphenation || final_hyphenation) {
+ // hyphenation exceptions in a pattern file are subject to `.hy'
+ // restrictions; we mark such entries with a trailing space
+ buf[i++] = ' ';
+ insert_hyphenation(ex, buf, i);
+ final_hyphenation = 0;
+ }
+ }
+ }
+ fclose(fp);
+ free(path);
+ return;
+}
+
+void hyphenate(hyphen_list *h, unsigned flags)
+{
+ if (!current_language)
+ return;
+ while (h) {
+ while (h && h->hyphenation_code == 0)
+ h = h->next;
+ int len = 0;
+ char hbuf[WORD_MAX + 2];
+ char *buf = hbuf + 1;
+ hyphen_list *tem;
+ for (tem = h; tem && len < WORD_MAX; tem = tem->next) {
+ if (tem->hyphenation_code != 0)
+ buf[len++] = tem->hyphenation_code;
+ else
+ break;
+ }
+ hyphen_list *nexth = tem;
+ if (len >= 2) {
+ // check `.hw' entries
+ buf[len] = 0;
+ unsigned char *pos
+ = (unsigned char *)current_language->exceptions.lookup(buf);
+ if (pos != 0) {
+ int j = 0;
+ int i = 1;
+ for (tem = h; tem != 0; tem = tem->next, i++)
+ if (pos[j] == i) {
+ tem->hyphen = 1;
+ j++;
+ }
+ }
+ else {
+ // check `\hyphenation' entries from pattern files;
+ // such entries are marked with a trailing space
+ buf[len] = ' ';
+ buf[len + 1] = 0;
+ pos = (unsigned char *)current_language->exceptions.lookup(buf);
+ if (pos != 0) {
+ int j = 0;
+ int i = 1;
+ tem = h;
+ if (pos[j] == i) {
+ if (flags & HYPHEN_FIRST_CHAR)
+ tem->hyphen = 1;
+ j++;
+ }
+ tem = tem->next;
+ i++;
+ if (pos[j] == i) {
+ if (!(flags & HYPHEN_NOT_FIRST_CHARS))
+ tem->hyphen = 1;
+ j++;
+ }
+ tem = tem->next;
+ i++;
+ if (!(flags & HYPHEN_LAST_CHAR))
+ --len;
+ if (flags & HYPHEN_NOT_LAST_CHARS)
+ --len;
+ for (; i < len && tem; tem = tem->next, i++)
+ if (pos[j] == i) {
+ tem->hyphen = 1;
+ j++;
+ }
+ }
+ else {
+ hbuf[0] = hbuf[len + 1] = '.';
+ int num[WORD_MAX + 3];
+ current_language->patterns.hyphenate(hbuf, len + 2, num);
+ // The position of a hyphenation point gets marked with an odd
+ // number. Example:
+ //
+ // hbuf: . h e l p f u l .
+ // num: 0 0 0 2 4 3 0 0 0 0
+ if (!(flags & HYPHEN_FIRST_CHAR))
+ num[2] = 0;
+ if (flags & HYPHEN_NOT_FIRST_CHARS)
+ num[3] = 0;
+ if (flags & HYPHEN_LAST_CHAR)
+ ++len;
+ if (flags & HYPHEN_NOT_LAST_CHARS)
+ --len;
+ int i;
+ for (i = 2, tem = h; i < len && tem; tem = tem->next, i++)
+ if (num[i] & 1)
+ tem->hyphen = 1;
+ }
+ }
+ }
+ h = nexth;
+ }
+}
+
+static void do_hyphenation_patterns_file(bool append)
+{
+ symbol name = get_long_name(true /* required */);
+ if (!name.is_null()) {
+ if (!current_language)
+ error("no current hyphenation language");
+ else
+ current_language->patterns.read_patterns_file(
+ name.contents(), append,
+ &current_language->exceptions);
+ }
+ skip_line();
+}
+
+static void hyphenation_patterns_file()
+{
+ do_hyphenation_patterns_file(false /* append */);
+}
+
+static void hyphenation_patterns_file_append()
+{
+ do_hyphenation_patterns_file(true /* append */);
+}
+
+class hyphenation_language_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *hyphenation_language_reg::get_string()
+{
+ return current_language ? current_language->name.contents() : "";
+}
+
+void init_hyphen_requests()
+{
+ init_request("hw", hyphen_word);
+ init_request("hla", set_hyphenation_language);
+ init_request("hpf", hyphenation_patterns_file);
+ init_request("hpfa", hyphenation_patterns_file_append);
+ register_dictionary.define(".hla", new hyphenation_language_reg);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/env.h b/src/roff/troff/env.h
new file mode 100644
index 0000000..f6c1d21
--- /dev/null
+++ b/src/roff/troff/env.h
@@ -0,0 +1,421 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+class statem;
+
+struct size_range {
+ int min;
+ int max;
+};
+
+class font_size {
+ static size_range *size_table;
+ static int nranges;
+ int p;
+public:
+ font_size();
+ font_size(int points);
+ int to_points();
+ int to_scaled_points();
+ int to_units();
+ int operator==(font_size);
+ int operator!=(font_size);
+ static void init_size_table(int *sizes);
+};
+
+inline font_size::font_size() : p(0)
+{
+}
+
+inline int font_size::operator==(font_size fs)
+{
+ return p == fs.p;
+}
+
+inline int font_size::operator!=(font_size fs)
+{
+ return p != fs.p;
+}
+
+inline int font_size::to_scaled_points()
+{
+ return p;
+}
+
+inline int font_size::to_points()
+{
+ return p/sizescale;
+}
+
+class environment;
+
+hunits env_digit_width(environment *);
+hunits env_space_width(environment *);
+hunits env_sentence_space_width(environment *);
+hunits env_narrow_space_width(environment *);
+hunits env_half_narrow_space_width(environment *);
+int env_get_zoom(environment *);
+
+struct tab;
+
+enum tab_type { TAB_NONE, TAB_LEFT, TAB_CENTER, TAB_RIGHT };
+
+class tab_stops {
+ tab *initial_list;
+ tab *repeated_list;
+public:
+ tab_stops();
+ tab_stops(hunits distance, tab_type type);
+ tab_stops(const tab_stops &);
+ ~tab_stops();
+ void operator=(const tab_stops &);
+ tab_type distance_to_next_tab(hunits pos, hunits *distance);
+ tab_type distance_to_next_tab(hunits curpos, hunits *distance, hunits *leftpos);
+ void clear();
+ void add_tab(hunits pos, tab_type type, int repeated);
+ const char *to_string();
+};
+
+const unsigned MARGIN_CHARACTER_ON = 1;
+const unsigned MARGIN_CHARACTER_NEXT = 2;
+
+class charinfo;
+struct node;
+struct breakpoint;
+class font_family;
+class pending_output_line;
+
+// declarations to avoid friend name injection problems
+void title_length();
+void space_size();
+void fill();
+void no_fill();
+void adjust();
+void no_adjust();
+void center();
+void right_justify();
+void vertical_spacing();
+void post_vertical_spacing();
+void line_spacing();
+void line_length();
+void indent();
+void temporary_indent();
+void do_underline(int);
+void do_input_trap(int);
+void set_tabs();
+void margin_character();
+void no_number();
+void number_lines();
+void leader_character();
+void tab_character();
+void hyphenate_request();
+void no_hyphenate();
+void hyphen_line_max_request();
+void hyphenation_space_request();
+void hyphenation_margin_request();
+void line_width();
+#if 0
+void tabs_save();
+void tabs_restore();
+#endif
+void line_tabs_request();
+void title();
+#ifdef WIDOW_CONTROL
+void widow_control_request();
+#endif /* WIDOW_CONTROL */
+
+class environment {
+ int dummy; // dummy environment used for \w
+ hunits prev_line_length;
+ hunits line_length;
+ hunits prev_title_length;
+ hunits title_length;
+ font_size prev_size;
+ font_size size;
+ int requested_size;
+ int prev_requested_size;
+ int char_height;
+ int char_slant;
+ int prev_fontno;
+ int fontno;
+ font_family *prev_family;
+ font_family *family;
+ int space_size; // in 36ths of an em
+ int sentence_space_size; // same but for spaces at the end of sentences
+ int adjust_mode;
+ int fill;
+ int interrupted;
+ int prev_line_interrupted;
+ int center_lines;
+ int right_justify_lines;
+ vunits prev_vertical_spacing;
+ vunits vertical_spacing;
+ vunits prev_post_vertical_spacing;
+ vunits post_vertical_spacing;
+ int prev_line_spacing;
+ int line_spacing;
+ hunits prev_indent;
+ hunits indent;
+ hunits temporary_indent;
+ int have_temporary_indent;
+ hunits saved_indent;
+ hunits target_text_length;
+ int pre_underline_fontno;
+ int underline_lines;
+ int underline_spaces;
+ symbol input_trap;
+ int input_trap_count;
+ int continued_input_trap;
+ node *line; // in reverse order
+ hunits prev_text_length;
+ hunits width_total;
+ int space_total;
+ hunits input_line_start;
+ node *tab_contents;
+ hunits tab_width;
+ hunits tab_distance;
+ int line_tabs;
+ tab_type current_tab;
+ node *leader_node;
+ charinfo *tab_char;
+ charinfo *leader_char;
+ int current_field; // is there a current field?
+ hunits field_distance;
+ hunits pre_field_width;
+ int field_spaces;
+ int tab_field_spaces;
+ int tab_precedes_field;
+ int discarding;
+ int spread_flag; // set by \p
+ unsigned margin_character_flags;
+ node *margin_character_node;
+ hunits margin_character_distance;
+ node *numbering_nodes;
+ hunits line_number_digit_width;
+ int number_text_separation; // in digit spaces
+ int line_number_indent; // in digit spaces
+ int line_number_multiple;
+ int no_number_count;
+ unsigned hyphenation_flags;
+ int hyphen_line_count;
+ int hyphen_line_max;
+ hunits hyphenation_space;
+ hunits hyphenation_margin;
+ int composite; // used for construction of composite char?
+ pending_output_line *pending_lines;
+#ifdef WIDOW_CONTROL
+ int widow_control;
+#endif /* WIDOW_CONTROL */
+ color *glyph_color;
+ color *prev_glyph_color;
+ color *fill_color;
+ color *prev_fill_color;
+
+ tab_type distance_to_next_tab(hunits *);
+ tab_type distance_to_next_tab(hunits *distance, hunits *leftpos);
+ void start_line();
+ void output_line(node *, hunits, int);
+ void output(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width, int was_centered);
+ void output_title(node *nd, int retain_size, vunits vs, vunits post_vs,
+ hunits width);
+#ifdef WIDOW_CONTROL
+ void mark_last_line();
+#endif /* WIDOW_CONTROL */
+ breakpoint *choose_breakpoint();
+ void hyphenate_line(int start_here = 0);
+ void start_field();
+ void wrap_up_field();
+ void add_padding();
+ node *make_tab_node(hunits d, node *next = 0);
+ node *get_prev_char();
+public:
+ int seen_space;
+ int seen_eol;
+ int suppress_next_eol;
+ int seen_break;
+ tab_stops tabs;
+ const symbol name;
+ unsigned char control_char;
+ unsigned char no_break_control_char;
+ charinfo *hyphen_indicator_char;
+
+ environment(symbol);
+ environment(const environment *); // for temporary environment
+ ~environment();
+ statem *construct_state(int only_eol);
+ void print_env();
+ void copy(const environment *);
+ int is_dummy() { return dummy; }
+ int is_empty();
+ int is_composite() { return composite; }
+ void set_composite() { composite = 1; }
+ vunits get_vertical_spacing(); // .v
+ vunits get_post_vertical_spacing(); // .pvs
+ int get_line_spacing(); // .L
+ vunits total_post_vertical_spacing();
+ int get_point_size() { return size.to_scaled_points(); }
+ font_size get_font_size() { return size; }
+ int get_size() { return size.to_units(); }
+ int get_requested_point_size() { return requested_size; }
+ int get_char_height() { return char_height; }
+ int get_char_slant() { return char_slant; }
+ hunits get_digit_width();
+ int get_font() { return fontno; }; // .f
+ int get_zoom(); // .zoom
+ int get_numbering_nodes(); // .nm
+ font_family *get_family() { return family; }
+ int get_bold(); // .b
+ int get_adjust_mode(); // .j
+ int get_fill(); // .u
+ hunits get_indent(); // .i
+ hunits get_temporary_indent();
+ hunits get_line_length(); // .l
+ hunits get_saved_line_length(); // .ll
+ hunits get_saved_indent(); // .in
+ hunits get_title_length();
+ hunits get_prev_char_width(); // .w
+ hunits get_prev_char_skew();
+ vunits get_prev_char_height();
+ vunits get_prev_char_depth();
+ hunits get_text_length(); // .k
+ hunits get_prev_text_length(); // .n
+ hunits get_space_width() { return env_space_width(this); }
+ int get_space_size() { return space_size; } // in ems/36
+ int get_sentence_space_size() { return sentence_space_size; }
+ hunits get_narrow_space_width() { return env_narrow_space_width(this); }
+ hunits get_half_narrow_space_width()
+ { return env_half_narrow_space_width(this); }
+ hunits get_input_line_position();
+ const char *get_tabs();
+ int get_line_tabs();
+ int get_hyphenation_flags();
+ int get_hyphen_line_max();
+ int get_hyphen_line_count();
+ hunits get_hyphenation_space();
+ hunits get_hyphenation_margin();
+ int get_center_lines();
+ int get_right_justify_lines();
+ int get_no_number_count();
+ int get_prev_line_interrupted() { return prev_line_interrupted; }
+ color *get_fill_color();
+ color *get_glyph_color();
+ color *get_prev_glyph_color();
+ color *get_prev_fill_color();
+ void set_glyph_color(color *c);
+ void set_fill_color(color *c);
+ node *make_char_node(charinfo *);
+ node *extract_output_line();
+ void width_registers();
+ void wrap_up_tab();
+ bool set_font(int);
+ bool set_font(symbol);
+ void set_family(symbol);
+ void set_size(int);
+ void set_char_height(int);
+ void set_char_slant(int);
+ void set_input_line_position(hunits); // used by \n(hp
+ void interrupt();
+ void spread() { spread_flag = 1; }
+ void possibly_break_line(int start_here = 0, int forced = 0);
+ void do_break(int spread = 0); // .br
+ void final_break();
+ node *make_tag(const char *name, int i);
+ void newline();
+ void handle_tab(int is_leader = 0); // do a tab or leader
+ void add_node(node *);
+ void add_char(charinfo *);
+ void add_hyphen_indicator();
+ void add_italic_correction();
+ void space();
+ void space(hunits, hunits);
+ void space_newline();
+ const char *get_glyph_color_string();
+ const char *get_fill_color_string();
+ const char *get_font_family_string();
+ const char *get_font_name_string();
+ const char *get_style_name_string();
+ const char *get_name_string();
+ const char *get_point_size_string();
+ const char *get_requested_point_size_string();
+ void output_pending_lines();
+ void construct_format_state(node *n, int was_centered, int fill);
+ void construct_new_line_state(node *n);
+ void dump_troff_state();
+
+ friend void title_length();
+ friend void space_size();
+ friend void fill();
+ friend void no_fill();
+ friend void adjust();
+ friend void no_adjust();
+ friend void center();
+ friend void right_justify();
+ friend void vertical_spacing();
+ friend void post_vertical_spacing();
+ friend void line_spacing();
+ friend void line_length();
+ friend void indent();
+ friend void temporary_indent();
+ friend void do_underline(int);
+ friend void do_input_trap(int);
+ friend void set_tabs();
+ friend void margin_character();
+ friend void no_number();
+ friend void number_lines();
+ friend void leader_character();
+ friend void tab_character();
+ friend void hyphenate_request();
+ friend void no_hyphenate();
+ friend void hyphen_line_max_request();
+ friend void hyphenation_space_request();
+ friend void hyphenation_margin_request();
+ friend void line_width();
+#if 0
+ friend void tabs_save();
+ friend void tabs_restore();
+#endif
+ friend void line_tabs_request();
+ friend void title();
+#ifdef WIDOW_CONTROL
+ friend void widow_control_request();
+#endif /* WIDOW_CONTROL */
+
+ friend void do_divert(int append, int boxing);
+};
+
+extern environment *curenv;
+extern void pop_env();
+extern void push_env(int);
+
+void init_environments();
+void read_hyphen_file(const char *name);
+
+extern double spread_limit;
+
+extern int break_flag;
+extern symbol default_family;
+extern int translate_space_to_dummy;
+
+extern unsigned char hpf_code_table[];
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/hvunits.h b/src/roff/troff/hvunits.h
new file mode 100644
index 0000000..e47a0a0
--- /dev/null
+++ b/src/roff/troff/hvunits.h
@@ -0,0 +1,339 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+class vunits {
+ int n;
+public:
+ vunits();
+ vunits(units);
+ units to_units();
+ int is_zero();
+ vunits& operator+=(const vunits&);
+ vunits& operator-=(const vunits&);
+ friend inline vunits scale(vunits n, units x, units y); // scale n by x/y
+ friend inline vunits scale(vunits n, vunits x, vunits y);
+ friend inline vunits operator +(const vunits&, const vunits&);
+ friend inline vunits operator -(const vunits&, const vunits&);
+ friend inline vunits operator -(const vunits&);
+ friend inline int operator /(const vunits&, const vunits&);
+ friend inline vunits operator /(const vunits&, int);
+ friend inline vunits operator *(const vunits&, int);
+ friend inline vunits operator *(int, const vunits&);
+ friend inline int operator <(const vunits&, const vunits&);
+ friend inline int operator >(const vunits&, const vunits&);
+ friend inline int operator <=(const vunits&, const vunits&);
+ friend inline int operator >=(const vunits&, const vunits&);
+ friend inline int operator ==(const vunits&, const vunits&);
+ friend inline int operator !=(const vunits&, const vunits&);
+};
+
+extern const vunits V0;
+
+
+class hunits {
+ int n;
+public:
+ hunits();
+ hunits(units);
+ units to_units();
+ int is_zero();
+ hunits& operator+=(const hunits&);
+ hunits& operator-=(const hunits&);
+ friend inline hunits scale(hunits n, units x, units y); // scale n by x/y
+ friend inline hunits scale(hunits n, double x);
+ friend inline hunits operator +(const hunits&, const hunits&);
+ friend inline hunits operator -(const hunits&, const hunits&);
+ friend inline hunits operator -(const hunits&);
+ friend inline int operator /(const hunits&, const hunits&);
+ friend inline hunits operator /(const hunits&, int);
+ friend inline hunits operator *(const hunits&, int);
+ friend inline hunits operator *(int, const hunits&);
+ friend inline int operator <(const hunits&, const hunits&);
+ friend inline int operator >(const hunits&, const hunits&);
+ friend inline int operator <=(const hunits&, const hunits&);
+ friend inline int operator >=(const hunits&, const hunits&);
+ friend inline int operator ==(const hunits&, const hunits&);
+ friend inline int operator !=(const hunits&, const hunits&);
+};
+
+extern const hunits H0;
+
+extern int get_vunits(vunits *, unsigned char si);
+extern int get_hunits(hunits *, unsigned char si);
+extern int get_vunits(vunits *, unsigned char si, vunits prev_value);
+extern int get_hunits(hunits *, unsigned char si, hunits prev_value);
+
+inline vunits:: vunits() : n(0)
+{
+}
+
+inline units vunits::to_units()
+{
+ return n*vresolution;
+}
+
+inline int vunits::is_zero()
+{
+ return n == 0;
+}
+
+inline vunits operator +(const vunits & x, const vunits & y)
+{
+ vunits r;
+ r = x;
+ r.n += y.n;
+ return r;
+}
+
+inline vunits operator -(const vunits & x, const vunits & y)
+{
+ vunits r;
+ r = x;
+ r.n -= y.n;
+ return r;
+}
+
+inline vunits operator -(const vunits & x)
+{
+ vunits r;
+ r.n = -x.n;
+ return r;
+}
+
+inline int operator /(const vunits & x, const vunits & y)
+{
+ return x.n/y.n;
+}
+
+inline vunits operator /(const vunits & x, int n)
+{
+ vunits r;
+ r = x;
+ r.n /= n;
+ return r;
+}
+
+inline vunits operator *(const vunits & x, int n)
+{
+ vunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline vunits operator *(int n, const vunits & x)
+{
+ vunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline int operator <(const vunits & x, const vunits & y)
+{
+ return x.n < y.n;
+}
+
+inline int operator >(const vunits & x, const vunits & y)
+{
+ return x.n > y.n;
+}
+
+inline int operator <=(const vunits & x, const vunits & y)
+{
+ return x.n <= y.n;
+}
+
+inline int operator >=(const vunits & x, const vunits & y)
+{
+ return x.n >= y.n;
+}
+
+inline int operator ==(const vunits & x, const vunits & y)
+{
+ return x.n == y.n;
+}
+
+inline int operator !=(const vunits & x, const vunits & y)
+{
+ return x.n != y.n;
+}
+
+
+inline vunits& vunits::operator+=(const vunits & x)
+{
+ n += x.n;
+ return *this;
+}
+
+inline vunits& vunits::operator-=(const vunits & x)
+{
+ n -= x.n;
+ return *this;
+}
+
+inline hunits:: hunits() : n(0)
+{
+}
+
+inline units hunits::to_units()
+{
+ return n*hresolution;
+}
+
+inline int hunits::is_zero()
+{
+ return n == 0;
+}
+
+inline hunits operator +(const hunits & x, const hunits & y)
+{
+ hunits r;
+ r = x;
+ r.n += y.n;
+ return r;
+}
+
+inline hunits operator -(const hunits & x, const hunits & y)
+{
+ hunits r;
+ r = x;
+ r.n -= y.n;
+ return r;
+}
+
+inline hunits operator -(const hunits & x)
+{
+ hunits r;
+ r = x;
+ r.n = -x.n;
+ return r;
+}
+
+inline int operator /(const hunits & x, const hunits & y)
+{
+ return x.n/y.n;
+}
+
+inline hunits operator /(const hunits & x, int n)
+{
+ hunits r;
+ r = x;
+ r.n /= n;
+ return r;
+}
+
+inline hunits operator *(const hunits & x, int n)
+{
+ hunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline hunits operator *(int n, const hunits & x)
+{
+ hunits r;
+ r = x;
+ r.n *= n;
+ return r;
+}
+
+inline int operator <(const hunits & x, const hunits & y)
+{
+ return x.n < y.n;
+}
+
+inline int operator >(const hunits & x, const hunits & y)
+{
+ return x.n > y.n;
+}
+
+inline int operator <=(const hunits & x, const hunits & y)
+{
+ return x.n <= y.n;
+}
+
+inline int operator >=(const hunits & x, const hunits & y)
+{
+ return x.n >= y.n;
+}
+
+inline int operator ==(const hunits & x, const hunits & y)
+{
+ return x.n == y.n;
+}
+
+inline int operator !=(const hunits & x, const hunits & y)
+{
+ return x.n != y.n;
+}
+
+
+inline hunits& hunits::operator+=(const hunits & x)
+{
+ n += x.n;
+ return *this;
+}
+
+inline hunits& hunits::operator-=(const hunits & x)
+{
+ n -= x.n;
+ return *this;
+}
+
+inline hunits scale(hunits n, units x, units y)
+{
+ hunits r;
+ r.n = scale(n.n, x, y);
+ return r;
+}
+
+inline vunits scale(vunits n, units x, units y)
+{
+ vunits r;
+ r.n = scale(n.n, x, y);
+ return r;
+}
+
+inline vunits scale(vunits n, vunits x, vunits y)
+{
+ vunits r;
+ r.n = scale(n.n, x.n, y.n);
+ return r;
+}
+
+inline hunits scale(hunits n, double x)
+{
+ hunits r;
+ r.n = int(n.n*x);
+ return r;
+}
+
+inline units scale(units n, double x)
+{
+ return int(n*x);
+}
+
+inline units points_to_units(units n)
+{
+ return scale(n, units_per_inch, 72);
+}
+
diff --git a/src/roff/troff/input.cpp b/src/roff/troff/input.cpp
new file mode 100644
index 0000000..292ee73
--- /dev/null
+++ b/src/roff/troff/input.cpp
@@ -0,0 +1,9209 @@
+/* Copyright (C) 1989-2022 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "troff.h"
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "font.h"
+#include "charinfo.h"
+#include "macropath.h"
+#include "input.h"
+#include "defs.h"
+#include "unicode.h"
+#include "curtime.h"
+
+// Needed for getpid() and isatty()
+#include "posix.h"
+
+#include "nonposix.h"
+
+#ifdef NEED_DECLARATION_PUTENV
+extern "C" {
+ int putenv(const char *);
+}
+#endif /* NEED_DECLARATION_PUTENV */
+
+#define MACRO_PREFIX "tmac."
+#define MACRO_POSTFIX ".tmac"
+#define INITIAL_STARTUP_FILE "troffrc"
+#define FINAL_STARTUP_FILE "troffrc-end"
+#define DEFAULT_INPUT_STACK_LIMIT 1000
+
+#ifndef DEFAULT_WARNING_MASK
+// warnings that are enabled by default
+#define DEFAULT_WARNING_MASK \
+ (WARN_CHAR|WARN_NUMBER|WARN_BREAK|WARN_SPACE|WARN_FONT|WARN_FILE)
+#endif
+
+// initial size of buffer for reading names; expanded as necessary
+#define ABUF_SIZE 16
+
+extern "C" const char *program_name;
+extern "C" const char *Version_string;
+
+#ifdef COLUMN
+void init_column_requests();
+#endif /* COLUMN */
+
+static node *read_draw_node();
+static void read_color_draw_node(token &);
+static void push_token(const token &);
+void copy_file();
+#ifdef COLUMN
+void vjustify();
+#endif /* COLUMN */
+void transparent_file();
+
+token tok;
+int break_flag = 0;
+int class_flag = 0;
+int color_flag = 1; // colors are on by default
+static int backtrace_flag = 0;
+#ifndef POPEN_MISSING
+char *pipe_command = 0;
+#endif
+charinfo *charset_table[256];
+unsigned char hpf_code_table[256];
+
+static int warning_mask = DEFAULT_WARNING_MASK;
+static int inhibit_errors = 0;
+static int ignoring = 0;
+
+static void enable_warning(const char *);
+static void disable_warning(const char *);
+
+static int escape_char = '\\';
+static symbol end_of_input_macro_name;
+static symbol blank_line_macro_name;
+static symbol leading_spaces_macro_name;
+static int compatible_flag = 0;
+static int do_old_compatible_flag = -1; // for .do request
+int ascii_output_flag = 0;
+int suppress_output_flag = 0;
+int is_html = 0;
+int begin_level = 0; // number of nested \O escapes
+
+int have_input = 0; // whether \f, \F, \D'F...', \H, \m, \M,
+ // \O[345], \R, \s, or \S has been processed
+ // in token::next()
+int old_have_input = 0; // value of have_input right before \n
+bool device_has_tcommand = false; // 't' output command supported
+int unsafe_flag = 0; // safer by default
+
+bool have_multiple_params = false; // e.g., \[e aa], \*[foo bar]
+
+double spread_limit = -3.0 - 1.0; // negative means deactivated
+
+double warn_scale;
+char warn_scaling_indicator;
+int debug_state = 0; // turns on debugging of the html troff state
+
+search_path *mac_path = &safer_macro_path;
+
+// Defaults to the current directory.
+search_path include_search_path(0, 0, 0, 1);
+
+static int get_copy(node**, bool = false, bool = false);
+static void copy_mode_error(const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+enum read_mode { ALLOW_EMPTY, WITH_ARGS, NO_ARGS };
+static symbol read_escape_parameter(read_mode = NO_ARGS);
+static symbol read_long_escape_parameters(read_mode = NO_ARGS);
+static void interpolate_string(symbol);
+static void interpolate_string_with_args(symbol);
+static void interpolate_macro(symbol, bool = false);
+static void interpolate_number_format(symbol);
+static void interpolate_environment_variable(symbol);
+
+static symbol composite_glyph_name(symbol);
+static void interpolate_arg(symbol);
+static request_or_macro *lookup_request(symbol);
+static int get_delim_number(units *, unsigned char);
+static int get_delim_number(units *, unsigned char, units);
+static symbol do_get_long_name(bool, char);
+static int get_line_arg(units *res, unsigned char si, charinfo **cp);
+static bool read_size(int *);
+static symbol get_delim_name();
+static void init_registers();
+static void trapping_blank_line();
+
+class input_iterator;
+input_iterator *make_temp_iterator(const char *);
+const char *input_char_description(int);
+
+void process_input_stack();
+void chop_macro(); // declare to avoid friend name injection
+
+
+void set_escape_char()
+{
+ if (has_arg()) {
+ if (tok.ch() == 0) {
+ error("cannot select invalid escape character; using '\\'");
+ escape_char = '\\';
+ }
+ else
+ escape_char = tok.ch();
+ }
+ else
+ escape_char = '\\';
+ skip_line();
+}
+
+void escape_off()
+{
+ escape_char = 0;
+ skip_line();
+}
+
+static int saved_escape_char = '\\';
+
+void save_escape_char()
+{
+ saved_escape_char = escape_char;
+ skip_line();
+}
+
+void restore_escape_char()
+{
+ escape_char = saved_escape_char;
+ skip_line();
+}
+
+struct arg_list;
+
+class input_iterator {
+public:
+ input_iterator();
+ input_iterator(int is_div);
+ virtual ~input_iterator() {}
+ int get(node **);
+ friend class input_stack;
+ int is_diversion;
+ statem *diversion_state;
+protected:
+ const unsigned char *ptr;
+ const unsigned char *eptr;
+ input_iterator *next;
+private:
+ virtual int fill(node **);
+ virtual int peek();
+ virtual int has_args() { return 0; }
+ virtual int nargs() { return 0; }
+ virtual input_iterator *get_arg(int) { return 0; }
+ virtual arg_list *get_arg_list() { return 0; }
+ virtual symbol get_macro_name() { return NULL_SYMBOL; }
+ virtual int space_follows_arg(int) { return 0; }
+ virtual int get_break_flag() { return 0; }
+ virtual int get_location(int, const char **, int *) { return 0; }
+ virtual void backtrace() {}
+ virtual int set_location(const char *, int) { return 0; }
+ virtual int next_file(FILE *, const char *) { return 0; }
+ virtual void shift(int) {}
+ virtual int is_boundary() {return 0; }
+ virtual int is_file() { return 0; }
+ virtual int is_macro() { return 0; }
+ virtual void save_compatible_flag(int) {}
+ virtual int get_compatible_flag() { return 0; }
+};
+
+input_iterator::input_iterator()
+: is_diversion(0), ptr(0), eptr(0)
+{
+}
+
+input_iterator::input_iterator(int is_div)
+: is_diversion(is_div), ptr(0), eptr(0)
+{
+}
+
+int input_iterator::fill(node **)
+{
+ return EOF;
+}
+
+int input_iterator::peek()
+{
+ return EOF;
+}
+
+inline int input_iterator::get(node **p)
+{
+ return ptr < eptr ? *ptr++ : fill(p);
+}
+
+class input_boundary : public input_iterator {
+public:
+ int is_boundary() { return 1; }
+};
+
+class input_return_boundary : public input_iterator {
+public:
+ int is_boundary() { return 2; }
+};
+
+class file_iterator : public input_iterator {
+ FILE *fp;
+ int lineno;
+ const char *filename;
+ int popened;
+ int newline_flag;
+ int seen_escape;
+ enum { BUF_SIZE = 512 };
+ unsigned char buf[BUF_SIZE];
+ void close();
+public:
+ file_iterator(FILE *, const char *, int = 0);
+ ~file_iterator();
+ int fill(node **);
+ int peek();
+ int get_location(int, const char **, int *);
+ void backtrace();
+ int set_location(const char *, int);
+ int next_file(FILE *, const char *);
+ int is_file();
+};
+
+file_iterator::file_iterator(FILE *f, const char *fn, int po)
+: fp(f), lineno(1), filename(fn), popened(po),
+ newline_flag(0), seen_escape(0)
+{
+ if ((font::use_charnames_in_special) && (fn != 0)) {
+ if (!the_output)
+ init_output();
+ the_output->put_filename(fn, po);
+ }
+}
+
+file_iterator::~file_iterator()
+{
+ close();
+}
+
+void file_iterator::close()
+{
+ if (fp == stdin)
+ clearerr(stdin);
+#ifndef POPEN_MISSING
+ else if (popened)
+ pclose(fp);
+#endif /* not POPEN_MISSING */
+ else
+ fclose(fp);
+}
+
+int file_iterator::is_file()
+{
+ return 1;
+}
+
+int file_iterator::next_file(FILE *f, const char *s)
+{
+ close();
+ filename = s;
+ fp = f;
+ lineno = 1;
+ newline_flag = 0;
+ seen_escape = 0;
+ popened = 0;
+ ptr = 0;
+ eptr = 0;
+ return 1;
+}
+
+int file_iterator::fill(node **)
+{
+ if (newline_flag)
+ lineno++;
+ newline_flag = 0;
+ unsigned char *p = buf;
+ ptr = p;
+ unsigned char *e = p + BUF_SIZE;
+ while (p < e) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ *p++ = c;
+ if (c == '\n') {
+ seen_escape = 0;
+ newline_flag = 1;
+ break;
+ }
+ seen_escape = (c == '\\');
+ }
+ }
+ if (p > buf) {
+ eptr = p;
+ return *ptr++;
+ }
+ else {
+ eptr = p;
+ return EOF;
+ }
+}
+
+int file_iterator::peek()
+{
+ int c = getc(fp);
+ while (is_invalid_input_char(c)) {
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ c = getc(fp);
+ }
+ if (c != EOF)
+ ungetc(c, fp);
+ return c;
+}
+
+int file_iterator::get_location(int /*allow_macro*/,
+ const char **filenamep, int *linenop)
+{
+ *linenop = lineno;
+ if (filename != 0 && strcmp(filename, "-") == 0)
+ *filenamep = "<standard input>";
+ else
+ *filenamep = filename;
+ return 1;
+}
+
+void file_iterator::backtrace()
+{
+ const char *f;
+ int n;
+ // Get side effect of filename rewrite if stdin.
+ (void) get_location(0, &f, &n);
+ if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ errprint("backtrace: %3 '%1':%2\n", f, n, popened ? "pipe" : "file");
+}
+
+int file_iterator::set_location(const char *f, int ln)
+{
+ if (f) {
+ filename = f;
+ if (!the_output)
+ init_output();
+ the_output->put_filename(f, 0);
+ }
+ lineno = ln;
+ return 1;
+}
+
+input_iterator nil_iterator;
+
+class input_stack {
+public:
+ static int get(node **);
+ static int peek();
+ static void push(input_iterator *);
+ static input_iterator *get_arg(int);
+ static arg_list *get_arg_list();
+ static symbol get_macro_name();
+ static int space_follows_arg(int);
+ static int get_break_flag();
+ static int nargs();
+ static int get_location(int, const char **, int *);
+ static int set_location(const char *, int);
+ static void backtrace();
+ static void next_file(FILE *, const char *);
+ static void end_file();
+ static void shift(int n);
+ static void add_boundary();
+ static void add_return_boundary();
+ static int is_return_boundary();
+ static void remove_boundary();
+ static int get_level();
+ static int get_div_level();
+ static void increase_level();
+ static void decrease_level();
+ static void clear();
+ static void pop_macro();
+ static void save_compatible_flag(int);
+ static int get_compatible_flag();
+ static statem *get_diversion_state();
+ static void check_end_diversion(input_iterator *t);
+ static int limit;
+ static int div_level;
+ static statem *diversion_state;
+private:
+ static input_iterator *top;
+ static int level;
+ static int finish_get(node **);
+ static int finish_peek();
+};
+
+input_iterator *input_stack::top = &nil_iterator;
+int input_stack::level = 0;
+int input_stack::limit = DEFAULT_INPUT_STACK_LIMIT;
+int input_stack::div_level = 0;
+statem *input_stack::diversion_state = 0;
+int suppress_push=0;
+
+
+inline int input_stack::get_level()
+{
+ return level;
+}
+
+inline void input_stack::increase_level()
+{
+ level++;
+}
+
+inline void input_stack::decrease_level()
+{
+ level--;
+}
+
+inline int input_stack::get_div_level()
+{
+ return div_level;
+}
+
+inline int input_stack::get(node **np)
+{
+ int res = (top->ptr < top->eptr) ? *top->ptr++ : finish_get(np);
+ if (res == '\n') {
+ old_have_input = have_input;
+ have_input = 0;
+ }
+ return res;
+}
+
+int input_stack::finish_get(node **np)
+{
+ for (;;) {
+ int c = top->fill(np);
+ if (c != EOF || top->is_boundary())
+ return c;
+ if (top == &nil_iterator)
+ break;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+#if defined(DEBUGGING)
+ if (debug_state)
+ if (tem->is_diversion)
+ fprintf(stderr,
+ "in diversion level = %d\n", input_stack::get_div_level());
+#endif
+ top = top->next;
+ level--;
+ delete tem;
+ if (top->ptr < top->eptr)
+ return *top->ptr++;
+ }
+ assert(level == 0);
+ return EOF;
+}
+
+inline int input_stack::peek()
+{
+ return (top->ptr < top->eptr) ? *top->ptr : finish_peek();
+}
+
+void input_stack::check_end_diversion(input_iterator *t)
+{
+ if (t->is_diversion) {
+ div_level--;
+ if (diversion_state)
+ delete diversion_state;
+ diversion_state = t->diversion_state;
+ }
+}
+
+int input_stack::finish_peek()
+{
+ for (;;) {
+ int c = top->peek();
+ if (c != EOF || top->is_boundary())
+ return c;
+ if (top == &nil_iterator)
+ break;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ if (top->ptr < top->eptr)
+ return *top->ptr;
+ }
+ assert(level == 0);
+ return EOF;
+}
+
+void input_stack::add_boundary()
+{
+ push(new input_boundary);
+}
+
+void input_stack::add_return_boundary()
+{
+ push(new input_return_boundary);
+}
+
+int input_stack::is_return_boundary()
+{
+ return top->is_boundary() == 2;
+}
+
+void input_stack::remove_boundary()
+{
+ assert(top->is_boundary());
+ input_iterator *temp = top->next;
+ check_end_diversion(top);
+
+ delete top;
+ top = temp;
+ level--;
+}
+
+void input_stack::push(input_iterator *in)
+{
+ if (in == 0)
+ return;
+ if (++level > limit && limit > 0)
+ fatal("input stack limit exceeded (probable infinite loop)");
+ in->next = top;
+ top = in;
+ if (top->is_diversion) {
+ div_level++;
+ in->diversion_state = diversion_state;
+ diversion_state = curenv->construct_state(0);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ curenv->dump_troff_state();
+ fflush(stderr);
+ }
+#endif
+ }
+#if defined(DEBUGGING)
+ if (debug_state)
+ if (top->is_diversion) {
+ fprintf(stderr,
+ "in diversion level = %d\n", input_stack::get_div_level());
+ fflush(stderr);
+ }
+#endif
+}
+
+statem *get_diversion_state()
+{
+ return input_stack::get_diversion_state();
+}
+
+statem *input_stack::get_diversion_state()
+{
+ if (0 == diversion_state)
+ return 0;
+ else
+ return new statem(diversion_state);
+}
+
+input_iterator *input_stack::get_arg(int i)
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_arg(i);
+ return 0;
+}
+
+arg_list *input_stack::get_arg_list()
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_arg_list();
+ return 0;
+}
+
+symbol input_stack::get_macro_name()
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->get_macro_name();
+ return NULL_SYMBOL;
+}
+
+int input_stack::space_follows_arg(int i)
+{
+ input_iterator *p;
+ for (p = top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->space_follows_arg(i);
+ return 0;
+}
+
+int input_stack::get_break_flag()
+{
+ return top->get_break_flag();
+}
+
+void input_stack::shift(int n)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->has_args()) {
+ p->shift(n);
+ return;
+ }
+}
+
+int input_stack::nargs()
+{
+ for (input_iterator *p =top; p != 0; p = p->next)
+ if (p->has_args())
+ return p->nargs();
+ return 0;
+}
+
+int input_stack::get_location(int allow_macro, const char **filenamep, int *linenop)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->get_location(allow_macro, filenamep, linenop))
+ return 1;
+ return 0;
+}
+
+void input_stack::backtrace()
+{
+ for (input_iterator *p = top; p; p = p->next)
+ p->backtrace();
+}
+
+int input_stack::set_location(const char *filename, int lineno)
+{
+ for (input_iterator *p = top; p; p = p->next)
+ if (p->set_location(filename, lineno))
+ return 1;
+ return 0;
+}
+
+void input_stack::next_file(FILE *fp, const char *s)
+{
+ input_iterator **pp;
+ for (pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
+ if ((*pp)->next_file(fp, s))
+ return;
+ if (++level > limit && limit > 0)
+ fatal("input stack limit exceeded");
+ *pp = new file_iterator(fp, s);
+ (*pp)->next = &nil_iterator;
+}
+
+void input_stack::end_file()
+{
+ for (input_iterator **pp = &top; *pp != &nil_iterator; pp = &(*pp)->next)
+ if ((*pp)->is_file()) {
+ input_iterator *tem = *pp;
+ check_end_diversion(tem);
+ *pp = (*pp)->next;
+ delete tem;
+ level--;
+ return;
+ }
+}
+
+void input_stack::clear()
+{
+ int nboundaries = 0;
+ while (top != &nil_iterator) {
+ if (top->is_boundary())
+ nboundaries++;
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ }
+ // Keep while_request happy.
+ for (; nboundaries > 0; --nboundaries)
+ add_return_boundary();
+}
+
+void input_stack::pop_macro()
+{
+ int nboundaries = 0;
+ int is_macro = 0;
+ do {
+ if (top->next == &nil_iterator)
+ break;
+ if (top->is_boundary())
+ nboundaries++;
+ is_macro = top->is_macro();
+ input_iterator *tem = top;
+ check_end_diversion(tem);
+ top = top->next;
+ level--;
+ delete tem;
+ } while (!is_macro);
+ // Keep while_request happy.
+ for (; nboundaries > 0; --nboundaries)
+ add_return_boundary();
+}
+
+inline void input_stack::save_compatible_flag(int f)
+{
+ top->save_compatible_flag(f);
+}
+
+inline int input_stack::get_compatible_flag()
+{
+ return top->get_compatible_flag();
+}
+
+void backtrace_request()
+{
+ input_stack::backtrace();
+ fflush(stderr);
+ skip_line();
+}
+
+void next_file()
+{
+ symbol nm = get_long_name();
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (nm.is_null())
+ input_stack::end_file();
+ else {
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(nm.contents());
+ if (!fp)
+ error("can't open '%1': %2", nm.contents(), strerror(errno));
+ else
+ input_stack::next_file(fp, nm.contents());
+ }
+ tok.next();
+}
+
+void shift()
+{
+ int n;
+ if (!has_arg() || !get_integer(&n))
+ n = 1;
+ input_stack::shift(n);
+ skip_line();
+}
+
+static char get_char_for_escape_parameter(bool allow_space = false)
+{
+ int c = get_copy(0 /* nullptr */, false /* is defining */,
+ true /* handle \E */);
+ switch (c) {
+ case EOF:
+ copy_mode_error("end of input in escape sequence");
+ return '\0';
+ default:
+ if (!is_invalid_input_char(c))
+ break;
+ // fall through
+ case '\n':
+ if (c == '\n')
+ input_stack::push(make_temp_iterator("\n"));
+ // fall through
+ case ' ':
+ if (c == ' ' && allow_space)
+ break;
+ // fall through
+ case '\t':
+ case '\001':
+ case '\b':
+ copy_mode_error("%1 is not allowed in an escape sequence parameter",
+ input_char_description(c));
+ return '\0';
+ }
+ return c;
+}
+
+static symbol read_two_char_escape_parameter()
+{
+ char buf[3];
+ buf[0] = get_char_for_escape_parameter();
+ if (buf[0] != '\0') {
+ buf[1] = get_char_for_escape_parameter();
+ if (buf[1] == '\0')
+ buf[0] = 0;
+ else
+ buf[2] = 0;
+ }
+ return symbol(buf);
+}
+
+static symbol read_long_escape_parameters(read_mode mode)
+{
+ int start_level = input_stack::get_level();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ char c;
+ int have_char = 0;
+ for (;;) {
+ c = get_char_for_escape_parameter(have_char && mode == WITH_ARGS);
+ if (c == 0) {
+ if (buf != abuf)
+ delete[] buf;
+ return NULL_SYMBOL;
+ }
+ have_char = 1;
+ if (mode == WITH_ARGS && c == ' ')
+ break;
+ if (i + 2 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ if (c == ']' && input_stack::get_level() == start_level)
+ break;
+ buf[i++] = c;
+ }
+ buf[i] = 0;
+ if (c == ' ')
+ have_multiple_params = true;
+ if (buf == abuf) {
+ if (i == 0) {
+ if (mode != ALLOW_EMPTY)
+ copy_mode_error("empty escape name");
+ return EMPTY_SYMBOL;
+ }
+ return symbol(abuf);
+ }
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+static symbol read_escape_parameter(read_mode mode)
+{
+ char c = get_char_for_escape_parameter();
+ if (c == 0)
+ return NULL_SYMBOL;
+ if (c == '(')
+ return read_two_char_escape_parameter();
+ if (c == '[' && !compatible_flag)
+ return read_long_escape_parameters(mode);
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ return symbol(buf);
+}
+
+static symbol read_increment_and_escape_parameter(int *incp)
+{
+ char c = get_char_for_escape_parameter();
+ switch (c) {
+ case 0:
+ *incp = 0;
+ return NULL_SYMBOL;
+ case '(':
+ *incp = 0;
+ return read_two_char_escape_parameter();
+ case '+':
+ *incp = 1;
+ return read_escape_parameter();
+ case '-':
+ *incp = -1;
+ return read_escape_parameter();
+ case '[':
+ if (!compatible_flag) {
+ *incp = 0;
+ return read_long_escape_parameters();
+ }
+ break;
+ }
+ *incp = 0;
+ char buf[2];
+ buf[0] = c;
+ buf[1] = '\0';
+ return symbol(buf);
+}
+
+static int get_copy(node **nd, bool is_defining, bool handle_escape_E)
+{
+ for (;;) {
+ int c = input_stack::get(nd);
+ if (c == PUSH_GROFF_MODE) {
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 0;
+ continue;
+ }
+ if (c == PUSH_COMP_MODE) {
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 1;
+ continue;
+ }
+ if (c == POP_GROFFCOMP_MODE) {
+ compatible_flag = input_stack::get_compatible_flag();
+ continue;
+ }
+ if (c == BEGIN_QUOTE) {
+ input_stack::increase_level();
+ continue;
+ }
+ if (c == END_QUOTE) {
+ input_stack::decrease_level();
+ continue;
+ }
+ if (c == DOUBLE_QUOTE)
+ continue;
+ if (c == ESCAPE_E && handle_escape_E)
+ c = escape_char;
+ if (c == ESCAPE_NEWLINE) {
+ if (is_defining)
+ return c;
+ do {
+ c = input_stack::get(nd);
+ } while (c == ESCAPE_NEWLINE);
+ }
+ if (c != escape_char || escape_char <= 0)
+ return c;
+ again:
+ c = input_stack::peek();
+ switch(c) {
+ case 0:
+ return escape_char;
+ case '"':
+ (void)input_stack::get(0);
+ while ((c = input_stack::get(0)) != '\n' && c != EOF)
+ ;
+ return c;
+ case '#': // Like \" but newline is ignored.
+ (void)input_stack::get(0);
+ while ((c = input_stack::get(0)) != '\n')
+ if (c == EOF)
+ return EOF;
+ break;
+ case '$':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_arg(s);
+ break;
+ }
+ case '*':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter(WITH_ARGS);
+ if (!(s.is_null() || s.is_empty())) {
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ interpolate_string_with_args(s);
+ }
+ else
+ interpolate_string(s);
+ }
+ break;
+ }
+ case 'a':
+ (void)input_stack::get(0);
+ return '\001';
+ case 'e':
+ (void)input_stack::get(0);
+ return ESCAPE_e;
+ case 'E':
+ (void)input_stack::get(0);
+ if (handle_escape_E)
+ goto again;
+ return ESCAPE_E;
+ case 'n':
+ {
+ (void)input_stack::get(0);
+ int inc;
+ symbol s = read_increment_and_escape_parameter(&inc);
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_reg(s, inc);
+ break;
+ }
+ case 'g':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_format(s);
+ break;
+ }
+ case 't':
+ (void)input_stack::get(0);
+ return '\t';
+ case 'V':
+ {
+ (void)input_stack::get(0);
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_environment_variable(s);
+ break;
+ }
+ case '\n':
+ (void)input_stack::get(0);
+ if (is_defining)
+ return ESCAPE_NEWLINE;
+ break;
+ case ' ':
+ (void)input_stack::get(0);
+ return ESCAPE_SPACE;
+ case '~':
+ (void)input_stack::get(0);
+ return ESCAPE_TILDE;
+ case ':':
+ (void)input_stack::get(0);
+ return ESCAPE_COLON;
+ case '|':
+ (void)input_stack::get(0);
+ return ESCAPE_BAR;
+ case '^':
+ (void)input_stack::get(0);
+ return ESCAPE_CIRCUMFLEX;
+ case '{':
+ (void)input_stack::get(0);
+ return ESCAPE_LEFT_BRACE;
+ case '}':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_BRACE;
+ case '`':
+ (void)input_stack::get(0);
+ return ESCAPE_LEFT_QUOTE;
+ case '\'':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_QUOTE;
+ case '-':
+ (void)input_stack::get(0);
+ return ESCAPE_HYPHEN;
+ case '_':
+ (void)input_stack::get(0);
+ return ESCAPE_UNDERSCORE;
+ case 'c':
+ (void)input_stack::get(0);
+ return ESCAPE_c;
+ case '!':
+ (void)input_stack::get(0);
+ return ESCAPE_BANG;
+ case '?':
+ (void)input_stack::get(0);
+ return ESCAPE_QUESTION;
+ case '&':
+ (void)input_stack::get(0);
+ return ESCAPE_AMPERSAND;
+ case ')':
+ (void)input_stack::get(0);
+ return ESCAPE_RIGHT_PARENTHESIS;
+ case '.':
+ (void)input_stack::get(0);
+ return c;
+ case '%':
+ (void)input_stack::get(0);
+ return ESCAPE_PERCENT;
+ default:
+ if (c == escape_char) {
+ (void)input_stack::get(0);
+ return c;
+ }
+ else
+ return escape_char;
+ }
+ }
+}
+
+class non_interpreted_char_node : public node {
+ unsigned char c;
+public:
+ non_interpreted_char_node(unsigned char);
+ node *copy();
+ int interpret(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+int non_interpreted_char_node::same(node *nd)
+{
+ return c == ((non_interpreted_char_node *)nd)->c;
+}
+
+const char *non_interpreted_char_node::type()
+{
+ return "non_interpreted_char_node";
+}
+
+int non_interpreted_char_node::force_tprint()
+{
+ return 0;
+}
+
+int non_interpreted_char_node::is_tag()
+{
+ return 0;
+}
+
+non_interpreted_char_node::non_interpreted_char_node(unsigned char n) : c(n)
+{
+ assert(n != 0);
+}
+
+node *non_interpreted_char_node::copy()
+{
+ return new non_interpreted_char_node(c);
+}
+
+int non_interpreted_char_node::interpret(macro *mac)
+{
+ mac->append(c);
+ return 1;
+}
+
+static void do_width();
+static node *do_non_interpreted();
+static node *do_special();
+static node *do_suppress(symbol nm);
+static void do_register();
+
+dictionary color_dictionary(501);
+
+static color *lookup_color(symbol nm)
+{
+ assert(!nm.is_null());
+ if (nm == default_symbol)
+ return &default_color;
+ color *c = (color *)color_dictionary.lookup(nm);
+ if (c == 0)
+ warning(WARN_COLOR, "color '%1' not defined", nm.contents());
+ return c;
+}
+
+void do_glyph_color(symbol nm)
+{
+ if (nm.is_null())
+ return;
+ if (nm.is_empty())
+ curenv->set_glyph_color(curenv->get_prev_glyph_color());
+ else {
+ color *tem = lookup_color(nm);
+ if (tem)
+ curenv->set_glyph_color(tem);
+ else
+ (void)color_dictionary.lookup(nm, new color(nm));
+ }
+}
+
+void do_fill_color(symbol nm)
+{
+ if (nm.is_null())
+ return;
+ if (nm.is_empty())
+ curenv->set_fill_color(curenv->get_prev_fill_color());
+ else {
+ color *tem = lookup_color(nm);
+ if (tem)
+ curenv->set_fill_color(tem);
+ else
+ (void)color_dictionary.lookup(nm, new color(nm));
+ }
+}
+
+static unsigned int get_color_element(const char *scheme, const char *col)
+{
+ units val;
+ if (!get_number(&val, 'f')) {
+ warning(WARN_COLOR, "%1 in %2 definition set to 0", col, scheme);
+ tok.next();
+ return 0;
+ }
+ if (val < 0) {
+ warning(WARN_RANGE, "%1 cannot be negative: set to 0", col);
+ return 0;
+ }
+ if (val > color::MAX_COLOR_VAL+1) {
+ warning(WARN_RANGE, "%1 cannot be greater than 1", col);
+ // we change 0x10000 to 0xffff
+ return color::MAX_COLOR_VAL;
+ }
+ return (unsigned int)val;
+}
+
+static color *read_rgb(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing rgb color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_rgb(s)) {
+ warning(WARN_COLOR, "expecting rgb color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int r = get_color_element("rgb color", "red component");
+ unsigned int g = get_color_element("rgb color", "green component");
+ unsigned int b = get_color_element("rgb color", "blue component");
+ col->set_rgb(r, g, b);
+ }
+ return col;
+}
+
+static color *read_cmy(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing cmy color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_cmy(s)) {
+ warning(WARN_COLOR, "expecting cmy color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int c = get_color_element("cmy color", "cyan component");
+ unsigned int m = get_color_element("cmy color", "magenta component");
+ unsigned int y = get_color_element("cmy color", "yellow component");
+ col->set_cmy(c, m, y);
+ }
+ return col;
+}
+
+static color *read_cmyk(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing cmyk color values");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_cmyk(s)) {
+ warning(WARN_COLOR, "expecting cmyk color definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator(" "));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int c = get_color_element("cmyk color", "cyan component");
+ unsigned int m = get_color_element("cmyk color", "magenta component");
+ unsigned int y = get_color_element("cmyk color", "yellow component");
+ unsigned int k = get_color_element("cmyk color", "black component");
+ col->set_cmyk(c, m, y, k);
+ }
+ return col;
+}
+
+static color *read_gray(char end = 0)
+{
+ symbol component = do_get_long_name(0, end);
+ if (component.is_null()) {
+ warning(WARN_COLOR, "missing gray value");
+ return 0;
+ }
+ const char *s = component.contents();
+ color *col = new color;
+ if (*s == '#') {
+ if (!col->read_gray(s)) {
+ warning(WARN_COLOR, "expecting gray definition,"
+ " not '%1'", s);
+ delete col;
+ return 0;
+ }
+ }
+ else {
+ if (!end)
+ input_stack::push(make_temp_iterator("\n"));
+ input_stack::push(make_temp_iterator(s));
+ tok.next();
+ unsigned int g = get_color_element("gray", "gray value");
+ col->set_gray(g);
+ }
+ return col;
+}
+
+static void activate_color()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ color_flag = n != 0;
+ else
+ color_flag = 1;
+ skip_line();
+}
+
+static void define_color()
+{
+ symbol color_name = get_long_name(true /* required */);
+ if (color_name.is_null()) {
+ skip_line();
+ return;
+ }
+ if (color_name == default_symbol) {
+ warning(WARN_COLOR, "default color can't be redefined");
+ skip_line();
+ return;
+ }
+ symbol style = get_long_name(true /* required */);
+ if (style.is_null()) {
+ skip_line();
+ return;
+ }
+ color *col;
+ if (strcmp(style.contents(), "rgb") == 0)
+ col = read_rgb();
+ else if (strcmp(style.contents(), "cmyk") == 0)
+ col = read_cmyk();
+ else if (strcmp(style.contents(), "gray") == 0)
+ col = read_gray();
+ else if (strcmp(style.contents(), "grey") == 0)
+ col = read_gray();
+ else if (strcmp(style.contents(), "cmy") == 0)
+ col = read_cmy();
+ else {
+ warning(WARN_COLOR, "unknown color space '%1';"
+ " use 'rgb', 'cmyk', 'gray' or 'cmy'", style.contents());
+ skip_line();
+ return;
+ }
+ if (col) {
+ col->nm = color_name;
+ (void)color_dictionary.lookup(color_name, col);
+ }
+ skip_line();
+}
+
+node *do_overstrike()
+{
+ overstrike_node *on = new overstrike_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in overstrike"
+ " escape sequence (got %1)", tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (tok.is_horizontal_space())
+ on->overstrike(tok.nd->copy());
+ else if (tok.is_unstretchable_space())
+ {
+ node *n = new hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ on->overstrike(n);
+ }
+ else {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ node *n = curenv->make_char_node(ci);
+ if (n)
+ on->overstrike(n);
+ }
+ }
+ }
+ return on;
+}
+
+static node *do_bracket()
+{
+ bracket_node *bn = new bracket_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " bracket-building escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ node *n = curenv->make_char_node(ci);
+ if (n)
+ bn->bracket(n);
+ }
+ }
+ return bn;
+}
+
+static int do_name_test()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ bool got_bad_char = false;
+ bool got_some_char = false;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in identifier"
+ " validation escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (!tok.ch())
+ got_bad_char = true;
+ got_some_char = true;
+ }
+ return (got_some_char && !got_bad_char);
+}
+
+static int do_expr_test()
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return 0;
+ tok.next();
+ // disable all warning and error messages temporarily
+ int saved_warning_mask = warning_mask;
+ int saved_inhibit_errors = inhibit_errors;
+ warning_mask = 0;
+ inhibit_errors = 1;
+ int dummy;
+ int result = get_number_rigidly(&dummy, 'u');
+ warning_mask = saved_warning_mask;
+ inhibit_errors = saved_inhibit_errors;
+ if (tok == start && input_stack::get_level() == start_level)
+ return result;
+ // ignore everything up to the delimiter in case we aren't right there
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " expression test escape sequence (got %1)",
+ tok.description());
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start && input_stack::get_level() == start_level)
+ break;
+ }
+ return 0;
+}
+
+#if 0
+static node *do_zero_width()
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ error("missing closing delimiter");
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ tok.process();
+ }
+ curenv = oldenv;
+ node *rev = env.extract_output_line();
+ node *n = 0;
+ while (rev) {
+ node *tem = rev;
+ rev = rev->next;
+ tem->next = n;
+ n = tem;
+ }
+ return new zero_width_node(n);
+}
+
+#else
+
+// It's undesirable for \Z to change environments, because then
+// \n(.w won't work as expected.
+
+static node *do_zero_width()
+{
+ node *rev = new dummy_node;
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " zero-width escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if (!tok.add_to_zero_width_node_list(&rev))
+ error("invalid token in argument to escaped 'Z'");
+ }
+ node *n = 0;
+ while (rev) {
+ node *tem = rev;
+ rev = rev->next;
+ tem->next = n;
+ n = tem;
+ }
+ return new zero_width_node(n);
+}
+
+#endif
+
+token_node *node::get_token_node()
+{
+ return 0;
+}
+
+class token_node : public node {
+public:
+ token tk;
+ token_node(const token &t);
+ node *copy();
+ token_node *get_token_node();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+token_node::token_node(const token &t) : tk(t)
+{
+}
+
+node *token_node::copy()
+{
+ return new token_node(tk);
+}
+
+token_node *token_node::get_token_node()
+{
+ return this;
+}
+
+int token_node::same(node *nd)
+{
+ return tk == ((token_node *)nd)->tk;
+}
+
+const char *token_node::type()
+{
+ return "token_node";
+}
+
+int token_node::force_tprint()
+{
+ return 0;
+}
+
+int token_node::is_tag()
+{
+ return 0;
+}
+
+token::token() : nd(0), type(TOKEN_EMPTY)
+{
+}
+
+token::~token()
+{
+ delete nd;
+}
+
+token::token(const token &t)
+: nm(t.nm), c(t.c), val(t.val), dim(t.dim), type(t.type)
+{
+ // Use two statements to work around bug in SGI C++.
+ node *tem = t.nd;
+ nd = tem ? tem->copy() : 0;
+}
+
+void token::operator=(const token &t)
+{
+ delete nd;
+ nm = t.nm;
+ // Use two statements to work around bug in SGI C++.
+ node *tem = t.nd;
+ nd = tem ? tem->copy() : 0;
+ c = t.c;
+ val = t.val;
+ dim = t.dim;
+ type = t.type;
+}
+
+void token::skip()
+{
+ while (is_space())
+ next();
+}
+
+bool has_arg()
+{
+ while (tok.is_space())
+ tok.next();
+ return !tok.is_newline();
+}
+
+void token::make_space()
+{
+ type = TOKEN_SPACE;
+}
+
+void token::make_newline()
+{
+ type = TOKEN_NEWLINE;
+}
+
+void token::next()
+{
+ if (nd) {
+ delete nd;
+ nd = 0;
+ }
+ units x;
+ for (;;) {
+ node *n = 0;
+ int cc = input_stack::get(&n);
+ if (cc != escape_char || escape_char == 0) {
+ handle_ordinary_char:
+ switch(cc) {
+ case INPUT_NO_BREAK_SPACE:
+ type = TOKEN_STRETCHABLE_SPACE;
+ return;
+ case INPUT_SOFT_HYPHEN:
+ type = TOKEN_HYPHEN_INDICATOR;
+ return;
+ case PUSH_GROFF_MODE:
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 0;
+ continue;
+ case PUSH_COMP_MODE:
+ input_stack::save_compatible_flag(compatible_flag);
+ compatible_flag = 1;
+ continue;
+ case POP_GROFFCOMP_MODE:
+ compatible_flag = input_stack::get_compatible_flag();
+ continue;
+ case BEGIN_QUOTE:
+ input_stack::increase_level();
+ continue;
+ case END_QUOTE:
+ input_stack::decrease_level();
+ continue;
+ case DOUBLE_QUOTE:
+ continue;
+ case EOF:
+ type = TOKEN_EOF;
+ return;
+ case TRANSPARENT_FILE_REQUEST:
+ case TITLE_REQUEST:
+ case COPY_FILE_REQUEST:
+#ifdef COLUMN
+ case VJUSTIFY_REQUEST:
+#endif /* COLUMN */
+ type = TOKEN_REQUEST;
+ c = cc;
+ return;
+ case BEGIN_TRAP:
+ type = TOKEN_BEGIN_TRAP;
+ return;
+ case END_TRAP:
+ type = TOKEN_END_TRAP;
+ return;
+ case LAST_PAGE_EJECTOR:
+ seen_last_page_ejector = 1;
+ // fall through
+ case PAGE_EJECTOR:
+ type = TOKEN_PAGE_EJECTOR;
+ return;
+ case ESCAPE_PERCENT:
+ ESCAPE_PERCENT:
+ type = TOKEN_HYPHEN_INDICATOR;
+ return;
+ case ESCAPE_SPACE:
+ ESCAPE_SPACE:
+ type = TOKEN_UNSTRETCHABLE_SPACE;
+ return;
+ case ESCAPE_TILDE:
+ ESCAPE_TILDE:
+ type = TOKEN_STRETCHABLE_SPACE;
+ return;
+ case ESCAPE_COLON:
+ ESCAPE_COLON:
+ type = TOKEN_ZERO_WIDTH_BREAK;
+ return;
+ case ESCAPE_e:
+ ESCAPE_e:
+ type = TOKEN_ESCAPE;
+ return;
+ case ESCAPE_E:
+ goto handle_escape_char;
+ case ESCAPE_BAR:
+ ESCAPE_BAR:
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(curenv->get_narrow_space_width(),
+ curenv->get_fill_color());
+ return;
+ case ESCAPE_CIRCUMFLEX:
+ ESCAPE_CIRCUMFLEX:
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(curenv->get_half_narrow_space_width(),
+ curenv->get_fill_color());
+ return;
+ case ESCAPE_NEWLINE:
+ have_input = 0;
+ break;
+ case ESCAPE_LEFT_BRACE:
+ ESCAPE_LEFT_BRACE:
+ type = TOKEN_LEFT_BRACE;
+ return;
+ case ESCAPE_RIGHT_BRACE:
+ ESCAPE_RIGHT_BRACE:
+ type = TOKEN_RIGHT_BRACE;
+ return;
+ case ESCAPE_LEFT_QUOTE:
+ ESCAPE_LEFT_QUOTE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("ga");
+ return;
+ case ESCAPE_RIGHT_QUOTE:
+ ESCAPE_RIGHT_QUOTE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("aa");
+ return;
+ case ESCAPE_HYPHEN:
+ ESCAPE_HYPHEN:
+ type = TOKEN_SPECIAL;
+ nm = symbol("-");
+ return;
+ case ESCAPE_UNDERSCORE:
+ ESCAPE_UNDERSCORE:
+ type = TOKEN_SPECIAL;
+ nm = symbol("ul");
+ return;
+ case ESCAPE_c:
+ ESCAPE_c:
+ type = TOKEN_INTERRUPT;
+ return;
+ case ESCAPE_BANG:
+ ESCAPE_BANG:
+ type = TOKEN_TRANSPARENT;
+ return;
+ case ESCAPE_QUESTION:
+ ESCAPE_QUESTION:
+ nd = do_non_interpreted();
+ if (nd) {
+ type = TOKEN_NODE;
+ return;
+ }
+ break;
+ case ESCAPE_AMPERSAND:
+ ESCAPE_AMPERSAND:
+ type = TOKEN_DUMMY;
+ return;
+ case ESCAPE_RIGHT_PARENTHESIS:
+ ESCAPE_RIGHT_PARENTHESIS:
+ type = TOKEN_TRANSPARENT_DUMMY;
+ return;
+ case '\b':
+ type = TOKEN_BACKSPACE;
+ return;
+ case ' ':
+ type = TOKEN_SPACE;
+ return;
+ case '\t':
+ type = TOKEN_TAB;
+ return;
+ case '\n':
+ type = TOKEN_NEWLINE;
+ return;
+ case '\001':
+ type = TOKEN_LEADER;
+ return;
+ case 0:
+ {
+ assert(n != 0);
+ token_node *tn = n->get_token_node();
+ if (tn) {
+ *this = tn->tk;
+ delete tn;
+ }
+ else {
+ nd = n;
+ type = TOKEN_NODE;
+ }
+ }
+ return;
+ default:
+ type = TOKEN_CHAR;
+ c = cc;
+ return;
+ }
+ }
+ else {
+ handle_escape_char:
+ cc = input_stack::get(&n);
+ switch(cc) {
+ case '(':
+ nm = read_two_char_escape_parameter();
+ type = TOKEN_SPECIAL;
+ return;
+ case EOF:
+ type = TOKEN_EOF;
+ error("end of input after escape character");
+ return;
+ case '`':
+ goto ESCAPE_LEFT_QUOTE;
+ case '\'':
+ goto ESCAPE_RIGHT_QUOTE;
+ case '-':
+ goto ESCAPE_HYPHEN;
+ case '_':
+ goto ESCAPE_UNDERSCORE;
+ case '%':
+ goto ESCAPE_PERCENT;
+ case ' ':
+ goto ESCAPE_SPACE;
+ case '0':
+ nd = new hmotion_node(curenv->get_digit_width(),
+ curenv->get_fill_color());
+ type = TOKEN_HORIZONTAL_SPACE;
+ return;
+ case '|':
+ goto ESCAPE_BAR;
+ case '^':
+ goto ESCAPE_CIRCUMFLEX;
+ case '/':
+ type = TOKEN_ITALIC_CORRECTION;
+ return;
+ case ',':
+ type = TOKEN_NODE;
+ nd = new left_italic_corrected_node;
+ return;
+ case '&':
+ goto ESCAPE_AMPERSAND;
+ case ')':
+ goto ESCAPE_RIGHT_PARENTHESIS;
+ case '!':
+ goto ESCAPE_BANG;
+ case '?':
+ goto ESCAPE_QUESTION;
+ case '~':
+ goto ESCAPE_TILDE;
+ case ':':
+ goto ESCAPE_COLON;
+ case '"':
+ while ((cc = input_stack::get(0)) != '\n' && cc != EOF)
+ ;
+ if (cc == '\n')
+ type = TOKEN_NEWLINE;
+ else
+ type = TOKEN_EOF;
+ return;
+ case '#': // Like \" but newline is ignored.
+ while ((cc = input_stack::get(0)) != '\n')
+ if (cc == EOF) {
+ type = TOKEN_EOF;
+ return;
+ }
+ break;
+ case '$':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_arg(s);
+ break;
+ }
+ case '*':
+ {
+ symbol s = read_escape_parameter(WITH_ARGS);
+ if (!(s.is_null() || s.is_empty())) {
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ interpolate_string_with_args(s);
+ }
+ else
+ interpolate_string(s);
+ }
+ break;
+ }
+ case 'a':
+ nd = new non_interpreted_char_node('\001');
+ type = TOKEN_NODE;
+ return;
+ case 'A':
+ c = '0' + do_name_test();
+ type = TOKEN_CHAR;
+ return;
+ case 'b':
+ nd = do_bracket();
+ type = TOKEN_NODE;
+ return;
+ case 'B':
+ c = '0' + do_expr_test();
+ type = TOKEN_CHAR;
+ return;
+ case 'c':
+ goto ESCAPE_c;
+ case 'C':
+ nm = get_delim_name();
+ if (nm.is_null())
+ break;
+ type = TOKEN_SPECIAL;
+ return;
+ case 'd':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(curenv->get_size() / 2,
+ curenv->get_fill_color());
+ return;
+ case 'D':
+ nd = read_draw_node();
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'e':
+ goto ESCAPE_e;
+ case 'E':
+ goto handle_escape_char;
+ case 'f':
+ {
+ symbol s = read_escape_parameter(ALLOW_EMPTY);
+ if (s.is_null())
+ break;
+ const char *p;
+ for (p = s.contents(); *p != '\0'; p++)
+ if (!csdigit(*p))
+ break;
+ // environment::set_font warns if a bogus mounting position is
+ // requested. We must warn here if a bogus font name is
+ // selected.
+ if (*p != '\0' || s.is_empty()) {
+ if (!curenv->set_font(s))
+ warning(WARN_FONT, "cannot select font '%1'",
+ s.contents());
+ }
+ else
+ (void) curenv->set_font(atoi(s.contents()));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ }
+ case 'F':
+ {
+ symbol s = read_escape_parameter(ALLOW_EMPTY);
+ if (s.is_null())
+ break;
+ curenv->set_family(s);
+ have_input = 1;
+ break;
+ }
+ case 'g':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_format(s);
+ break;
+ }
+ case 'h':
+ if (!get_delim_number(&x, 'm'))
+ break;
+ type = TOKEN_HORIZONTAL_SPACE;
+ nd = new hmotion_node(x, curenv->get_fill_color());
+ return;
+ case 'H':
+ // don't take height increments relative to previous height if
+ // in compatibility mode
+ if (!compatible_flag && curenv->get_char_height()) {
+ if (get_delim_number(&x, 'z', curenv->get_char_height()))
+ curenv->set_char_height(x);
+ }
+ else {
+ if (get_delim_number(&x, 'z', curenv->get_requested_point_size()))
+ curenv->set_char_height(x);
+ }
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'k':
+ nm = read_escape_parameter();
+ if (nm.is_null() || nm.is_empty())
+ break;
+ type = TOKEN_MARK_INPUT;
+ return;
+ case 'l':
+ case 'L':
+ {
+ charinfo *s = 0;
+ if (!get_line_arg(&x, (cc == 'l' ? 'm': 'v'), &s))
+ break;
+ if (s == 0)
+ s = get_charinfo(cc == 'l' ? "ru" : "br");
+ type = TOKEN_NODE;
+ node *char_node = curenv->make_char_node(s);
+ if (cc == 'l')
+ nd = new hline_node(x, char_node);
+ else
+ nd = new vline_node(x, char_node);
+ return;
+ }
+ case 'm':
+ do_glyph_color(read_escape_parameter(ALLOW_EMPTY));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'M':
+ do_fill_color(read_escape_parameter(ALLOW_EMPTY));
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'n':
+ {
+ int inc;
+ symbol s = read_increment_and_escape_parameter(&inc);
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_number_reg(s, inc);
+ break;
+ }
+ case 'N':
+ if (!get_delim_number(&val, 0))
+ break;
+ if (val < 0) {
+ warning(WARN_CHAR, "invalid numbered character %1", val);
+ break;
+ }
+ type = TOKEN_NUMBERED_CHAR;
+ return;
+ case 'o':
+ nd = do_overstrike();
+ type = TOKEN_NODE;
+ return;
+ case 'O':
+ nd = do_suppress(read_escape_parameter());
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'p':
+ type = TOKEN_SPREAD;
+ return;
+ case 'r':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(-curenv->get_size(), curenv->get_fill_color());
+ return;
+ case 'R':
+ do_register();
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 's':
+ if (read_size(&x))
+ curenv->set_size(x);
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 'S':
+ if (get_delim_number(&x, 0))
+ curenv->set_char_slant(x);
+ if (!compatible_flag)
+ have_input = 1;
+ break;
+ case 't':
+ type = TOKEN_NODE;
+ nd = new non_interpreted_char_node('\t');
+ return;
+ case 'u':
+ type = TOKEN_NODE;
+ nd = new vmotion_node(-curenv->get_size() / 2,
+ curenv->get_fill_color());
+ return;
+ case 'v':
+ if (!get_delim_number(&x, 'v'))
+ break;
+ type = TOKEN_NODE;
+ nd = new vmotion_node(x, curenv->get_fill_color());
+ return;
+ case 'V':
+ {
+ symbol s = read_escape_parameter();
+ if (!(s.is_null() || s.is_empty()))
+ interpolate_environment_variable(s);
+ break;
+ }
+ case 'w':
+ do_width();
+ break;
+ case 'x':
+ if (!get_delim_number(&x, 'v'))
+ break;
+ type = TOKEN_NODE;
+ nd = new extra_size_node(x);
+ return;
+ case 'X':
+ nd = do_special();
+ if (!nd)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case 'Y':
+ {
+ symbol s = read_escape_parameter();
+ if (s.is_null() || s.is_empty())
+ break;
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m) {
+ error("can't transparently throughput a request");
+ break;
+ }
+ nd = new special_node(*m);
+ type = TOKEN_NODE;
+ return;
+ }
+ case 'z':
+ {
+ next();
+ if (type == TOKEN_NODE || type == TOKEN_HORIZONTAL_SPACE)
+ nd = new zero_width_node(nd);
+ else {
+ charinfo *ci = get_char(true /* required */);
+ if (ci == 0)
+ break;
+ node *gn = curenv->make_char_node(ci);
+ if (gn == 0)
+ break;
+ nd = new zero_width_node(gn);
+ type = TOKEN_NODE;
+ }
+ return;
+ }
+ case 'Z':
+ nd = do_zero_width();
+ if (nd == 0)
+ break;
+ type = TOKEN_NODE;
+ return;
+ case '{':
+ goto ESCAPE_LEFT_BRACE;
+ case '}':
+ goto ESCAPE_RIGHT_BRACE;
+ case '\n':
+ break;
+ case '[':
+ if (!compatible_flag) {
+ symbol s = read_long_escape_parameters(WITH_ARGS);
+ if (s.is_null() || s.is_empty())
+ break;
+ if (have_multiple_params) {
+ have_multiple_params = false;
+ nm = composite_glyph_name(s);
+ }
+ else {
+ const char *gn = check_unicode_name(s.contents());
+ if (gn) {
+ const char *gn_decomposed = decompose_unicode(gn);
+ if (gn_decomposed)
+ gn = &gn_decomposed[1];
+ const char *groff_gn = unicode_to_glyph_name(gn);
+ if (groff_gn)
+ nm = symbol(groff_gn);
+ else {
+ char *buf = new char[strlen(gn) + 1 + 1];
+ strcpy(buf, "u");
+ strcat(buf, gn);
+ nm = symbol(buf);
+ delete[] buf;
+ }
+ }
+ else
+ nm = symbol(s.contents());
+ }
+ type = TOKEN_SPECIAL;
+ return;
+ }
+ goto handle_ordinary_char;
+ default:
+ if (cc != escape_char && cc != '.')
+ warning(WARN_ESCAPE, "escape character ignored before %1",
+ input_char_description(cc));
+ goto handle_ordinary_char;
+ }
+ }
+ }
+}
+
+int token::operator==(const token &t)
+{
+ if (type != t.type)
+ return 0;
+ switch(type) {
+ case TOKEN_CHAR:
+ return c == t.c;
+ case TOKEN_SPECIAL:
+ return nm == t.nm;
+ case TOKEN_NUMBERED_CHAR:
+ return val == t.val;
+ default:
+ return 1;
+ }
+}
+
+int token::operator!=(const token &t)
+{
+ return !(*this == t);
+}
+
+// is token a suitable delimiter (like ')?
+
+bool token::usable_as_delimiter(bool report_error)
+{
+ switch(type) {
+ case TOKEN_CHAR:
+ switch(c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case '+':
+ case '-':
+ case '/':
+ case '*':
+ case '%':
+ case '<':
+ case '>':
+ case '=':
+ case '&':
+ case ':':
+ case '(':
+ case ')':
+ case '.':
+ if (report_error)
+ error("character '%1' is not allowed as a starting delimiter",
+ char(c));
+ return false;
+ default:
+ return true;
+ }
+ case TOKEN_NODE:
+ // the user doesn't know what a node is
+ if (report_error)
+ error("missing argument or invalid starting delimiter");
+ return false;
+ case TOKEN_SPACE:
+ case TOKEN_STRETCHABLE_SPACE:
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ case TOKEN_HORIZONTAL_SPACE:
+ case TOKEN_TAB:
+ case TOKEN_NEWLINE:
+ if (report_error)
+ error("%1 is not allowed as a starting delimiter", description());
+ return false;
+ default:
+ return true;
+ }
+}
+
+const char *token::description()
+{
+ static char buf[4];
+ switch (type) {
+ case TOKEN_BACKSPACE:
+ return "a backspace character";
+ case TOKEN_CHAR:
+ if (c == INPUT_DELETE)
+ return "a delete character";
+ else {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ buf[3] = '\0';
+ return buf;
+ }
+ case TOKEN_DUMMY:
+ return "an escaped '&'";
+ case TOKEN_ESCAPE:
+ return "an escaped 'e'";
+ case TOKEN_HYPHEN_INDICATOR:
+ return "an escaped '%'";
+ case TOKEN_INTERRUPT:
+ return "an escaped 'c'";
+ case TOKEN_ITALIC_CORRECTION:
+ return "an escaped '/'";
+ case TOKEN_LEADER:
+ return "a leader character";
+ case TOKEN_LEFT_BRACE:
+ return "an escaped '{'";
+ case TOKEN_MARK_INPUT:
+ return "an escaped 'k'";
+ case TOKEN_NEWLINE:
+ return "a newline";
+ case TOKEN_NODE:
+ return "a node";
+ case TOKEN_NUMBERED_CHAR:
+ return "an escaped 'N'";
+ case TOKEN_RIGHT_BRACE:
+ return "an escaped '}'";
+ case TOKEN_SPACE:
+ return "a space";
+ case TOKEN_SPECIAL:
+ return "a special character";
+ case TOKEN_SPREAD:
+ return "an escaped 'p'";
+ case TOKEN_STRETCHABLE_SPACE:
+ return "an escaped '~'";
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ return "an escaped ' '";
+ case TOKEN_HORIZONTAL_SPACE:
+ return "a horizontal motion";
+ case TOKEN_TAB:
+ return "a tab character";
+ case TOKEN_TRANSPARENT:
+ return "an escaped '!'";
+ case TOKEN_TRANSPARENT_DUMMY:
+ return "an escaped ')'";
+ case TOKEN_ZERO_WIDTH_BREAK:
+ return "an escaped ':'";
+ case TOKEN_EOF:
+ return "end of input";
+ default:
+ break;
+ }
+ return "a magic token";
+}
+
+void skip_line()
+{
+ while (!tok.is_newline())
+ if (tok.is_eof())
+ return;
+ else
+ tok.next();
+ tok.next();
+}
+
+void compatible()
+{
+ int n;
+ if (has_arg() && get_integer(&n))
+ compatible_flag = n != 0;
+ else
+ compatible_flag = 1;
+ skip_line();
+}
+
+static void diagnose_missing_identifier(bool required)
+{
+ if (tok.is_newline() || tok.is_eof()) {
+ if (required)
+ warning(WARN_MISSING, "missing identifier");
+ }
+ else if (tok.is_right_brace() || tok.is_tab()) {
+ const char *start = tok.description();
+ do {
+ tok.next();
+ } while (tok.is_space() || tok.is_right_brace() || tok.is_tab());
+ if (!tok.is_newline() && !tok.is_eof())
+ error("%1 is not allowed before an argument", start);
+ else if (required)
+ warning(WARN_MISSING, "missing identifier");
+ }
+ else if (required)
+ error("expected identifier, got %1", tok.description());
+ else
+ error("expected identifier, got %1; treated as missing",
+ tok.description());
+}
+
+static void diagnose_invalid_identifier()
+{
+ if (!tok.is_newline() && !tok.is_eof() && !tok.is_space()
+ && !tok.is_tab() && !tok.is_right_brace()
+ // We don't want to give a warning for .el\{
+ && !tok.is_left_brace())
+ error("%1 is not allowed in an identifier", tok.description());
+}
+
+symbol get_name(bool required)
+{
+ if (compatible_flag) {
+ char buf[3];
+ tok.skip();
+ if ((buf[0] = tok.ch()) != 0) {
+ tok.next();
+ if ((buf[1] = tok.ch()) != 0) {
+ buf[2] = 0;
+ tok.make_space();
+ }
+ else
+ diagnose_invalid_identifier();
+ return symbol(buf);
+ }
+ else {
+ diagnose_missing_identifier(required);
+ return NULL_SYMBOL;
+ }
+ }
+ else
+ return get_long_name(required);
+}
+
+symbol get_long_name(bool required)
+{
+ return do_get_long_name(required, 0);
+}
+
+static symbol do_get_long_name(bool required, char end)
+{
+ while (tok.is_space())
+ tok.next();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ for (;;) {
+ // If end != 0 we normally have to append a null byte
+ if (i + 2 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ if ((buf[i] = tok.ch()) == 0 || buf[i] == end)
+ break;
+ i++;
+ tok.next();
+ }
+ if (i == 0) {
+ diagnose_missing_identifier(required);
+ return NULL_SYMBOL;
+ }
+ if (end && buf[i] == end)
+ buf[i+1] = '\0';
+ else
+ diagnose_invalid_identifier();
+ if (buf == abuf)
+ return symbol(buf);
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+void exit_troff()
+{
+ is_exit_underway = true;
+ topdiv->set_last_page();
+ if (!end_of_input_macro_name.is_null()) {
+ spring_trap(end_of_input_macro_name);
+ tok.next();
+ process_input_stack();
+ }
+ curenv->final_break();
+ tok.next();
+ process_input_stack();
+ end_diversions();
+ if (topdiv->get_page_length() > 0) {
+ is_eoi_macro_finished = true;
+ topdiv->set_ejecting();
+ static unsigned char buf[2] = { LAST_PAGE_EJECTOR, '\0' };
+ input_stack::push(make_temp_iterator((char *)buf));
+ topdiv->space(topdiv->get_page_length(), 1);
+ tok.next();
+ process_input_stack();
+ seen_last_page_ejector = 1; // should be set already
+ topdiv->set_ejecting();
+ push_page_ejector();
+ topdiv->space(topdiv->get_page_length(), 1);
+ tok.next();
+ process_input_stack();
+ }
+ cleanup_and_exit(EXIT_SUCCESS);
+}
+
+// This implements .ex. The input stack must be cleared before calling
+// exit_troff().
+
+void exit_request()
+{
+ input_stack::clear();
+ if (is_exit_underway)
+ tok.next();
+ else
+ exit_troff();
+}
+
+void return_macro_request()
+{
+ if (has_arg() && tok.ch())
+ input_stack::pop_macro();
+ input_stack::pop_macro();
+ tok.next();
+}
+
+void eoi_macro()
+{
+ end_of_input_macro_name = get_name();
+ skip_line();
+}
+
+void blank_line_macro()
+{
+ blank_line_macro_name = get_name();
+ skip_line();
+}
+
+void leading_spaces_macro()
+{
+ leading_spaces_macro_name = get_name();
+ skip_line();
+}
+
+static void trapping_blank_line()
+{
+ if (!blank_line_macro_name.is_null())
+ spring_trap(blank_line_macro_name);
+ else
+ blank_line();
+}
+
+void do_request()
+{
+ assert(do_old_compatible_flag == -1);
+ do_old_compatible_flag = compatible_flag;
+ compatible_flag = 0;
+ symbol nm = get_name();
+ if (nm.is_null())
+ skip_line();
+ else
+ interpolate_macro(nm, true /* don't want next token */);
+ compatible_flag = do_old_compatible_flag;
+ do_old_compatible_flag = -1;
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (m)
+ tok.next();
+}
+
+inline int possibly_handle_first_page_transition()
+{
+ if (topdiv->before_first_page && curdiv == topdiv && !curenv->is_dummy()) {
+ handle_first_page_transition();
+ return 1;
+ }
+ else
+ return 0;
+}
+
+static int transparent_translate(int cc)
+{
+ if (!is_invalid_input_char(cc)) {
+ charinfo *ci = charset_table[cc];
+ switch (ci->get_special_translation(1)) {
+ case charinfo::TRANSLATE_SPACE:
+ return ' ';
+ case charinfo::TRANSLATE_STRETCHABLE_SPACE:
+ return ESCAPE_TILDE;
+ case charinfo::TRANSLATE_DUMMY:
+ return ESCAPE_AMPERSAND;
+ case charinfo::TRANSLATE_HYPHEN_INDICATOR:
+ return ESCAPE_PERCENT;
+ }
+ // This is really ugly.
+ ci = ci->get_translation(1);
+ if (ci) {
+ int c = ci->get_ascii_code();
+ if (c != '\0')
+ return c;
+ if (getenv("GROFF_ENABLE_TRANSPARENCY_WARNINGS")
+ != 0 /* nullptr */)
+ error("can't translate %1 to special character '%2'"
+ " in transparent throughput",
+ input_char_description(cc),
+ ci->nm.contents());
+ }
+ }
+ return cc;
+}
+
+class int_stack {
+ struct int_stack_element {
+ int n;
+ int_stack_element *next;
+ } *top;
+public:
+ int_stack();
+ ~int_stack();
+ void push(int);
+ int is_empty();
+ int pop();
+};
+
+int_stack::int_stack()
+{
+ top = 0;
+}
+
+int_stack::~int_stack()
+{
+ while (top != 0) {
+ int_stack_element *temp = top;
+ top = top->next;
+ delete temp;
+ }
+}
+
+int int_stack::is_empty()
+{
+ return top == 0;
+}
+
+void int_stack::push(int n)
+{
+ int_stack_element *p = new int_stack_element;
+ p->next = top;
+ p->n = n;
+ top = p;
+}
+
+int int_stack::pop()
+{
+ assert(top != 0);
+ int_stack_element *p = top;
+ top = top->next;
+ int n = p->n;
+ delete p;
+ return n;
+}
+
+int node::reread(int *)
+{
+ return 0;
+}
+
+int global_diverted_space = 0;
+
+int diverted_space_node::reread(int *bolp)
+{
+ global_diverted_space = 1;
+ if (curenv->get_fill())
+ trapping_blank_line();
+ else
+ curdiv->space(n);
+ global_diverted_space = 0;
+ *bolp = 1;
+ return 1;
+}
+
+int diverted_copy_file_node::reread(int *bolp)
+{
+ curdiv->copy_file(filename.contents());
+ *bolp = 1;
+ return 1;
+}
+
+int word_space_node::reread(int *)
+{
+ if (unformat) {
+ for (width_list *w = orig_width; w; w = w->next)
+ curenv->space(w->width, w->sentence_width);
+ unformat = 0;
+ return 1;
+ }
+ return 0;
+}
+
+int unbreakable_space_node::reread(int *)
+{
+ return 0;
+}
+
+int hmotion_node::reread(int *)
+{
+ if (unformat && was_tab) {
+ curenv->handle_tab(0);
+ unformat = 0;
+ return 1;
+ }
+ return 0;
+}
+
+static int leading_spaces_number = 0;
+static int leading_spaces_space = 0;
+
+void process_input_stack()
+{
+ int_stack trap_bol_stack;
+ int bol = 1;
+ for (;;) {
+ int suppress_next = 0;
+ switch (tok.type) {
+ case token::TOKEN_CHAR:
+ {
+ unsigned char ch = tok.c;
+ if (bol && !have_input
+ && (ch == curenv->control_char
+ || ch == curenv->no_break_control_char)) {
+ break_flag = ch == curenv->control_char;
+ // skip tabs as well as spaces here
+ do {
+ tok.next();
+ } while (tok.is_white_space());
+ symbol nm = get_name();
+#if defined(DEBUGGING)
+ if (debug_state) {
+ if (! nm.is_null()) {
+ if (strcmp(nm.contents(), "test") == 0) {
+ fprintf(stderr, "found it!\n");
+ fflush(stderr);
+ }
+ fprintf(stderr, "interpreting [%s]", nm.contents());
+ if (strcmp(nm.contents(), "di") == 0 && topdiv != curdiv)
+ fprintf(stderr, " currently in diversion: %s",
+ curdiv->get_diversion_name());
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ }
+ }
+#endif
+ if (nm.is_null())
+ skip_line();
+ else {
+ interpolate_macro(nm);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "finished interpreting [%s] and environment state is\n", nm.contents());
+ curenv->dump_troff_state();
+ }
+#endif
+ }
+ suppress_next = 1;
+ }
+ else {
+ if (possibly_handle_first_page_transition())
+ ;
+ else {
+ for (;;) {
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "found [%c]\n", ch); fflush(stderr);
+ }
+#endif
+ curenv->add_char(charset_table[ch]);
+ tok.next();
+ if (tok.type != token::TOKEN_CHAR)
+ break;
+ ch = tok.c;
+ }
+ suppress_next = 1;
+ bol = 0;
+ }
+ }
+ break;
+ }
+ case token::TOKEN_TRANSPARENT:
+ {
+ if (bol) {
+ if (possibly_handle_first_page_transition())
+ ;
+ else {
+ int cc;
+ do {
+ node *n;
+ cc = get_copy(&n);
+ if (cc != EOF) {
+ if (cc != '\0')
+ curdiv->transparent_output(transparent_translate(cc));
+ else
+ curdiv->transparent_output(n);
+ }
+ } while (cc != '\n' && cc != EOF);
+ if (cc == EOF)
+ curdiv->transparent_output('\n');
+ }
+ }
+ break;
+ }
+ case token::TOKEN_NEWLINE:
+ {
+ if (bol && !old_have_input
+ && !curenv->get_prev_line_interrupted())
+ trapping_blank_line();
+ else {
+ curenv->newline();
+ bol = 1;
+ }
+ break;
+ }
+ case token::TOKEN_REQUEST:
+ {
+ int request_code = tok.c;
+ tok.next();
+ switch (request_code) {
+ case TITLE_REQUEST:
+ title();
+ break;
+ case COPY_FILE_REQUEST:
+ copy_file();
+ break;
+ case TRANSPARENT_FILE_REQUEST:
+ transparent_file();
+ break;
+#ifdef COLUMN
+ case VJUSTIFY_REQUEST:
+ vjustify();
+ break;
+#endif /* COLUMN */
+ default:
+ assert(0);
+ break;
+ }
+ suppress_next = 1;
+ break;
+ }
+ case token::TOKEN_SPACE:
+ {
+ if (possibly_handle_first_page_transition())
+ ;
+ else if (bol && !curenv->get_prev_line_interrupted()) {
+ int nspaces = 0;
+ // save space_width now so that it isn't changed by \f or \s
+ // which we wouldn't notice here
+ hunits space_width = curenv->get_space_width();
+ do {
+ nspaces += tok.nspaces();
+ tok.next();
+ } while (tok.is_space());
+ if (tok.is_newline())
+ trapping_blank_line();
+ else {
+ push_token(tok);
+ leading_spaces_number = nspaces;
+ leading_spaces_space = space_width.to_units() * nspaces;
+ if (!leading_spaces_macro_name.is_null())
+ spring_trap(leading_spaces_macro_name);
+ else {
+ curenv->do_break();
+ curenv->add_node(new hmotion_node(space_width * nspaces,
+ curenv->get_fill_color()));
+ }
+ bol = 0;
+ }
+ }
+ else {
+ curenv->space();
+ bol = 0;
+ }
+ break;
+ }
+ case token::TOKEN_EOF:
+ return;
+ case token::TOKEN_NODE:
+ case token::TOKEN_HORIZONTAL_SPACE:
+ {
+ if (possibly_handle_first_page_transition())
+ ;
+ else if (tok.nd->reread(&bol)) {
+ delete tok.nd;
+ tok.nd = 0;
+ }
+ else {
+ curenv->add_node(tok.nd);
+ tok.nd = 0;
+ bol = 0;
+ curenv->possibly_break_line(1);
+ }
+ break;
+ }
+ case token::TOKEN_PAGE_EJECTOR:
+ {
+ continue_page_eject();
+ // I think we just want to preserve bol.
+ // bol = 1;
+ break;
+ }
+ case token::TOKEN_BEGIN_TRAP:
+ {
+ trap_bol_stack.push(bol);
+ bol = 1;
+ have_input = 0;
+ break;
+ }
+ case token::TOKEN_END_TRAP:
+ {
+ if (trap_bol_stack.is_empty())
+ error("spurious end trap token detected!");
+ else
+ bol = trap_bol_stack.pop();
+ have_input = 0;
+
+ /* I'm not totally happy about this. But I can't think of any other
+ way to do it. Doing an output_pending_lines() whenever a
+ TOKEN_END_TRAP is detected doesn't work: for example,
+
+ .wh -1i x
+ .de x
+ 'bp
+ ..
+ .wh -.5i y
+ .de y
+ .tl ''-%-''
+ ..
+ .br
+ .ll .5i
+ .sp |\n(.pu-1i-.5v
+ a\%very\%very\%long\%word
+
+ will print all but the first lines from the word immediately
+ after the footer, rather than on the next page. */
+
+ if (trap_bol_stack.is_empty())
+ curenv->output_pending_lines();
+ break;
+ }
+ default:
+ {
+ bol = 0;
+ tok.process();
+ break;
+ }
+ }
+ if (!suppress_next)
+ tok.next();
+ trap_sprung_flag = 0;
+ }
+}
+
+#ifdef WIDOW_CONTROL
+
+void flush_pending_lines()
+{
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ curenv->output_pending_lines();
+ tok.next();
+}
+
+#endif /* WIDOW_CONTROL */
+
+request_or_macro::request_or_macro()
+{
+}
+
+macro *request_or_macro::to_macro()
+{
+ return 0;
+}
+
+request::request(REQUEST_FUNCP pp) : p(pp)
+{
+}
+
+void request::invoke(symbol, bool)
+{
+ (*p)();
+}
+
+struct char_block {
+ enum { SIZE = 128 };
+ unsigned char s[SIZE];
+ char_block *next;
+ char_block();
+};
+
+char_block::char_block()
+: next(0)
+{
+}
+
+class char_list {
+public:
+ char_list();
+ ~char_list();
+ void append(unsigned char);
+ void set(unsigned char, int);
+ unsigned char get(int);
+ int length();
+private:
+ unsigned char *ptr;
+ int len;
+ char_block *head;
+ char_block *tail;
+ friend class macro_header;
+ friend class string_iterator;
+};
+
+char_list::char_list()
+: ptr(0), len(0), head(0), tail(0)
+{
+}
+
+char_list::~char_list()
+{
+ while (head != 0) {
+ char_block *tem = head;
+ head = head->next;
+ delete tem;
+ }
+}
+
+int char_list::length()
+{
+ return len;
+}
+
+void char_list::append(unsigned char c)
+{
+ if (tail == 0) {
+ head = tail = new char_block;
+ ptr = tail->s;
+ }
+ else {
+ if (ptr >= tail->s + char_block::SIZE) {
+ tail->next = new char_block;
+ tail = tail->next;
+ ptr = tail->s;
+ }
+ }
+ *ptr++ = c;
+ len++;
+}
+
+void char_list::set(unsigned char c, int offset)
+{
+ assert(len > offset);
+ // optimization for access at the end
+ int boundary = len - len % char_block::SIZE;
+ if (offset >= boundary) {
+ *(tail->s + offset - boundary) = c;
+ return;
+ }
+ char_block *tem = head;
+ int l = 0;
+ for (;;) {
+ l += char_block::SIZE;
+ if (l > offset) {
+ *(tem->s + offset % char_block::SIZE) = c;
+ return;
+ }
+ tem = tem->next;
+ }
+}
+
+unsigned char char_list::get(int offset)
+{
+ assert(len > offset);
+ // optimization for access at the end
+ int boundary = len - len % char_block::SIZE;
+ if (offset >= boundary)
+ return *(tail->s + offset - boundary);
+ char_block *tem = head;
+ int l = 0;
+ for (;;) {
+ l += char_block::SIZE;
+ if (l > offset)
+ return *(tem->s + offset % char_block::SIZE);
+ tem = tem->next;
+ }
+}
+
+class node_list {
+ node *head;
+ node *tail;
+public:
+ node_list();
+ ~node_list();
+ void append(node *);
+ int length();
+ node *extract();
+
+ friend class macro_header;
+ friend class string_iterator;
+};
+
+void node_list::append(node *n)
+{
+ if (head == 0) {
+ n->next = 0;
+ head = tail = n;
+ }
+ else {
+ n->next = 0;
+ tail = tail->next = n;
+ }
+}
+
+int node_list::length()
+{
+ int total = 0;
+ for (node *n = head; n != 0; n = n->next)
+ ++total;
+ return total;
+}
+
+node_list::node_list()
+{
+ head = tail = 0;
+}
+
+node *node_list::extract()
+{
+ node *temp = head;
+ head = tail = 0;
+ return temp;
+}
+
+node_list::~node_list()
+{
+ delete_node_list(head);
+}
+
+class macro_header {
+public:
+ int count;
+ char_list cl;
+ node_list nl;
+ macro_header() { count = 1; }
+ macro_header *copy(int);
+};
+
+macro::~macro()
+{
+ if (p != 0 && --(p->count) <= 0)
+ delete p;
+}
+
+macro::macro()
+: is_a_diversion(0), is_a_string(1)
+{
+ if (!input_stack::get_location(1, &filename, &lineno)) {
+ filename = 0;
+ lineno = 0;
+ }
+ len = 0;
+ empty_macro = 1;
+ p = 0;
+}
+
+macro::macro(const macro &m)
+: filename(m.filename), lineno(m.lineno), len(m.len),
+ empty_macro(m.empty_macro), is_a_diversion(m.is_a_diversion),
+ is_a_string(m.is_a_string), p(m.p)
+{
+ if (p != 0)
+ p->count++;
+}
+
+macro::macro(int is_div)
+: is_a_diversion(is_div)
+{
+ if (!input_stack::get_location(1, &filename, &lineno)) {
+ filename = 0;
+ lineno = 0;
+ }
+ len = 0;
+ empty_macro = 1;
+ is_a_string = 1;
+ p = 0;
+}
+
+int macro::is_diversion()
+{
+ return is_a_diversion;
+}
+
+int macro::is_string()
+{
+ return is_a_string;
+}
+
+void macro::clear_string_flag()
+{
+ is_a_string = 0;
+}
+
+macro &macro::operator=(const macro &m)
+{
+ // don't assign object
+ if (m.p != 0)
+ m.p->count++;
+ if (p != 0 && --(p->count) <= 0)
+ delete p;
+ p = m.p;
+ filename = m.filename;
+ lineno = m.lineno;
+ len = m.len;
+ empty_macro = m.empty_macro;
+ is_a_diversion = m.is_a_diversion;
+ is_a_string = m.is_a_string;
+ return *this;
+}
+
+void macro::append(unsigned char c)
+{
+ assert(c != 0);
+ if (p == 0)
+ p = new macro_header;
+ if (p->cl.length() != len) {
+ macro_header *tem = p->copy(len);
+ if (--(p->count) <= 0)
+ delete p;
+ p = tem;
+ }
+ p->cl.append(c);
+ ++len;
+ if (c != PUSH_GROFF_MODE && c != PUSH_COMP_MODE && c != POP_GROFFCOMP_MODE)
+ empty_macro = 0;
+}
+
+void macro::set(unsigned char c, int offset)
+{
+ assert(p != 0);
+ assert(c != 0);
+ p->cl.set(c, offset);
+}
+
+unsigned char macro::get(int offset)
+{
+ assert(p != 0);
+ return p->cl.get(offset);
+}
+
+int macro::length()
+{
+ return len;
+}
+
+void macro::append_str(const char *s)
+{
+ int i = 0;
+
+ if (s) {
+ while (s[i] != (char)0) {
+ append(s[i]);
+ i++;
+ }
+ }
+}
+
+void macro::append(node *n)
+{
+ assert(n != 0);
+ if (p == 0)
+ p = new macro_header;
+ if (p->cl.length() != len) {
+ macro_header *tem = p->copy(len);
+ if (--(p->count) <= 0)
+ delete p;
+ p = tem;
+ }
+ p->cl.append(0);
+ p->nl.append(n);
+ ++len;
+ empty_macro = 0;
+}
+
+void macro::append_unsigned(unsigned int i)
+{
+ unsigned int j = i / 10;
+ if (j != 0)
+ append_unsigned(j);
+ append(((unsigned char)(((int)'0') + i % 10)));
+}
+
+void macro::append_int(int i)
+{
+ if (i < 0) {
+ append('-');
+ i = -i;
+ }
+ append_unsigned((unsigned int)i);
+}
+
+void macro::print_size()
+{
+ errprint("%1", len);
+}
+
+// make a copy of the first n bytes
+
+macro_header *macro_header::copy(int n)
+{
+ macro_header *p = new macro_header;
+ char_block *bp = cl.head;
+ unsigned char *ptr = bp->s;
+ node *nd = nl.head;
+ while (--n >= 0) {
+ if (ptr >= bp->s + char_block::SIZE) {
+ bp = bp->next;
+ ptr = bp->s;
+ }
+ unsigned char c = *ptr++;
+ p->cl.append(c);
+ if (c == 0) {
+ p->nl.append(nd->copy());
+ nd = nd->next;
+ }
+ }
+ return p;
+}
+
+void print_macros()
+{
+ object_dictionary_iterator iter(request_dictionary);
+ request_or_macro *rm;
+ symbol s;
+ while (iter.get(&s, (object **)&rm)) {
+ assert(!s.is_null());
+ macro *m = rm->to_macro();
+ if (m) {
+ errprint("%1\t", s.contents());
+ m->print_size();
+ errprint("\n");
+ }
+ }
+ fflush(stderr);
+ skip_line();
+}
+
+class string_iterator : public input_iterator {
+ macro mac;
+ const char *how_invoked;
+ int newline_flag;
+ int lineno;
+ char_block *bp;
+ int count; // of characters remaining
+ node *nd;
+ int saved_compatible_flag;
+ int with_break; // inherited from the caller
+protected:
+ symbol nm;
+ string_iterator();
+public:
+ string_iterator(const macro &, const char * = 0, symbol = NULL_SYMBOL);
+ int fill(node **);
+ int peek();
+ int get_location(int, const char **, int *);
+ void backtrace();
+ int get_break_flag() { return with_break; }
+ void save_compatible_flag(int f) { saved_compatible_flag = f; }
+ int get_compatible_flag() { return saved_compatible_flag; }
+ int is_diversion();
+};
+
+string_iterator::string_iterator(const macro &m, const char *p, symbol s)
+: input_iterator(m.is_a_diversion), mac(m), how_invoked(p), newline_flag(0),
+ lineno(1), nm(s)
+{
+ count = mac.len;
+ if (count != 0) {
+ bp = mac.p->cl.head;
+ nd = mac.p->nl.head;
+ ptr = eptr = bp->s;
+ }
+ else {
+ bp = 0;
+ nd = 0;
+ ptr = eptr = 0;
+ }
+ with_break = input_stack::get_break_flag();
+}
+
+string_iterator::string_iterator()
+{
+ bp = 0;
+ nd = 0;
+ ptr = eptr = 0;
+ newline_flag = 0;
+ how_invoked = 0;
+ lineno = 1;
+ count = 0;
+ with_break = input_stack::get_break_flag();
+}
+
+int string_iterator::is_diversion()
+{
+ return mac.is_diversion();
+}
+
+int string_iterator::fill(node **np)
+{
+ if (newline_flag)
+ lineno++;
+ newline_flag = 0;
+ if (count <= 0)
+ return EOF;
+ const unsigned char *p = eptr;
+ if (p >= bp->s + char_block::SIZE) {
+ bp = bp->next;
+ p = bp->s;
+ }
+ if (*p == '\0') {
+ if (np) {
+ *np = nd->copy();
+ if (is_diversion())
+ (*np)->div_nest_level = input_stack::get_div_level();
+ else
+ (*np)->div_nest_level = 0;
+ }
+ nd = nd->next;
+ eptr = ptr = p + 1;
+ count--;
+ return 0;
+ }
+ const unsigned char *e = bp->s + char_block::SIZE;
+ if (e - p > count)
+ e = p + count;
+ ptr = p;
+ while (p < e) {
+ unsigned char c = *p;
+ if (c == '\n' || c == ESCAPE_NEWLINE) {
+ newline_flag = 1;
+ p++;
+ break;
+ }
+ if (c == '\0')
+ break;
+ p++;
+ }
+ eptr = p;
+ count -= p - ptr;
+ return *ptr++;
+}
+
+int string_iterator::peek()
+{
+ if (count <= 0)
+ return EOF;
+ const unsigned char *p = eptr;
+ if (p >= bp->s + char_block::SIZE) {
+ p = bp->next->s;
+ }
+ return *p;
+}
+
+int string_iterator::get_location(int allow_macro,
+ const char **filep, int *linep)
+{
+ if (!allow_macro)
+ return 0;
+ if (mac.filename == 0)
+ return 0;
+ *filep = mac.filename;
+ *linep = mac.lineno + lineno - 1;
+ return 1;
+}
+
+void string_iterator::backtrace()
+{
+ if (mac.filename) {
+ if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ errprint("backtrace: '%1':%2", mac.filename,
+ mac.lineno + lineno - 1);
+ if (how_invoked) {
+ if (!nm.is_null())
+ errprint(": %1 '%2'\n", how_invoked, nm.contents());
+ else
+ errprint(": %1\n", how_invoked);
+ }
+ else
+ errprint("\n");
+ }
+}
+
+class temp_iterator : public input_iterator {
+ unsigned char *base;
+ temp_iterator(const char *, int len);
+public:
+ ~temp_iterator();
+ friend input_iterator *make_temp_iterator(const char *);
+};
+
+#ifdef __GNUG__
+inline
+#endif
+temp_iterator::temp_iterator(const char *s, int len)
+{
+ base = new unsigned char[len];
+ if (len > 0)
+ memcpy(base, s, len);
+ ptr = base;
+ eptr = base + len;
+}
+
+temp_iterator::~temp_iterator()
+{
+ delete[] base;
+}
+
+
+input_iterator *make_temp_iterator(const char *s)
+{
+ if (s == 0)
+ return new temp_iterator(s, 0);
+ else {
+ int n = strlen(s);
+ return new temp_iterator(s, n);
+ }
+}
+
+// this is used when macros with arguments are interpolated
+
+struct arg_list {
+ macro mac;
+ int space_follows;
+ arg_list *next;
+ arg_list(const macro &, int);
+ arg_list(const arg_list *);
+ ~arg_list();
+};
+
+arg_list::arg_list(const macro &m, int s) : mac(m), space_follows(s), next(0)
+{
+}
+
+arg_list::arg_list(const arg_list *al)
+: next(0)
+{
+ mac = al->mac;
+ space_follows = al->space_follows;
+ arg_list **a = &next;
+ arg_list *p = al->next;
+ while (p) {
+ *a = new arg_list(p->mac, p->space_follows);
+ p = p->next;
+ a = &(*a)->next;
+ }
+}
+
+arg_list::~arg_list()
+{
+}
+
+class macro_iterator : public string_iterator {
+ arg_list *args;
+ int argc;
+ int with_break; // whether called as .foo or 'foo
+public:
+ macro_iterator(symbol, macro &, const char * = "macro", int = 0);
+ macro_iterator();
+ ~macro_iterator();
+ int has_args() { return 1; }
+ input_iterator *get_arg(int);
+ arg_list *get_arg_list();
+ symbol get_macro_name();
+ int space_follows_arg(int);
+ int get_break_flag() { return with_break; }
+ int nargs() { return argc; }
+ void add_arg(const macro &, int);
+ void shift(int);
+ int is_macro() { return 1; }
+ int is_diversion();
+};
+
+input_iterator *macro_iterator::get_arg(int i)
+{
+ if (i == 0)
+ return make_temp_iterator(nm.contents());
+ if (i > 0 && i <= argc) {
+ arg_list *p = args;
+ for (int j = 1; j < i; j++) {
+ assert(p != 0);
+ p = p->next;
+ }
+ return new string_iterator(p->mac);
+ }
+ else
+ return 0;
+}
+
+arg_list *macro_iterator::get_arg_list()
+{
+ return args;
+}
+
+symbol macro_iterator::get_macro_name()
+{
+ return nm;
+}
+
+int macro_iterator::space_follows_arg(int i)
+{
+ if (i > 0 && i <= argc) {
+ arg_list *p = args;
+ for (int j = 1; j < i; j++) {
+ assert(p != 0);
+ p = p->next;
+ }
+ return p->space_follows;
+ }
+ else
+ return 0;
+}
+
+void macro_iterator::add_arg(const macro &m, int s)
+{
+ arg_list **p;
+ for (p = &args; *p; p = &((*p)->next))
+ ;
+ *p = new arg_list(m, s);
+ ++argc;
+}
+
+void macro_iterator::shift(int n)
+{
+ while (n > 0 && argc > 0) {
+ arg_list *tem = args;
+ args = args->next;
+ delete tem;
+ --argc;
+ --n;
+ }
+}
+
+// This gets used by, e.g., .if '\?xxx\?''.
+
+int operator==(const macro &m1, const macro &m2)
+{
+ if (m1.len != m2.len)
+ return 0;
+ string_iterator iter1(m1);
+ string_iterator iter2(m2);
+ int n = m1.len;
+ while (--n >= 0) {
+ node *nd1 = 0;
+ int c1 = iter1.get(&nd1);
+ assert(c1 != EOF);
+ node *nd2 = 0;
+ int c2 = iter2.get(&nd2);
+ assert(c2 != EOF);
+ if (c1 != c2) {
+ if (c1 == 0)
+ delete nd1;
+ else if (c2 == 0)
+ delete nd2;
+ return 0;
+ }
+ if (c1 == 0) {
+ assert(nd1 != 0);
+ assert(nd2 != 0);
+ int are_same = nd1->type() == nd2->type() && nd1->same(nd2);
+ delete nd1;
+ delete nd2;
+ if (!are_same)
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void interpolate_macro(symbol nm, bool do_not_want_next_token)
+{
+ request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
+ if (p == 0) {
+ int warned = 0;
+ const char *s = nm.contents();
+ if (strlen(s) > 2) {
+ request_or_macro *r;
+ char buf[3];
+ buf[0] = s[0];
+ buf[1] = s[1];
+ buf[2] = '\0';
+ r = (request_or_macro *)request_dictionary.lookup(symbol(buf));
+ if (r) {
+ macro *m = r->to_macro();
+ if (!m || !m->empty())
+ warned = warning(WARN_SPACE,
+ "macro '%1' not defined "
+ "(possibly missing space after '%2')",
+ nm.contents(), buf);
+ }
+ }
+ if (!warned) {
+ warning(WARN_MAC, "macro '%1' not defined", nm.contents());
+ p = new macro;
+ request_dictionary.define(nm, p);
+ }
+ }
+ if (p)
+ p->invoke(nm, do_not_want_next_token);
+ else {
+ skip_line();
+ return;
+ }
+}
+
+static void decode_args(macro_iterator *mi)
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ node *n;
+ int c = get_copy(&n);
+ for (;;) {
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '\n' || c == EOF)
+ break;
+ macro arg;
+ int quote_input_level = 0;
+ int done_tab_warning = 0;
+ arg.append(compatible_flag ? PUSH_COMP_MODE : PUSH_GROFF_MODE);
+ // we store discarded double quotes for \$^
+ if (c == '"') {
+ arg.append(DOUBLE_QUOTE);
+ quote_input_level = input_stack::get_level();
+ c = get_copy(&n);
+ }
+ while (c != EOF && c != '\n' && !(c == ' ' && quote_input_level == 0)) {
+ if (quote_input_level > 0 && c == '"'
+ && (compatible_flag
+ || input_stack::get_level() == quote_input_level)) {
+ arg.append(DOUBLE_QUOTE);
+ c = get_copy(&n);
+ if (c == '"') {
+ arg.append(c);
+ c = get_copy(&n);
+ }
+ else
+ break;
+ }
+ else {
+ if (c == 0)
+ arg.append(n);
+ else {
+ if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
+ warning(WARN_TAB, "tab character in unquoted macro argument");
+ done_tab_warning = 1;
+ }
+ arg.append(c);
+ }
+ c = get_copy(&n);
+ }
+ }
+ arg.append(POP_GROFFCOMP_MODE);
+ mi->add_arg(arg, (c == ' '));
+ }
+ }
+}
+
+static void decode_string_args(macro_iterator *mi)
+{
+ node *n;
+ int c = get_copy(&n);
+ for (;;) {
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '\n' || c == EOF) {
+ error("missing ']'");
+ break;
+ }
+ if (c == ']')
+ break;
+ macro arg;
+ int quote_input_level = 0;
+ int done_tab_warning = 0;
+ if (c == '"') {
+ quote_input_level = input_stack::get_level();
+ c = get_copy(&n);
+ }
+ while (c != EOF && c != '\n'
+ && !(c == ']' && quote_input_level == 0)
+ && !(c == ' ' && quote_input_level == 0)) {
+ if (quote_input_level > 0 && c == '"'
+ && input_stack::get_level() == quote_input_level) {
+ c = get_copy(&n);
+ if (c == '"') {
+ arg.append(c);
+ c = get_copy(&n);
+ }
+ else
+ break;
+ }
+ else {
+ if (c == 0)
+ arg.append(n);
+ else {
+ if (c == '\t' && quote_input_level == 0 && !done_tab_warning) {
+ warning(WARN_TAB, "tab character in unquoted string argument");
+ done_tab_warning = 1;
+ }
+ arg.append(c);
+ }
+ c = get_copy(&n);
+ }
+ }
+ mi->add_arg(arg, (c == ' '));
+ }
+}
+
+void macro::invoke(symbol nm, bool do_not_want_next_token)
+{
+ macro_iterator *mi = new macro_iterator(nm, *this);
+ decode_args(mi);
+ input_stack::push(mi);
+ // we must delay tok.next() in case the function has been called by
+ // do_request to assure proper handling of compatible_flag
+ if (!do_not_want_next_token)
+ tok.next();
+}
+
+macro *macro::to_macro()
+{
+ return this;
+}
+
+int macro::empty()
+{
+ return empty_macro == 1;
+}
+
+macro_iterator::macro_iterator(symbol s, macro &m, const char *how_called,
+ int init_args)
+: string_iterator(m, how_called, s), args(0), argc(0), with_break(break_flag)
+{
+ if (init_args) {
+ arg_list *al = input_stack::get_arg_list();
+ if (al) {
+ args = new arg_list(al);
+ argc = input_stack::nargs();
+ }
+ }
+}
+
+macro_iterator::macro_iterator() : args(0), argc(0), with_break(break_flag)
+{
+}
+
+macro_iterator::~macro_iterator()
+{
+ while (args != 0) {
+ arg_list *tem = args;
+ args = args->next;
+ delete tem;
+ }
+}
+
+dictionary composite_dictionary(17);
+
+void composite_request()
+{
+ symbol from = get_name(true /* required */);
+ if (!from.is_null()) {
+ const char *from_gn = glyph_name_to_unicode(from.contents());
+ if (!from_gn) {
+ from_gn = check_unicode_name(from.contents());
+ if (!from_gn) {
+ error("invalid composite glyph name '%1'", from.contents());
+ skip_line();
+ return;
+ }
+ }
+ const char *from_decomposed = decompose_unicode(from_gn);
+ if (from_decomposed)
+ from_gn = &from_decomposed[1];
+ symbol to = get_name(true /* required */);
+ if (to.is_null())
+ composite_dictionary.remove(symbol(from_gn));
+ else {
+ const char *to_gn = glyph_name_to_unicode(to.contents());
+ if (!to_gn) {
+ to_gn = check_unicode_name(to.contents());
+ if (!to_gn) {
+ error("invalid composite glyph name '%1'", to.contents());
+ skip_line();
+ return;
+ }
+ }
+ const char *to_decomposed = decompose_unicode(to_gn);
+ if (to_decomposed)
+ to_gn = &to_decomposed[1];
+ if (strcmp(from_gn, to_gn) == 0)
+ composite_dictionary.remove(symbol(from_gn));
+ else
+ (void)composite_dictionary.lookup(symbol(from_gn), (void *)to_gn);
+ }
+ }
+ skip_line();
+}
+
+static symbol composite_glyph_name(symbol nm)
+{
+ macro_iterator *mi = new macro_iterator();
+ decode_string_args(mi);
+ input_stack::push(mi);
+ const char *gn = glyph_name_to_unicode(nm.contents());
+ if (!gn) {
+ gn = check_unicode_name(nm.contents());
+ if (!gn) {
+ error("invalid base glyph '%1' in composite glyph name", nm.contents());
+ return EMPTY_SYMBOL;
+ }
+ }
+ const char *gn_decomposed = decompose_unicode(gn);
+ string glyph_name(gn_decomposed ? &gn_decomposed[1] : gn);
+ string gl;
+ int n = input_stack::nargs();
+ for (int i = 1; i <= n; i++) {
+ glyph_name += '_';
+ input_iterator *p = input_stack::get_arg(i);
+ gl.clear();
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ gl += c;
+ gl += '\0';
+ const char *u = glyph_name_to_unicode(gl.contents());
+ if (!u) {
+ u = check_unicode_name(gl.contents());
+ if (!u) {
+ error("invalid component '%1' in composite glyph name",
+ gl.contents());
+ return EMPTY_SYMBOL;
+ }
+ }
+ const char *decomposed = decompose_unicode(u);
+ if (decomposed)
+ u = &decomposed[1];
+ void *mapped_composite = composite_dictionary.lookup(symbol(u));
+ if (mapped_composite)
+ u = (const char *)mapped_composite;
+ glyph_name += u;
+ }
+ glyph_name += '\0';
+ const char *groff_gn = unicode_to_glyph_name(glyph_name.contents());
+ if (groff_gn)
+ return symbol(groff_gn);
+ gl.clear();
+ gl += 'u';
+ gl += glyph_name;
+ return symbol(gl.contents());
+}
+
+int trap_sprung_flag = 0;
+int postpone_traps_flag = 0;
+symbol postponed_trap;
+
+void spring_trap(symbol nm)
+{
+ assert(!nm.is_null());
+ trap_sprung_flag = 1;
+ if (postpone_traps_flag) {
+ postponed_trap = nm;
+ return;
+ }
+ static char buf[2] = { BEGIN_TRAP, '\0' };
+ static char buf2[2] = { END_TRAP, '\0' };
+ input_stack::push(make_temp_iterator(buf2));
+ request_or_macro *p = lookup_request(nm);
+ // We don't perform this validation at the time the trap is planted
+ // because a request name might be replaced by a macro by the time the
+ // trap springs.
+ macro *m = p->to_macro();
+ if (m)
+ input_stack::push(new macro_iterator(nm, *m, "trap-called macro"));
+ else
+ error("trap failed to spring: '%1' is a request", nm.contents());
+ input_stack::push(make_temp_iterator(buf));
+}
+
+void postpone_traps()
+{
+ postpone_traps_flag = 1;
+}
+
+int unpostpone_traps()
+{
+ postpone_traps_flag = 0;
+ if (!postponed_trap.is_null()) {
+ spring_trap(postponed_trap);
+ postponed_trap = NULL_SYMBOL;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void read_request()
+{
+ macro_iterator *mi = new macro_iterator;
+ int reading_from_terminal = isatty(fileno(stdin));
+ int had_prompt = 0;
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c = get_copy(0);
+ while (c == ' ')
+ c = get_copy(0);
+ while (c != EOF && c != '\n' && c != ' ') {
+ if (!is_invalid_input_char(c)) {
+ if (reading_from_terminal)
+ fputc(c, stderr);
+ had_prompt = 1;
+ }
+ c = get_copy(0);
+ }
+ if (c == ' ') {
+ tok.make_space();
+ decode_args(mi);
+ }
+ }
+ if (reading_from_terminal) {
+ fputc(had_prompt ? ':' : '\a', stderr);
+ fflush(stderr);
+ }
+ input_stack::push(mi);
+ macro mac;
+ int nl = 0;
+ int c;
+ while ((c = getchar()) != EOF) {
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ if (c == '\n') {
+ if (nl)
+ break;
+ else
+ nl = 1;
+ }
+ else
+ nl = 0;
+ mac.append(c);
+ }
+ }
+ if (reading_from_terminal)
+ clearerr(stdin);
+ input_stack::push(new string_iterator(mac));
+ tok.next();
+}
+
+enum define_mode { DEFINE_NORMAL, DEFINE_APPEND, DEFINE_IGNORE };
+enum calling_mode { CALLING_NORMAL, CALLING_INDIRECT };
+enum comp_mode { COMP_IGNORE, COMP_DISABLE, COMP_ENABLE };
+
+void do_define_string(define_mode mode, comp_mode comp)
+{
+ symbol nm;
+ node *n = 0; // pacify compiler
+ int c;
+ nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad string definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ macro mac;
+ request_or_macro *rm = (request_or_macro *)request_dictionary.lookup(nm);
+ macro *mm = rm ? rm->to_macro() : 0;
+ if (mode == DEFINE_APPEND && mm)
+ mac = *mm;
+ if (comp == COMP_DISABLE)
+ mac.append(PUSH_GROFF_MODE);
+ else if (comp == COMP_ENABLE)
+ mac.append(PUSH_COMP_MODE);
+ while (c != '\n' && c != EOF) {
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append((unsigned char)c);
+ c = get_copy(&n);
+ }
+ if (comp == COMP_DISABLE || comp == COMP_ENABLE)
+ mac.append(POP_GROFFCOMP_MODE);
+ if (!mm) {
+ mm = new macro;
+ request_dictionary.define(nm, mm);
+ }
+ *mm = mac;
+ tok.next();
+}
+
+void define_string()
+{
+ do_define_string(DEFINE_NORMAL,
+ compatible_flag ? COMP_ENABLE: COMP_IGNORE);
+}
+
+void define_nocomp_string()
+{
+ do_define_string(DEFINE_NORMAL, COMP_DISABLE);
+}
+
+void append_string()
+{
+ do_define_string(DEFINE_APPEND,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_nocomp_string()
+{
+ do_define_string(DEFINE_APPEND, COMP_DISABLE);
+}
+
+void do_define_character(char_mode mode, const char *font_name)
+{
+ node *n = 0; // pacify compiler
+ int c;
+ tok.skip();
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_line();
+ return;
+ }
+ if (font_name) {
+ string s(font_name);
+ s += ' ';
+ s += ci->nm.contents();
+ s += '\0';
+ ci = get_charinfo(symbol(s.contents()));
+ }
+ tok.next();
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad character definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ' || c == '\t')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ macro *m = new macro;
+ while (c != '\n' && c != EOF) {
+ if (c == 0)
+ m->append(n);
+ else
+ m->append((unsigned char)c);
+ c = get_copy(&n);
+ }
+ m = ci->setx_macro(m, mode);
+ if (m)
+ delete m;
+ tok.next();
+}
+
+void define_character()
+{
+ do_define_character(CHAR_NORMAL);
+}
+
+void define_fallback_character()
+{
+ do_define_character(CHAR_FALLBACK);
+}
+
+void define_special_character()
+{
+ do_define_character(CHAR_SPECIAL);
+}
+
+static void remove_character()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (!tok.is_space() && !tok.is_tab()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (!ci)
+ break;
+ macro *m = ci->set_macro(0);
+ if (m)
+ delete m;
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+static void interpolate_string(symbol nm)
+{
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot interpolate request '%1'", nm.contents());
+ else {
+ if (m->is_string()) {
+ string_iterator *si = new string_iterator(*m, "string", nm);
+ input_stack::push(si);
+ }
+ else {
+ // if a macro is called as a string, \$0 doesn't get changed
+ macro_iterator *mi = new macro_iterator(input_stack::get_macro_name(),
+ *m, "string", 1);
+ input_stack::push(mi);
+ }
+ }
+}
+
+static void interpolate_string_with_args(symbol nm)
+{
+ request_or_macro *p = lookup_request(nm);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot interpolate request '%1'", nm.contents());
+ else {
+ macro_iterator *mi = new macro_iterator(nm, *m);
+ decode_string_args(mi);
+ input_stack::push(mi);
+ }
+}
+
+static void interpolate_arg(symbol nm)
+{
+ const char *s = nm.contents();
+ if (!s || *s == '\0')
+ copy_mode_error("missing positional argument number");
+ else if (s[1] == 0 && csdigit(s[0]))
+ input_stack::push(input_stack::get_arg(s[0] - '0'));
+ else if (s[0] == '*' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ for (int i = 1; i <= limit; i++) {
+ input_iterator *p = input_stack::get_arg(i);
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ args += c;
+ if (i != limit)
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else if (s[0] == '@' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ for (int i = 1; i <= limit; i++) {
+ args += '"';
+ args += char(BEGIN_QUOTE);
+ input_iterator *p = input_stack::get_arg(i);
+ int c;
+ while ((c = p->get(0)) != EOF)
+ if (c != DOUBLE_QUOTE)
+ args += c;
+ args += char(END_QUOTE);
+ args += '"';
+ if (i != limit)
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else if (s[0] == '^' && s[1] == '\0') {
+ int limit = input_stack::nargs();
+ string args;
+ int c = input_stack::peek();
+ for (int i = 1; i <= limit; i++) {
+ input_iterator *p = input_stack::get_arg(i);
+ while ((c = p->get(0)) != EOF) {
+ if (c == DOUBLE_QUOTE)
+ c = '"';
+ args += c;
+ }
+ if (input_stack::space_follows_arg(i))
+ args += ' ';
+ delete p;
+ }
+ if (limit > 0) {
+ args += '\0';
+ input_stack::push(make_temp_iterator(args.contents()));
+ }
+ }
+ else {
+ const char *p;
+ for (p = s; *p && csdigit(*p); p++)
+ ;
+ if (*p)
+ copy_mode_error("invalid positional argument number '%1'", s);
+ else
+ input_stack::push(input_stack::get_arg(atoi(s)));
+ }
+}
+
+void handle_first_page_transition()
+{
+ push_token(tok);
+ topdiv->begin_page();
+}
+
+// We push back a token by wrapping it up in a token_node, and
+// wrapping that up in a string_iterator.
+
+static void push_token(const token &t)
+{
+ macro m;
+ m.append(new token_node(t));
+ input_stack::push(new string_iterator(m));
+}
+
+void push_page_ejector()
+{
+ static char buf[2] = { PAGE_EJECTOR, '\0' };
+ input_stack::push(make_temp_iterator(buf));
+}
+
+void handle_initial_request(unsigned char code)
+{
+ char buf[2];
+ buf[0] = code;
+ buf[1] = '\0';
+ macro mac;
+ mac.append(new token_node(tok));
+ input_stack::push(new string_iterator(mac));
+ input_stack::push(make_temp_iterator(buf));
+ topdiv->begin_page();
+ tok.next();
+}
+
+void handle_initial_title()
+{
+ handle_initial_request(TITLE_REQUEST);
+}
+
+void do_define_macro(define_mode mode, calling_mode calling, comp_mode comp)
+{
+ symbol nm, term, dot_symbol(".");
+ if (calling == CALLING_INDIRECT) {
+ symbol temp1 = get_name(true /* required */);
+ if (temp1.is_null()) {
+ skip_line();
+ return;
+ }
+ symbol temp2 = get_name();
+ input_stack::push(make_temp_iterator("\n"));
+ if (!temp2.is_null()) {
+ interpolate_string(temp2);
+ input_stack::push(make_temp_iterator(" "));
+ }
+ interpolate_string(temp1);
+ input_stack::push(make_temp_iterator(" "));
+ tok.next();
+ }
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ }
+ term = get_name(); // the request that terminates the definition
+ if (term.is_null())
+ term = dot_symbol;
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ const char *start_filename;
+ int start_lineno;
+ int have_start_location = input_stack::get_location(0, &start_filename,
+ &start_lineno);
+ node *n;
+ // doing this here makes the line numbers come out right
+ int c = get_copy(&n, true /* is defining*/);
+ macro mac;
+ macro *mm = 0;
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ request_or_macro *rm =
+ (request_or_macro *)request_dictionary.lookup(nm);
+ if (rm)
+ mm = rm->to_macro();
+ if (mm && mode == DEFINE_APPEND)
+ mac = *mm;
+ }
+ int bol = 1;
+ if (comp == COMP_DISABLE)
+ mac.append(PUSH_GROFF_MODE);
+ else if (comp == COMP_ENABLE)
+ mac.append(PUSH_COMP_MODE);
+ for (;;) {
+ if (c == '\n')
+ mac.clear_string_flag();
+ while (c == ESCAPE_NEWLINE) {
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND)
+ mac.append(c);
+ c = get_copy(&n, true /* is defining */);
+ }
+ if (bol && c == '.') {
+ const char *s = term.contents();
+ int d = 0;
+ // see if it matches term
+ int i = 0;
+ if (s[0] != 0) {
+ while ((d = get_copy(&n)) == ' ' || d == '\t')
+ ;
+ if ((unsigned char)s[0] == d) {
+ for (i = 1; s[i] != 0; i++) {
+ d = get_copy(&n);
+ if ((unsigned char)s[i] != d)
+ break;
+ }
+ }
+ }
+ if (s[i] == 0
+ && ((i == 2 && compatible_flag)
+ || (d = get_copy(&n)) == ' '
+ || d == '\n')) { // we found it
+ if (d == '\n')
+ tok.make_newline();
+ else
+ tok.make_space();
+ if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
+ if (!mm) {
+ mm = new macro;
+ request_dictionary.define(nm, mm);
+ }
+ if (comp == COMP_DISABLE || comp == COMP_ENABLE)
+ mac.append(POP_GROFFCOMP_MODE);
+ *mm = mac;
+ }
+ if (term != dot_symbol) {
+ ignoring = 0;
+ interpolate_macro(term);
+ }
+ else
+ skip_line();
+ return;
+ }
+ if (mode == DEFINE_APPEND || mode == DEFINE_NORMAL) {
+ mac.append(c);
+ for (int j = 0; j < i; j++)
+ mac.append(s[j]);
+ }
+ c = d;
+ }
+ if (c == EOF) {
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ if (have_start_location)
+ error_with_file_and_line(start_filename, start_lineno,
+ "end of file while defining macro '%1'",
+ nm.contents());
+ else
+ error("end of file while defining macro '%1'", nm.contents());
+ }
+ else {
+ if (have_start_location)
+ error_with_file_and_line(start_filename, start_lineno,
+ "end of file while ignoring input lines");
+ else
+ error("end of file while ignoring input lines");
+ }
+ tok.next();
+ return;
+ }
+ if (mode == DEFINE_NORMAL || mode == DEFINE_APPEND) {
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append(c);
+ }
+ bol = (c == '\n');
+ c = get_copy(&n, true /* is defining */);
+ }
+}
+
+void define_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_NORMAL,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void define_nocomp_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_NORMAL, COMP_DISABLE);
+}
+
+void define_indirect_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void define_indirect_nocomp_macro()
+{
+ do_define_macro(DEFINE_NORMAL, CALLING_INDIRECT, COMP_DISABLE);
+}
+
+void append_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_NORMAL,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_nocomp_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_NORMAL, COMP_DISABLE);
+}
+
+void append_indirect_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_INDIRECT,
+ compatible_flag ? COMP_ENABLE : COMP_IGNORE);
+}
+
+void append_indirect_nocomp_macro()
+{
+ do_define_macro(DEFINE_APPEND, CALLING_INDIRECT, COMP_DISABLE);
+}
+
+void ignore()
+{
+ ignoring = 1;
+ do_define_macro(DEFINE_IGNORE, CALLING_NORMAL, COMP_IGNORE);
+ ignoring = 0;
+}
+
+void remove_macro()
+{
+ for (;;) {
+ symbol s = get_name();
+ if (s.is_null())
+ break;
+ request_dictionary.remove(s);
+ }
+ skip_line();
+}
+
+void rename_macro()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null())
+ request_dictionary.rename(s1, s2);
+ }
+ skip_line();
+}
+
+void alias_macro()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null()) {
+ if (!request_dictionary.alias(s1, s2))
+ warning(WARN_MAC, "macro '%1' not defined", s2.contents());
+ }
+ }
+ skip_line();
+}
+
+void chop_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot chop request");
+ else if (m->empty())
+ error("cannot chop empty macro");
+ else {
+ int have_restore = 0;
+ // we have to check for additional save/restore pairs which could be
+ // there due to empty am1 requests.
+ for (;;) {
+ if (m->get(m->len - 1) != POP_GROFFCOMP_MODE)
+ break;
+ have_restore = 1;
+ m->len -= 1;
+ if (m->get(m->len - 1) != PUSH_GROFF_MODE
+ && m->get(m->len - 1) != PUSH_COMP_MODE)
+ break;
+ have_restore = 0;
+ m->len -= 1;
+ if (m->len == 0)
+ break;
+ }
+ if (m->len == 0)
+ error("cannot chop empty macro");
+ else {
+ if (have_restore)
+ m->set(POP_GROFFCOMP_MODE, m->len - 1);
+ else
+ m->len -= 1;
+ }
+ }
+ }
+ skip_line();
+}
+
+enum case_xform_mode { STRING_UPCASE, STRING_DOWNCASE };
+
+// Case-transform each byte of the string argument's contents.
+void do_string_case_transform(case_xform_mode mode)
+{
+ assert((mode == STRING_DOWNCASE) || (mode == STRING_UPCASE));
+ symbol s = get_name(true /* required */);
+ if (s.is_null()) {
+ skip_line();
+ return;
+ }
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m) {
+ error("cannot apply string case transformation to a request ('%1')",
+ s.contents());
+ skip_line();
+ return;
+ }
+ string_iterator iter1(*m);
+ macro *mac = new macro;
+ int len = m->macro::length();
+ for (int l = 0; l < len; l++) {
+ int nc, c = iter1.get(0);
+ if (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ nc = c;
+ else if (c == EOF)
+ break;
+ else
+ if (mode == STRING_DOWNCASE)
+ nc = tolower(c);
+ else
+ nc = toupper(c);
+ mac->append(nc);
+ }
+ request_dictionary.define(s, mac);
+ tok.next();
+}
+
+// Uppercase-transform each byte of the string argument's contents.
+void stringdown_request() {
+ do_string_case_transform(STRING_DOWNCASE);
+}
+
+// Lowercase-transform each byte of the string argument's contents.
+void stringup_request() {
+ do_string_case_transform(STRING_UPCASE);
+}
+
+void substring_request()
+{
+ int start; // 0, 1, ..., n-1 or -1, -2, ...
+ symbol s = get_name(true /* required */);
+ if (!s.is_null() && get_integer(&start)) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot apply 'substring' on a request");
+ else {
+ int end = -1;
+ if (!has_arg() || get_integer(&end)) {
+ int real_length = 0; // 1, 2, ..., n
+ string_iterator iter1(*m);
+ for (int l = 0; l < m->len; l++) {
+ int c = iter1.get(0);
+ if (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ continue;
+ if (c == EOF)
+ break;
+ real_length++;
+ }
+ if (start < 0)
+ start += real_length;
+ if (end < 0)
+ end += real_length;
+ if (start > end) {
+ int tem = start;
+ start = end;
+ end = tem;
+ }
+ if (start >= real_length || end < 0) {
+ warning(WARN_RANGE,
+ "start and end index of substring out of range");
+ m->len = 0;
+ if (m->p) {
+ if (--(m->p->count) <= 0)
+ delete m->p;
+ m->p = 0;
+ }
+ skip_line();
+ return;
+ }
+ if (start < 0) {
+ warning(WARN_RANGE,
+ "start index of substring out of range, set to 0");
+ start = 0;
+ }
+ if (end >= real_length) {
+ warning(WARN_RANGE,
+ "end index of substring out of range, set to string length");
+ end = real_length - 1;
+ }
+ // now extract the substring
+ string_iterator iter(*m);
+ int i;
+ for (i = 0; i < start; i++) {
+ int c = iter.get(0);
+ while (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ c = iter.get(0);
+ if (c == EOF)
+ break;
+ }
+ macro mac;
+ for (; i <= end; i++) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ while (c == PUSH_GROFF_MODE
+ || c == PUSH_COMP_MODE
+ || c == POP_GROFFCOMP_MODE)
+ c = iter.get(0);
+ if (c == EOF)
+ break;
+ if (c == 0)
+ mac.append(nd);
+ else
+ mac.append((unsigned char)c);
+ }
+ *m = mac;
+ }
+ }
+ }
+ skip_line();
+}
+
+void length_request()
+{
+ symbol ret;
+ ret = get_name(true /* required */);
+ if (ret.is_null()) {
+ skip_line();
+ return;
+ }
+ int c;
+ node *n;
+ if (tok.is_newline())
+ c = '\n';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (!tok.is_space()) {
+ error("bad string definition");
+ skip_line();
+ return;
+ }
+ else
+ c = get_copy(&n);
+ while (c == ' ')
+ c = get_copy(&n);
+ if (c == '"')
+ c = get_copy(&n);
+ int len = 0;
+ while (c != '\n' && c != EOF) {
+ ++len;
+ c = get_copy(&n);
+ }
+ reg *r = (reg*)register_dictionary.lookup(ret);
+ if (r)
+ r->set_value(len);
+ else
+ set_number_reg(ret, len);
+ tok.next();
+}
+
+void asciify_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot asciify request");
+ else {
+ macro am;
+ string_iterator iter(*m);
+ for (;;) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ if (c == EOF)
+ break;
+ if (c != 0)
+ am.append(c);
+ else
+ nd->asciify(&am);
+ }
+ *m = am;
+ }
+ }
+ skip_line();
+}
+
+void unformat_macro()
+{
+ symbol s = get_name(true /* required */);
+ if (!s.is_null()) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot unformat request");
+ else {
+ macro am;
+ string_iterator iter(*m);
+ for (;;) {
+ node *nd = 0; // pacify compiler
+ int c = iter.get(&nd);
+ if (c == EOF)
+ break;
+ if (c != 0)
+ am.append(c);
+ else {
+ if (nd->set_unformat_flag())
+ am.append(nd);
+ }
+ }
+ *m = am;
+ }
+ }
+ skip_line();
+}
+
+static void interpolate_environment_variable(symbol nm)
+{
+ const char *s = getenv(nm.contents());
+ if (s && *s)
+ input_stack::push(make_temp_iterator(s));
+}
+
+void interpolate_number_reg(symbol nm, int inc)
+{
+ reg *r = lookup_number_reg(nm);
+ if (inc < 0)
+ r->decrement();
+ else if (inc > 0)
+ r->increment();
+ input_stack::push(make_temp_iterator(r->get_string()));
+}
+
+static void interpolate_number_format(symbol nm)
+{
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r)
+ input_stack::push(make_temp_iterator(r->get_format()));
+}
+
+static int get_delim_number(units *n, unsigned char si, int prev_value)
+{
+ token start;
+ start.next();
+ if (start.usable_as_delimiter(true /* report error */)) {
+ tok.next();
+ if (get_number(n, si, prev_value)) {
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int get_delim_number(units *n, unsigned char si)
+{
+ token start;
+ start.next();
+ if (start.usable_as_delimiter(true /* report error */)) {
+ tok.next();
+ if (get_number(n, si)) {
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int get_line_arg(units *n, unsigned char si, charinfo **cp)
+{
+ token start;
+ start.next();
+ int start_level = input_stack::get_level();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return 0;
+ tok.next();
+ if (get_number(n, si)) {
+ if (tok.is_dummy() || tok.is_transparent_dummy())
+ tok.next();
+ if (!(start == tok && input_stack::get_level() == start_level)) {
+ *cp = tok.get_char(true /* required */);
+ tok.next();
+ }
+ if (!(start == tok && input_stack::get_level() == start_level))
+ warning(WARN_DELIM, "closing delimiter does not match");
+ return 1;
+ }
+ return 0;
+}
+
+static bool read_size(int *x)
+{
+ tok.next();
+ int c = tok.ch();
+ int inc = 0;
+ if (c == '-') {
+ inc = -1;
+ tok.next();
+ c = tok.ch();
+ }
+ else if (c == '+') {
+ inc = 1;
+ tok.next();
+ c = tok.ch();
+ }
+ int val = 0; // pacify compiler
+ bool contains_invalid_digit = false;
+ if (c == '(') {
+ tok.next();
+ c = tok.ch();
+ if (!inc) {
+ // allow an increment either before or after the left parenthesis
+ if (c == '-') {
+ inc = -1;
+ tok.next();
+ c = tok.ch();
+ }
+ else if (c == '+') {
+ inc = 1;
+ tok.next();
+ c = tok.ch();
+ }
+ }
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = c - '0';
+ tok.next();
+ c = tok.ch();
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = val*10 + (c - '0');
+ val *= sizescale;
+ }
+ }
+ }
+ else if (csdigit(c)) {
+ val = c - '0';
+ if (compatible_flag && !inc && c != '0' && c < '4') {
+ // Support legacy \sNN syntax.
+ tok.next();
+ c = tok.ch();
+ if (!csdigit(c))
+ contains_invalid_digit = true;
+ else {
+ val = val*10 + (c - '0');
+ error("ambiguous type size in escape sequence; rewrite to use"
+ " '%1s(%2' or similar", static_cast<char>(escape_char),
+ val);
+ }
+ }
+ val *= sizescale;
+ }
+ else if (!tok.usable_as_delimiter(true /* report error */))
+ return false;
+ else {
+ token start(tok);
+ tok.next();
+ c = tok.ch();
+ if (!inc && (c == '-' || c == '+')) {
+ inc = c == '+' ? 1 : -1;
+ tok.next();
+ }
+ if (!get_number(&val, 'z'))
+ return false;
+ if (!(start.ch() == '[' && tok.ch() == ']') && start != tok) {
+ if (start.ch() == '[')
+ error("missing ']' in type size escape sequence");
+ else
+ error("missing closing delimiter in type size escape sequence");
+ return false;
+ }
+ }
+ if (contains_invalid_digit) {
+ if (c)
+ error("expected valid digit in type size escape sequence, got %1",
+ input_char_description(c));
+ else
+ error("invalid digit in type size escape sequence");
+ return false;
+ }
+ else {
+ switch (inc) {
+ case 0:
+ if (val == 0) {
+ // special case -- point size 0 means "revert to previous size"
+ *x = 0;
+ return true;
+ }
+ *x = val;
+ break;
+ case 1:
+ *x = curenv->get_requested_point_size() + val;
+ break;
+ case -1:
+ *x = curenv->get_requested_point_size() - val;
+ break;
+ default:
+ assert(0);
+ }
+ if (*x <= 0) {
+ warning(WARN_RANGE,
+ "type size escape sequence results in non-positive size"
+ " %1u; setting it to 1u", *x);
+ *x = 1;
+ }
+ return true;
+ }
+}
+
+static symbol get_delim_name()
+{
+ token start;
+ start.next();
+ if (start.is_eof()) {
+ error("end of input at start of delimited name");
+ return NULL_SYMBOL;
+ }
+ if (start.is_newline()) {
+ error("can't delimit name with a newline");
+ return NULL_SYMBOL;
+ }
+ int start_level = input_stack::get_level();
+ char abuf[ABUF_SIZE];
+ char *buf = abuf;
+ int buf_size = ABUF_SIZE;
+ int i = 0;
+ for (;;) {
+ if (i + 1 > buf_size) {
+ if (buf == abuf) {
+ buf = new char[ABUF_SIZE*2];
+ memcpy(buf, abuf, buf_size);
+ buf_size = ABUF_SIZE*2;
+ }
+ else {
+ char *old_buf = buf;
+ buf = new char[buf_size*2];
+ memcpy(buf, old_buf, buf_size);
+ buf_size *= 2;
+ delete[] old_buf;
+ }
+ }
+ tok.next();
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ if ((buf[i] = tok.ch()) == 0) {
+ error("missing delimiter (got %1)", tok.description());
+ if (buf != abuf)
+ delete[] buf;
+ return NULL_SYMBOL;
+ }
+ i++;
+ }
+ buf[i] = '\0';
+ if (buf == abuf) {
+ if (i == 0) {
+ error("empty delimited name");
+ return NULL_SYMBOL;
+ }
+ else
+ return symbol(buf);
+ }
+ else {
+ symbol s(buf);
+ delete[] buf;
+ return s;
+ }
+}
+
+// Implement \R
+
+static void do_register()
+{
+ token start;
+ start.next();
+ if (!start.usable_as_delimiter(true /* report error */))
+ return;
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ return;
+ while (tok.is_space())
+ tok.next();
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ int prev_value;
+ if (!r || !r->get_value(&prev_value))
+ prev_value = 0;
+ int val;
+ if (!get_number(&val, 'u', prev_value))
+ return;
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ if (r)
+ r->set_value(val);
+ else
+ set_number_reg(nm, val);
+}
+
+// this implements the \w escape sequence
+
+static void do_width()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ environment env(curenv);
+ environment *oldenv = curenv;
+ curenv = &env;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ if (tok != start)
+ warning(WARN_DELIM, "missing closing delimiter in"
+ " width computation escape sequence (got %1)",
+ tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ tok.process();
+ }
+ env.wrap_up_tab();
+ units x = env.get_input_line_position().to_units();
+ input_stack::push(make_temp_iterator(i_to_a(x)));
+ env.width_registers();
+ curenv = oldenv;
+ have_input = 0;
+}
+
+charinfo *page_character;
+
+void set_page_character()
+{
+ page_character = get_optional_char();
+ skip_line();
+}
+
+static const symbol percent_symbol("%");
+
+void read_title_parts(node **part, hunits *part_width)
+{
+ tok.skip();
+ if (tok.is_newline() || tok.is_eof())
+ return;
+ token start(tok);
+ int start_level = input_stack::get_level();
+ tok.next();
+ for (int i = 0; i < 3; i++) {
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level)) {
+ tok.next();
+ break;
+ }
+ if (page_character != 0 && tok.get_char() == page_character)
+ interpolate_number_reg(percent_symbol, 0);
+ else
+ tok.process();
+ tok.next();
+ }
+ curenv->wrap_up_tab();
+ part_width[i] = curenv->get_input_line_position();
+ part[i] = curenv->extract_output_line();
+ }
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+}
+
+class non_interpreted_node : public node {
+ macro mac;
+public:
+ non_interpreted_node(const macro &);
+ int interpret(macro *);
+ node *copy();
+ int ends_sentence();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+non_interpreted_node::non_interpreted_node(const macro &m) : mac(m)
+{
+}
+
+int non_interpreted_node::ends_sentence()
+{
+ return 2;
+}
+
+int non_interpreted_node::same(node *nd)
+{
+ return mac == ((non_interpreted_node *)nd)->mac;
+}
+
+const char *non_interpreted_node::type()
+{
+ return "non_interpreted_node";
+}
+
+int non_interpreted_node::force_tprint()
+{
+ return 0;
+}
+
+int non_interpreted_node::is_tag()
+{
+ return 0;
+}
+
+node *non_interpreted_node::copy()
+{
+ return new non_interpreted_node(mac);
+}
+
+int non_interpreted_node::interpret(macro *m)
+{
+ string_iterator si(mac);
+ node *n = 0; // pacify compiler
+ for (;;) {
+ int c = si.get(&n);
+ if (c == EOF)
+ break;
+ if (c == 0)
+ m->append(n);
+ else
+ m->append(c);
+ }
+ return 1;
+}
+
+static node *do_non_interpreted()
+{
+ node *n;
+ int c;
+ macro mac;
+ while ((c = get_copy(&n)) != ESCAPE_QUESTION && c != EOF && c != '\n')
+ if (c == 0)
+ mac.append(n);
+ else
+ mac.append(c);
+ if (c == EOF || c == '\n') {
+ error("unterminated transparent embedding escape sequence");
+ return 0;
+ }
+ return new non_interpreted_node(mac);
+}
+
+static void encode_char(macro *mac, char c)
+{
+ if (c == '\0') {
+ if (tok.is_stretchable_space()
+ || tok.is_unstretchable_space())
+ mac->append(' ');
+ else if (tok.is_special()) {
+ const char *sc;
+ if (font::use_charnames_in_special) {
+ charinfo *ci = tok.get_char(true /* required */);
+ sc = ci->get_symbol()->contents();
+ }
+ else
+ sc = tok.get_char()->get_symbol()->contents();
+ if (strcmp("-", sc) == 0)
+ mac->append('-');
+ else if (strcmp("aq", sc) == 0)
+ mac->append('\'');
+ else if (strcmp("dq", sc) == 0)
+ mac->append('"');
+ else if (strcmp("ga", sc) == 0)
+ mac->append('`');
+ else if (strcmp("ha", sc) == 0)
+ mac->append('^');
+ else if (strcmp("rs", sc) == 0)
+ mac->append('\\');
+ else if (strcmp("ti", sc) == 0)
+ mac->append('~');
+ else {
+ if (font::use_charnames_in_special) {
+ if (sc[0] != (char)0) {
+ mac->append('\\');
+ mac->append('[');
+ int i = 0;
+ while (sc[i] != (char)0) {
+ mac->append(sc[i]);
+ i++;
+ }
+ mac->append(']');
+ }
+ else
+ error("special character '%1' cannot be used within"
+ " device control escape sequence", sc);
+ }
+ }
+ }
+ else if (!(tok.is_hyphen_indicator()
+ || tok.is_dummy()
+ || tok.is_transparent_dummy()
+ || tok.is_zero_width_break()))
+ error("%1 is invalid within device control escape sequence",
+ tok.description());
+ }
+ else {
+ if ((font::use_charnames_in_special) && (c == '\\')) {
+ /*
+ * add escape escape sequence
+ */
+ mac->append(c);
+ }
+ mac->append(c);
+ }
+}
+
+static node *do_special()
+{
+ int start_level = input_stack::get_level();
+ token start;
+ start.next();
+ macro mac;
+ for (;;) {
+ tok.next();
+ if (tok.is_newline()) {
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in device control"
+ " escape sequence (got %1)", tok.description());
+ // Synthesize an input line ending.
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ if (tok == start
+ && (compatible_flag || input_stack::get_level() == start_level))
+ break;
+ unsigned char c;
+ if (tok.is_space())
+ c = ' ';
+ else if (tok.is_tab())
+ c = '\t';
+ else if (tok.is_leader())
+ c = '\001';
+ else if (tok.is_backspace())
+ c = '\b';
+ else
+ c = tok.ch();
+ encode_char(&mac, c);
+ }
+ return new special_node(mac);
+}
+
+void device_request()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ macro mac;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ mac.append(c);
+ curenv->add_node(new special_node(mac));
+ }
+ tok.next();
+}
+
+void device_macro_request()
+{
+ symbol s = get_name(true /* required */);
+ if (!(s.is_null() || s.is_empty())) {
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (m)
+ curenv->add_node(new special_node(*m));
+ else
+ error("can't transparently throughput a request");
+ }
+ skip_line();
+}
+
+void output_request()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ topdiv->transparent_output(c);
+ topdiv->transparent_output('\n');
+ }
+ tok.next();
+}
+
+extern int image_no; // from node.cpp
+
+static node *do_suppress(symbol nm)
+{
+ if (nm.is_null() || nm.is_empty()) {
+ error("output suppression escape sequence requires an argument");
+ return 0;
+ }
+ const char *s = nm.contents();
+ switch (*s) {
+ case '0':
+ if (begin_level == 0)
+ // suppress generation of glyphs
+ return new suppress_node(0, 0);
+ break;
+ case '1':
+ if (begin_level == 0)
+ // enable generation of glyphs
+ return new suppress_node(1, 0);
+ break;
+ case '2':
+ if (begin_level == 0)
+ return new suppress_node(1, 1);
+ break;
+ case '3':
+ have_input = 1;
+ begin_level++;
+ break;
+ case '4':
+ have_input = 1;
+ begin_level--;
+ break;
+ case '5':
+ {
+ s++; // move over '5'
+ char position = *s;
+ if (*s == (char)0) {
+ error("missing position and filename in output suppression"
+ " escape sequence");
+ return 0;
+ }
+ if (!(position == 'l'
+ || position == 'r'
+ || position == 'c'
+ || position == 'i')) {
+ error("expected position 'l', 'r', 'c', or 'i' in output"
+ " suppression escape sequence, got '%1'", position);
+ return 0;
+ }
+ s++; // onto image name
+ if (s == (char *)0) {
+ error("missing image name in output suppression escape"
+ " sequence");
+ return 0;
+ }
+ image_no++;
+ if (begin_level == 0)
+ return new suppress_node(symbol(s), position, image_no);
+ else
+ have_input = 1;
+ }
+ break;
+ default:
+ error("invalid argument '%1' to output suppression escape sequence",
+ *s);
+ }
+ return 0;
+}
+
+void special_node::tprint(troff_output_file *out)
+{
+ tprint_start(out);
+ string_iterator iter(mac);
+ for (;;) {
+ int c = iter.get(0);
+ if (c == EOF)
+ break;
+ for (const char *s = ::asciify(c); *s; s++)
+ tprint_char(out, *s);
+ }
+ tprint_end(out);
+}
+
+int get_file_line(const char **filename, int *lineno)
+{
+ return input_stack::get_location(0, filename, lineno);
+}
+
+void line_file()
+{
+ int n;
+ if (get_integer(&n)) {
+ const char *filename = 0;
+ if (has_arg()) {
+ symbol s = get_long_name();
+ filename = s.contents();
+ }
+ (void)input_stack::set_location(filename, n-1);
+ }
+ skip_line();
+}
+
+static int nroff_mode = 0;
+
+static void nroff_request()
+{
+ nroff_mode = 1;
+ skip_line();
+}
+
+static void troff_request()
+{
+ nroff_mode = 0;
+ skip_line();
+}
+
+static void skip_alternative()
+{
+ int level = 0;
+ // ensure that ".if 0\{" works as expected
+ if (tok.is_left_brace())
+ level++;
+ int c;
+ for (;;) {
+ c = input_stack::get(0);
+ if (c == EOF)
+ break;
+ if (c == ESCAPE_LEFT_BRACE)
+ ++level;
+ else if (c == ESCAPE_RIGHT_BRACE)
+ --level;
+ else if (c == escape_char && escape_char > 0)
+ switch(input_stack::get(0)) {
+ case '{':
+ ++level;
+ break;
+ case '}':
+ --level;
+ break;
+ case '"':
+ while ((c = input_stack::get(0)) != '\n' && c != EOF)
+ ;
+ }
+ /*
+ Note that the level can properly be < 0, e.g.
+
+ .if 1 \{\
+ .if 0 \{\
+ .\}\}
+
+ So don't give an error message in this case.
+ */
+ if (level <= 0 && c == '\n')
+ break;
+ }
+ tok.next();
+}
+
+static void begin_alternative()
+{
+ while (tok.is_space() || tok.is_left_brace())
+ tok.next();
+}
+
+void nop_request()
+{
+ while (tok.is_space())
+ tok.next();
+}
+
+static int_stack if_else_stack;
+
+int do_if_request()
+{
+ int invert = 0;
+ while (tok.is_space())
+ tok.next();
+ while (tok.ch() == '!') {
+ tok.next();
+ invert = !invert;
+ }
+ int result;
+ unsigned char c = tok.ch();
+ if (compatible_flag)
+ switch (c) {
+ case 'F':
+ case 'S':
+ case 'c':
+ case 'd':
+ case 'm':
+ case 'r':
+ warning(WARN_SYNTAX,
+ "conditional operator '%1' used in compatibility mode",
+ c);
+ break;
+ default:
+ break;
+ }
+ if (c == 't') {
+ tok.next();
+ result = !nroff_mode;
+ }
+ else if (c == 'n') {
+ tok.next();
+ result = nroff_mode;
+ }
+ else if (c == 'v') {
+ tok.next();
+ result = 0;
+ }
+ else if (c == 'o') {
+ result = (topdiv->get_page_number() & 1);
+ tok.next();
+ }
+ else if (c == 'e') {
+ result = !(topdiv->get_page_number() & 1);
+ tok.next();
+ }
+ else if (c == 'd' || c == 'r') {
+ tok.next();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = (c == 'd'
+ ? request_dictionary.lookup(nm) != 0
+ : register_dictionary.lookup(nm) != 0);
+ }
+ else if (c == 'm') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = (nm == default_symbol
+ || color_dictionary.lookup(nm) != 0);
+ }
+ else if (c == 'c') {
+ tok.next();
+ tok.skip();
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0) {
+ skip_alternative();
+ return 0;
+ }
+ result = character_exists(ci, curenv);
+ tok.next();
+ }
+ else if (c == 'F') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = check_font(curenv->get_family()->nm, nm);
+ }
+ else if (c == 'S') {
+ tok.next();
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null()) {
+ skip_alternative();
+ return 0;
+ }
+ result = check_style(nm);
+ }
+ else if (tok.is_space())
+ result = 0;
+ else if (tok.usable_as_delimiter()) {
+ token delim = tok;
+ int delim_level = input_stack::get_level();
+ environment env1(curenv);
+ environment env2(curenv);
+ environment *oldenv = curenv;
+ curenv = &env1;
+ suppress_push = 1;
+ for (int i = 0; i < 2; i++) {
+ for (;;) {
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in output"
+ " comparison operator (got %1)", tok.description());
+ tok.next();
+ curenv = oldenv;
+ return 0;
+ }
+ if (tok == delim
+ && (compatible_flag || input_stack::get_level() == delim_level))
+ break;
+ tok.process();
+ }
+ curenv = &env2;
+ }
+ node *n1 = env1.extract_output_line();
+ node *n2 = env2.extract_output_line();
+ result = same_node_list(n1, n2);
+ delete_node_list(n1);
+ delete_node_list(n2);
+ curenv = oldenv;
+ have_input = 0;
+ suppress_push = 0;
+ tok.next();
+ }
+ else {
+ units n;
+ if (!get_number(&n, 'u')) {
+ skip_alternative();
+ return 0;
+ }
+ else
+ result = n > 0;
+ }
+ if (invert)
+ result = !result;
+ if (result)
+ begin_alternative();
+ else
+ skip_alternative();
+ return result;
+}
+
+void if_else_request()
+{
+ if_else_stack.push(do_if_request());
+}
+
+void if_request()
+{
+ do_if_request();
+}
+
+void else_request()
+{
+ if (if_else_stack.is_empty()) {
+ warning(WARN_EL, "unbalanced 'el' request");
+ skip_alternative();
+ }
+ else {
+ if (if_else_stack.pop())
+ skip_alternative();
+ else
+ begin_alternative();
+ }
+}
+
+static int while_depth = 0;
+static int while_break_flag = 0;
+
+void while_request()
+{
+ macro mac;
+ int escaped = 0;
+ int level = 0;
+ mac.append(new token_node(tok));
+ for (;;) {
+ node *n = 0; // pacify compiler
+ int c = input_stack::get(&n);
+ if (c == EOF)
+ break;
+ if (c == 0) {
+ escaped = 0;
+ mac.append(n);
+ }
+ else if (escaped) {
+ if (c == '{')
+ level += 1;
+ else if (c == '}')
+ level -= 1;
+ escaped = 0;
+ mac.append(c);
+ }
+ else {
+ if (c == ESCAPE_LEFT_BRACE)
+ level += 1;
+ else if (c == ESCAPE_RIGHT_BRACE)
+ level -= 1;
+ else if (c == escape_char)
+ escaped = 1;
+ mac.append(c);
+ if (c == '\n' && level <= 0)
+ break;
+ }
+ }
+ if (level != 0)
+ error("unbalanced brace escape sequences");
+ else {
+ while_depth++;
+ input_stack::add_boundary();
+ for (;;) {
+ input_stack::push(new string_iterator(mac, "while loop"));
+ tok.next();
+ if (!do_if_request()) {
+ while (input_stack::get(0) != EOF)
+ ;
+ break;
+ }
+ process_input_stack();
+ if (while_break_flag || input_stack::is_return_boundary()) {
+ while_break_flag = 0;
+ break;
+ }
+ }
+ input_stack::remove_boundary();
+ while_depth--;
+ }
+ tok.next();
+}
+
+void while_break_request()
+{
+ if (!while_depth) {
+ error("no while loop");
+ skip_line();
+ }
+ else {
+ while_break_flag = 1;
+ while (input_stack::get(0) != EOF)
+ ;
+ tok.next();
+ }
+}
+
+void while_continue_request()
+{
+ if (!while_depth) {
+ error("no while loop");
+ skip_line();
+ }
+ else {
+ while (input_stack::get(0) != EOF)
+ ;
+ tok.next();
+ }
+}
+
+void do_source(bool quietly)
+{
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ skip_line();
+ else {
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(nm.contents());
+ if (fp)
+ input_stack::push(new file_iterator(fp, nm.contents()));
+ else
+ // Suppress diagnostic only if we're operating quietly and it's an
+ // expected problem.
+ if (!(quietly && (ENOENT == errno)))
+ error("can't open '%1': %2", nm.contents(), strerror(errno));
+ tok.next();
+ }
+}
+
+// .so
+
+void source()
+{
+ do_source(false /* not quietly*/ );
+}
+
+// .soquiet: like .so, but silently ignore files that can't be opened
+// due to their nonexistence
+
+void source_quietly()
+{
+ do_source(true /* quietly */ );
+}
+
+// like .so but use popen()
+
+void pipe_source()
+{
+ if (!unsafe_flag) {
+ error("'pso' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+#ifdef POPEN_MISSING
+ error("pipes not available on this system");
+ skip_line();
+#else /* not POPEN_MISSING */
+ if (tok.is_newline() || tok.is_eof())
+ error("missing command");
+ else {
+ int c;
+ while ((c = get_copy(0)) == ' ' || c == '\t')
+ ;
+ int buf_size = 24;
+ char *buf = new char[buf_size];
+ int buf_used = 0;
+ for (; c != '\n' && c != EOF; c = get_copy(0)) {
+ const char *s = asciify(c);
+ int slen = strlen(s);
+ if (buf_used + slen + 1> buf_size) {
+ char *old_buf = buf;
+ int old_buf_size = buf_size;
+ buf_size *= 2;
+ buf = new char[buf_size];
+ memcpy(buf, old_buf, old_buf_size);
+ delete[] old_buf;
+ }
+ strcpy(buf + buf_used, s);
+ buf_used += slen;
+ }
+ buf[buf_used] = '\0';
+ errno = 0;
+ FILE *fp = popen(buf, POPEN_RT);
+ if (fp)
+ input_stack::push(new file_iterator(fp, symbol(buf).contents(), 1));
+ else
+ error("can't open pipe to process '%1': %2", buf, strerror(errno));
+ delete[] buf;
+ }
+ tok.next();
+#endif /* not POPEN_MISSING */
+ }
+}
+
+// .psbb
+//
+// Extract bounding box limits from PostScript file, and assign
+// them to the following four gtroff registers:--
+//
+static int llx_reg_contents = 0;
+static int lly_reg_contents = 0;
+static int urx_reg_contents = 0;
+static int ury_reg_contents = 0;
+
+// Manifest constants to specify the status of bounding box range
+// acquisition; (note that PSBB_RANGE_IS_BAD is also suitable for
+// assignment as a default ordinate property value).
+//
+#define PSBB_RANGE_IS_BAD 0
+#define PSBB_RANGE_IS_SET 1
+#define PSBB_RANGE_AT_END 2
+
+// Maximum input line length, for DSC conformance, and options to
+// control how it will be enforced; caller should select either of
+// DSC_LINE_MAX_IGNORED, to allow partial line collection spread
+// across multiple calls, or DSC_LINE_MAX_ENFORCE, to truncate
+// excess length lines at the DSC limit.
+//
+// Note that DSC_LINE_MAX_CHECKED is reserved for internal use by
+// ps_locator::get_line(), and should not be specified in any call;
+// also, handling of DSC_LINE_MAX_IGNORED, as a get_line() option,
+// is currently unimplemented.
+//
+#define DSC_LINE_MAX 255
+#define DSC_LINE_MAX_IGNORED -1
+#define DSC_LINE_MAX_ENFORCE 0
+#define DSC_LINE_MAX_CHECKED 1
+
+// Input characters to be considered as white space, when reading
+// PostScript file comments.
+//
+cset white_space("\n\r \t");
+
+// Class psbb_locator
+//
+// This locally declared and implemented class provides the methods
+// to be used for retrieval of bounding box properties from a specified
+// PostScript or PDF file.
+//
+class psbb_locator
+{
+ public:
+ // Only the class constructor is exposed publicly; instantiation of
+ // a class object will retrieve the requisite bounding box properties
+ // from the specified file, and assign them to gtroff registers.
+ //
+ psbb_locator(const char *);
+
+ private:
+ FILE *fp;
+ const char *filename;
+ char buf[2 + DSC_LINE_MAX];
+ int llx, lly, urx, ury;
+
+ // CRLF handling hook, for get_line() function.
+ //
+ int lastc;
+
+ // Private method functions facilitate implementation of the
+ // class constructor; none are used in any other context.
+ //
+ int get_line(int);
+ inline bool get_header_comment(void);
+ inline const char *context_args(const char *);
+ inline const char *context_args(const char *, const char *);
+ inline const char *bounding_box_args(void);
+ int parse_bounding_box(const char *);
+ inline void assign_registers(void);
+ inline int skip_to_trailer(void);
+};
+
+// psbb_locator class constructor.
+//
+psbb_locator::psbb_locator(const char *fname):
+filename(fname), llx(0), lly(0), urx(0), ury(0), lastc(EOF)
+{
+ // PS files might contain non-printable characters, such as ^Z
+ // and CRs not followed by an LF, so open them in binary mode.
+ //
+ fp = include_search_path.open_file_cautious(filename, 0, FOPEN_RB);
+ if (fp) {
+ // After successfully opening the file, acquire the first
+ // line, whence we may determine the file format...
+ //
+ if (get_line(DSC_LINE_MAX_ENFORCE) == 0)
+ //
+ // ...except in the case of an empty file, which we are
+ // unable to process further.
+ //
+ error("'%1' is empty", filename);
+
+# if 0
+ else if (context_args("%PDF-")) {
+ // TODO: PDF files specify a /MediaBox, as the equivalent
+ // of %%BoundingBox; we must implement a handler for this.
+ }
+# endif
+
+ else if (context_args("%!PS-Adobe-")) {
+ //
+ // PostScript files -- strictly, we expect EPS -- should
+ // specify a %%BoundingBox comment; locate it, initially
+ // expecting to find it in the comments header...
+ //
+ const char *context = NULL;
+ while ((context == NULL) && get_header_comment()) {
+ if ((context = bounding_box_args()) != NULL) {
+
+ // When the "%%BoundingBox" comment is found, it may simply
+ // specify the bounding box property values, or it may defer
+ // assignment to a similar trailer comment...
+ //
+ int status = parse_bounding_box(context);
+ if (status == PSBB_RANGE_AT_END) {
+ //
+ // ...in which case we must locate the trailer, and search
+ // for the appropriate specification within it.
+ //
+ if (skip_to_trailer() > 0) {
+ while ((context = bounding_box_args()) == NULL
+ && get_line(DSC_LINE_MAX_ENFORCE) > 0)
+ ;
+ if (context != NULL) {
+ //
+ // When we find a bounding box specification here...
+ //
+ if ((status = parse_bounding_box(context)) == PSBB_RANGE_AT_END)
+ //
+ // ...we must ensure it is not a further attempt to defer
+ // assignment to a trailer, (which we are already parsing).
+ //
+ error("'(atend)' is not allowed in trailer of '%1'",
+ filename);
+ }
+ }
+ else
+ // The trailer could not be found, so there is no context in
+ // which a trailing %%BoundingBox comment might be located.
+ //
+ context = NULL;
+ }
+ if (status == PSBB_RANGE_IS_BAD) {
+ //
+ // This arises when we found a %%BoundingBox comment, but
+ // we were unable to extract a valid set of range values from
+ // it; all we can do is diagnose this.
+ //
+ error("the arguments to the %%%%BoundingBox comment in '%1' are bad",
+ filename);
+ }
+ }
+ }
+ if (context == NULL)
+ //
+ // Conversely, this arises when no value specifying %%BoundingBox
+ // comment has been found, in any appropriate location...
+ //
+ error("%%%%BoundingBox comment not found in '%1'", filename);
+ }
+ else
+ // ...while this indicates that there was no appropriate file format
+ // identifier, on the first line of the input file.
+ //
+ error("'%1' does not conform to the Document Structuring Conventions",
+ filename);
+
+ // Regardless of success or failure of bounding box property acquisition,
+ // we did successfully open an input file, so we must now close it...
+ //
+ fclose(fp);
+ }
+ else
+ // ...but in this case, we did not successfully open any input file.
+ //
+ error("can't open '%1': %2", filename, strerror(errno));
+
+ // Irrespective of whether or not we were able to successfully acquire the
+ // bounding box properties, we ALWAYS update the associated gtroff registers.
+ //
+ assign_registers();
+}
+
+// psbb_locator::parse_bounding_box()
+//
+// Parse the argument to a %%BoundingBox comment, returning:
+// PSBB_RANGE_IS_SET if it contains four numbers,
+// PSBB_RANGE_AT_END if it contains "(atend)", or
+// PSBB_RANGE_IS_BAD otherwise.
+//
+int psbb_locator::parse_bounding_box(const char *context)
+{
+ // The Document Structuring Conventions say that the numbers
+ // should be integers.
+ //
+ int status = PSBB_RANGE_IS_SET;
+ if (sscanf(context, "%d %d %d %d", &llx, &lly, &urx, &ury) != 4) {
+ //
+ // Unfortunately some broken applications get this wrong;
+ // try to parse them as doubles instead...
+ //
+ double x1, x2, x3, x4;
+ if (sscanf(context, "%lf %lf %lf %lf", &x1, &x2, &x3, &x4) == 4) {
+ llx = (int)x1;
+ lly = (int)x2;
+ urx = (int)x3;
+ ury = (int)x4;
+ }
+ else {
+ // ...but if we can't parse four numbers, skip over any
+ // initial white space...
+ //
+ while (*context == '\x20' || *context == '\t')
+ context++;
+
+ // ...before checking for "(atend)", and setting the
+ // appropriate exit status accordingly.
+ //
+ status = (context_args("(atend)", context) == NULL)
+ ? llx = lly = urx = ury = PSBB_RANGE_IS_BAD
+ : PSBB_RANGE_AT_END;
+ }
+ }
+ return status;
+}
+
+// ps_locator::get_line()
+//
+// Collect an input record from a PostScript or PDF file.
+//
+// Inputs:
+// buf pointer to caller's input buffer.
+// fp FILE stream pointer, whence input is read.
+// filename name of input file, (for diagnostic use only).
+// dscopt DSC_LINE_MAX_ENFORCE or DSC_LINE_MAX_IGNORED.
+//
+// Returns the number of input characters stored into caller's
+// buffer, or zero at end of input stream.
+//
+// FIXME: Currently, get_line() always scans an entire line of
+// input, but returns only as much as will fit in caller's buffer;
+// the return value is always a positive integer, or zero, with no
+// way of indicating to caller, that there was more data than the
+// buffer could accommodate. A future enhancement could mitigate
+// this, returning a negative value in the event of truncation, or
+// even allowing for piecewise retrieval of excessively long lines
+// in successive reads; (this may be necessary to properly support
+// DSC_LINE_MAX_IGNORED, which is currently unimplemented).
+//
+int psbb_locator::get_line(int dscopt)
+{
+ int c, count = 0;
+ do {
+ // Collect input characters into caller's buffer, until we
+ // encounter a line terminator, or end of file...
+ //
+ while (((c = getc(fp)) != '\n') && (c != '\r') && (c != EOF)) {
+ if ((((lastc = c) < 0x1b) && !white_space(c)) || (c == 0x7f))
+ //
+ // ...rejecting any which may be designated as invalid.
+ //
+ error("invalid input character code %1 in '%2'", int(c), filename);
+
+ // On reading a valid input character, and when there is
+ // room in caller's buffer...
+ //
+ else if (count < DSC_LINE_MAX)
+ //
+ // ...store it.
+ //
+ buf[count++] = c;
+
+ // We have a valid input character, but it will not fit
+ // into caller's buffer; if enforcing DSC conformity...
+ //
+ else if (dscopt == DSC_LINE_MAX_ENFORCE) {
+ //
+ // ...diagnose and truncate.
+ //
+ dscopt = DSC_LINE_MAX_CHECKED;
+ error("PostScript file '%1' is non-conforming "
+ "because length of line exceeds 255", filename);
+ }
+ }
+ // Reading LF may be a special case: when it immediately
+ // follows a CR which terminated the preceding input line,
+ // we deem it to complete a CRLF terminator for the already
+ // collected preceding line; discard it, and restart input
+ // collection for the current line.
+ //
+ } while ((lastc == '\r') && ((lastc = c) == '\n'));
+
+ // For each collected input line, record its actual terminator,
+ // substitute our preferred LF terminator...
+ //
+ if (((lastc = c) != EOF) || (count > 0))
+ buf[count++] = '\n';
+
+ // ...and append the required C-string (NUL) terminator, before
+ // returning the actual count of input characters stored.
+ //
+ buf[count] = '\0';
+ return count;
+}
+
+// psbb_locator::context_args()
+//
+// Inputs:
+// tag literal text to be matched at start of input line
+//
+// Returns a pointer to the trailing substring of the current
+// input line, following an initial substring matching the "tag"
+// argument, or NULL if "tag" is not matched.
+//
+inline const char *psbb_locator::context_args(const char *tag)
+{
+ return context_args(tag, buf);
+}
+
+// psbb_locator::context_args()
+//
+// Overloaded variant of the preceding function, operating on
+// an alternative input buffer, (which may represent a terminal
+// substring of the psbb_locator's primary input line buffer).
+//
+// Inputs:
+// tag literal text to be matched at start of buffer
+// p pointer to text to be checked for "tag" match
+//
+// Returns a pointer to the trailing substring of the specified
+// text buffer, following an initial substring matching the "tag"
+// argument, or NULL if "tag" is not matched.
+//
+inline const char *psbb_locator::context_args(const char *tag, const char *p)
+{
+ size_t len = strlen(tag);
+ return (strncmp(tag, p, len) == 0) ? p + len : NULL;
+}
+
+// psbb_locator::bounding_box_args()
+//
+// Returns a pointer to the arguments string, within the current
+// input line, when this represents a PostScript "%%BoundingBox:"
+// comment, or NULL otherwise.
+//
+inline const char *psbb_locator::bounding_box_args(void)
+{
+ return context_args("%%BoundingBox:");
+}
+
+// psbb_locator::assign_registers()
+//
+// Copies the bounding box properties established within the
+// class object, to the associated gtroff registers.
+//
+inline void psbb_locator::assign_registers(void)
+{
+ llx_reg_contents = llx;
+ lly_reg_contents = lly;
+ urx_reg_contents = urx;
+ ury_reg_contents = ury;
+}
+
+// psbb_locator::get_header_comment()
+//
+// Fetch a line of PostScript input; return true if it complies with
+// the formatting requirements for header comments, and it is not an
+// "%%EndComments" line; otherwise return false.
+//
+inline bool psbb_locator::get_header_comment(void)
+{
+ return
+ // The first necessary requirement, for returning true,
+ // is that the input line is not empty, (i.e. not EOF).
+ //
+ get_line(DSC_LINE_MAX_ENFORCE) != 0
+
+ // In header comments, '%X' ('X' any printable character
+ // except whitespace) is also acceptable.
+ //
+ && (buf[0] == '%') && !white_space(buf[1])
+
+ // Finally, the input line must not say "%%EndComments".
+ //
+ && context_args("%%EndComments") == NULL;
+}
+
+// psbb_locator::skip_to_trailer()
+//
+// Reposition the PostScript input stream, such that the next get_line()
+// will retrieve the first line, if any, following a "%%Trailer" comment;
+// returns a positive integer value if the "%%Trailer" comment is found,
+// or zero if it is not.
+//
+inline int psbb_locator::skip_to_trailer(void)
+{
+ // Begin by considering a chunk of the input file starting 512 bytes
+ // before its end, and search it for a "%%Trailer" comment; if none is
+ // found, incrementally double the chunk size while it remains within
+ // a 32768L byte range, and search again...
+ //
+ for (ssize_t offset = 512L; offset > 0L; offset <<= 1) {
+ int status, failed;
+ if ((offset > 32768L) || ((failed = fseek(fp, -offset, SEEK_END)) != 0))
+ //
+ // ...ultimately resetting the offset to zero, and simply seeking
+ // to the start of the file, to terminate the cycle and do a "last
+ // ditch" search of the entire file, if any backward seek fails, or
+ // if we reach the arbitrary 32768L byte range limit.
+ //
+ failed = fseek(fp, offset = 0L, SEEK_SET);
+
+ // Following each successful seek...
+ //
+ if (!failed) {
+ //
+ // ...perform a search by reading lines from the input stream...
+ //
+ do { status = get_line(DSC_LINE_MAX_ENFORCE);
+ //
+ // ...until we either exhaust the available stream data, or
+ // we have located a "%%Trailer" comment line.
+ //
+ } while ((status != 0) && (context_args("%%Trailer") == NULL));
+ if (status > 0)
+ //
+ // We found the "%%Trailer" comment, so we may immediately
+ // return, with the stream positioned appropriately...
+ //
+ return status;
+ }
+ }
+ // ...otherwise, we report that no "%%Trailer" comment was found.
+ //
+ return 0;
+}
+
+// ps_bbox_request()
+//
+// Handle the .psbb request.
+//
+void ps_bbox_request()
+{
+ // Parse input line, to extract file name.
+ //
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ //
+ // No file name specified: ignore the entire request.
+ //
+ skip_line();
+ else {
+ // File name acquired: swallow the rest of the line.
+ //
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ errno = 0;
+
+ // Update {llx,lly,urx,ury}_reg_contents:
+ // declaring this class instance achieves this, as an
+ // intentional side effect of object construction.
+ //
+ psbb_locator do_ps_file(nm.contents());
+
+ // All done for .psbb; move on, to continue
+ // input stream processing.
+ //
+ tok.next();
+ }
+}
+
+const char *asciify(int c)
+{
+ static char buf[3];
+ buf[0] = escape_char == '\0' ? '\\' : escape_char;
+ buf[1] = buf[2] = '\0';
+ switch (c) {
+ case ESCAPE_QUESTION:
+ buf[1] = '?';
+ break;
+ case ESCAPE_AMPERSAND:
+ buf[1] = '&';
+ break;
+ case ESCAPE_RIGHT_PARENTHESIS:
+ buf[1] = ')';
+ break;
+ case ESCAPE_UNDERSCORE:
+ buf[1] = '_';
+ break;
+ case ESCAPE_BAR:
+ buf[1] = '|';
+ break;
+ case ESCAPE_CIRCUMFLEX:
+ buf[1] = '^';
+ break;
+ case ESCAPE_LEFT_BRACE:
+ buf[1] = '{';
+ break;
+ case ESCAPE_RIGHT_BRACE:
+ buf[1] = '}';
+ break;
+ case ESCAPE_LEFT_QUOTE:
+ buf[1] = '`';
+ break;
+ case ESCAPE_RIGHT_QUOTE:
+ buf[1] = '\'';
+ break;
+ case ESCAPE_HYPHEN:
+ buf[1] = '-';
+ break;
+ case ESCAPE_BANG:
+ buf[1] = '!';
+ break;
+ case ESCAPE_c:
+ buf[1] = 'c';
+ break;
+ case ESCAPE_e:
+ buf[1] = 'e';
+ break;
+ case ESCAPE_E:
+ buf[1] = 'E';
+ break;
+ case ESCAPE_PERCENT:
+ buf[1] = '%';
+ break;
+ case ESCAPE_SPACE:
+ buf[1] = ' ';
+ break;
+ case ESCAPE_TILDE:
+ buf[1] = '~';
+ break;
+ case ESCAPE_COLON:
+ buf[1] = ':';
+ break;
+ case PUSH_GROFF_MODE:
+ case PUSH_COMP_MODE:
+ case POP_GROFFCOMP_MODE:
+ buf[0] = '\0';
+ break;
+ default:
+ if (is_invalid_input_char(c))
+ buf[0] = '\0';
+ else
+ buf[0] = c;
+ break;
+ }
+ return buf;
+}
+
+const char *input_char_description(int c)
+{
+ switch (c) {
+ case '\n':
+ return "a newline character";
+ case '\b':
+ return "a backspace character";
+ case '\001':
+ return "a leader character";
+ case '\t':
+ return "a tab character";
+ case ' ':
+ return "a space character";
+ case '\0':
+ return "a node";
+ }
+ size_t bufsz = sizeof "magic character code " + INT_DIGITS + 1;
+ // repeat expression; no VLAs in ISO C++
+ static char buf[sizeof "magic character code " + INT_DIGITS + 1];
+ (void) memset(buf, 0, bufsz);
+ if (is_invalid_input_char(c)) {
+ const char *s = asciify(c);
+ if (*s) {
+ buf[0] = '\'';
+ strcpy(buf + 1, s);
+ strcat(buf, "'");
+ return buf;
+ }
+ sprintf(buf, "magic character code %d", c);
+ return buf;
+ }
+ if (csprint(c)) {
+ buf[0] = '\'';
+ buf[1] = c;
+ buf[2] = '\'';
+ return buf;
+ }
+ sprintf(buf, "character code %d", c);
+ return buf;
+}
+
+void tag()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ string s;
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ s = "x X ";
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ s += (char)c;
+ s += '\n';
+ curenv->add_node(new tag_node(s, 0));
+ }
+ tok.next();
+}
+
+void taga()
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ string s;
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ s = "x X ";
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ s += (char)c;
+ s += '\n';
+ curenv->add_node(new tag_node(s, 1));
+ }
+ tok.next();
+}
+
+// .tm, .tm1, and .tmc
+
+void do_terminal(int newline, int string_like)
+{
+ if (!tok.is_newline() && !tok.is_eof()) {
+ int c;
+ for (;;) {
+ c = get_copy(0);
+ if (string_like && c == '"') {
+ c = get_copy(0);
+ break;
+ }
+ if (c != ' ' && c != '\t')
+ break;
+ }
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), stderr);
+ }
+ if (newline)
+ fputc('\n', stderr);
+ fflush(stderr);
+ tok.next();
+}
+
+void terminal()
+{
+ do_terminal(1, 0);
+}
+
+void terminal1()
+{
+ do_terminal(1, 1);
+}
+
+void terminal_continue()
+{
+ do_terminal(0, 1);
+}
+
+dictionary stream_dictionary(20);
+
+void do_open(int append)
+{
+ symbol stream = get_name(true /* required */);
+ if (!stream.is_null()) {
+ symbol filename = get_long_name(true /* required */);
+ if (!filename.is_null()) {
+ errno = 0;
+ FILE *fp = fopen(filename.contents(), append ? "a" : "w");
+ if (!fp) {
+ error("can't open '%1' for %2: %3",
+ filename.contents(),
+ append ? "appending" : "writing",
+ strerror(errno));
+ fp = (FILE *)stream_dictionary.remove(stream);
+ }
+ else
+ fp = (FILE *)stream_dictionary.lookup(stream, fp);
+ if (fp)
+ fclose(fp);
+ }
+ }
+ skip_line();
+}
+
+void open_request()
+{
+ if (!unsafe_flag) {
+ error("'open' request is not allowed in safer mode");
+ skip_line();
+ }
+ else
+ do_open(0);
+}
+
+void opena_request()
+{
+ if (!unsafe_flag) {
+ error("'opena' request is not allowed in safer mode");
+ skip_line();
+ }
+ else
+ do_open(1);
+}
+
+void close_request()
+{
+ symbol stream = get_name(true /* required */);
+ if (!stream.is_null()) {
+ FILE *fp = (FILE *)stream_dictionary.remove(stream);
+ if (!fp)
+ error("no stream named '%1'", stream.contents());
+ else
+ fclose(fp);
+ }
+ skip_line();
+}
+
+// .write and .writec
+
+void do_write_request(int newline)
+{
+ symbol stream = get_name(true /* required */);
+ if (stream.is_null()) {
+ skip_line();
+ return;
+ }
+ FILE *fp = (FILE *)stream_dictionary.lookup(stream);
+ if (!fp) {
+ error("no stream named '%1'", stream.contents());
+ skip_line();
+ return;
+ }
+ int c;
+ while ((c = get_copy(0)) == ' ')
+ ;
+ if (c == '"')
+ c = get_copy(0);
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), fp);
+ if (newline)
+ fputc('\n', fp);
+ fflush(fp);
+ tok.next();
+}
+
+void write_request()
+{
+ do_write_request(1);
+}
+
+void write_request_continue()
+{
+ do_write_request(0);
+}
+
+void write_macro_request()
+{
+ symbol stream = get_name(true /* required */);
+ if (stream.is_null()) {
+ skip_line();
+ return;
+ }
+ FILE *fp = (FILE *)stream_dictionary.lookup(stream);
+ if (!fp) {
+ error("no stream named '%1'", stream.contents());
+ skip_line();
+ return;
+ }
+ symbol s = get_name(true /* required */);
+ if (s.is_null()) {
+ skip_line();
+ return;
+ }
+ request_or_macro *p = lookup_request(s);
+ macro *m = p->to_macro();
+ if (!m)
+ error("cannot write request");
+ else {
+ string_iterator iter(*m);
+ for (;;) {
+ int c = iter.get(0);
+ if (c == EOF)
+ break;
+ fputs(asciify(c), fp);
+ }
+ fflush(fp);
+ }
+ skip_line();
+}
+
+void warnscale_request()
+{
+ if (has_arg()) {
+ char c = tok.ch();
+ if (c == 'u')
+ warn_scale = 1.0;
+ else if (c == 'i')
+ warn_scale = (double)units_per_inch;
+ else if (c == 'c')
+ warn_scale = (double)units_per_inch / 2.54;
+ else if (c == 'p')
+ warn_scale = (double)units_per_inch / 72.0;
+ else if (c == 'P')
+ warn_scale = (double)units_per_inch / 6.0;
+ else {
+ warning(WARN_SCALE,
+ "scaling unit '%1' invalid; using 'i' instead", c);
+ c = 'i';
+ }
+ warn_scaling_indicator = c;
+ }
+ skip_line();
+}
+
+void spreadwarn_request()
+{
+ hunits n;
+ if (has_arg() && get_hunits(&n, 'm')) {
+ if (n < 0)
+ n = 0;
+ hunits em = curenv->get_size();
+ spread_limit = (double)n.to_units()
+ / (em.is_zero() ? hresolution : em.to_units());
+ }
+ else
+ spread_limit = -spread_limit - 1; // no arg toggles on/off without
+ // changing value; we mirror at
+ // -0.5 to make zero a valid value
+ skip_line();
+}
+
+static void init_charset_table()
+{
+ char buf[16];
+ strcpy(buf, "char");
+ for (int i = 0; i < 256; i++) {
+ strcpy(buf + 4, i_to_a(i));
+ charset_table[i] = get_charinfo(symbol(buf));
+ charset_table[i]->set_ascii_code(i);
+ if (csalpha(i))
+ charset_table[i]->set_hyphenation_code(cmlower(i));
+ }
+ charset_table['.']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['?']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['!']->set_flags(charinfo::ENDS_SENTENCE);
+ charset_table['-']->set_flags(charinfo::BREAK_AFTER);
+ charset_table['"']->set_flags(charinfo::TRANSPARENT);
+ charset_table['\'']->set_flags(charinfo::TRANSPARENT);
+ charset_table[')']->set_flags(charinfo::TRANSPARENT);
+ charset_table[']']->set_flags(charinfo::TRANSPARENT);
+ charset_table['*']->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("dg"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("dd"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("rq"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("cq"))->set_flags(charinfo::TRANSPARENT);
+ get_charinfo(symbol("em"))->set_flags(charinfo::BREAK_AFTER);
+ get_charinfo(symbol("hy"))->set_flags(charinfo::BREAK_AFTER);
+ get_charinfo(symbol("ul"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("rn"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("radicalex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("sqrtex"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("ru"))->set_flags(charinfo::OVERLAPS_HORIZONTALLY);
+ get_charinfo(symbol("br"))->set_flags(charinfo::OVERLAPS_VERTICALLY);
+ page_character = charset_table['%'];
+}
+
+static void init_hpf_code_table()
+{
+ for (int i = 0; i < 256; i++)
+ hpf_code_table[i] = cmlower(i);
+}
+
+static void do_translate(int translate_transparent, int translate_input)
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (tok.is_space()) {
+ // This is a really bizarre troff feature.
+ tok.next();
+ translate_space_to_dummy = tok.is_dummy();
+ if (tok.is_newline() || tok.is_eof())
+ break;
+ error("cannot translate space character; ignoring");
+ tok.next();
+ continue;
+ }
+ charinfo *ci1 = tok.get_char(true /* required */);
+ if (ci1 == 0)
+ break;
+ tok.next();
+ if (tok.is_newline() || tok.is_eof()) {
+ ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
+ translate_transparent);
+ break;
+ }
+ if (tok.is_space())
+ ci1->set_special_translation(charinfo::TRANSLATE_SPACE,
+ translate_transparent);
+ else if (tok.is_stretchable_space())
+ ci1->set_special_translation(charinfo::TRANSLATE_STRETCHABLE_SPACE,
+ translate_transparent);
+ else if (tok.is_dummy())
+ ci1->set_special_translation(charinfo::TRANSLATE_DUMMY,
+ translate_transparent);
+ else if (tok.is_hyphen_indicator())
+ ci1->set_special_translation(charinfo::TRANSLATE_HYPHEN_INDICATOR,
+ translate_transparent);
+ else {
+ charinfo *ci2 = tok.get_char(true /* required */);
+ if (ci2 == 0)
+ break;
+ if (ci1 == ci2)
+ ci1->set_translation(0, translate_transparent, translate_input);
+ else
+ ci1->set_translation(ci2, translate_transparent, translate_input);
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+void translate()
+{
+ do_translate(1, 0);
+}
+
+void translate_no_transparent()
+{
+ do_translate(0, 0);
+}
+
+void translate_input()
+{
+ do_translate(1, 1);
+}
+
+void char_flags()
+{
+ int flags;
+ if (get_integer(&flags))
+ while (has_arg()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci) {
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ ci->set_flags(flags);
+ }
+ tok.next();
+ }
+ skip_line();
+}
+
+void hyphenation_code()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ charinfo *ci = tok.get_char(true /* required */);
+ if (ci == 0)
+ break;
+ tok.next();
+ tok.skip();
+ unsigned char c = tok.ch();
+ if (c == 0) {
+ error("hyphenation code must be ordinary character");
+ break;
+ }
+ if (csdigit(c)) {
+ error("hyphenation code cannot be digit");
+ break;
+ }
+ ci->set_hyphenation_code(c);
+ if (ci->get_translation()
+ && ci->get_translation()->get_translation_input())
+ ci->get_translation()->set_hyphenation_code(c);
+ tok.next();
+ tok.skip();
+ }
+ skip_line();
+}
+
+void hyphenation_patterns_file_code()
+{
+ tok.skip();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ int n1, n2;
+ if (get_integer(&n1) && (0 <= n1 && n1 <= 255)) {
+ if (!has_arg()) {
+ error("missing output hyphenation code");
+ break;
+ }
+ if (get_integer(&n2) && (0 <= n2 && n2 <= 255)) {
+ hpf_code_table[n1] = n2;
+ tok.skip();
+ }
+ else {
+ error("output hyphenation code must be integer in the range 0..255");
+ break;
+ }
+ }
+ else {
+ error("input hyphenation code must be integer in the range 0..255");
+ break;
+ }
+ }
+ skip_line();
+}
+
+dictionary char_class_dictionary(501);
+
+void define_class()
+{
+ tok.skip();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ charinfo *ci = get_charinfo(nm);
+ charinfo *child1 = 0, *child2 = 0;
+ while (!tok.is_newline() && !tok.is_eof()) {
+ tok.skip();
+ if (child1 != 0 && tok.ch() == '-') {
+ tok.next();
+ child2 = tok.get_char(true /* required */);
+ if (!child2) {
+ warning(WARN_MISSING,
+ "missing end of character range in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ if (child1->is_class() || child2->is_class()) {
+ warning(WARN_SYNTAX,
+ "a nested character class is not allowed in a range"
+ " definition");
+ skip_line();
+ return;
+ }
+ int u1 = child1->get_unicode_code();
+ int u2 = child2->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid start value in character range");
+ skip_line();
+ return;
+ }
+ if (u2 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid end value in character range");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1, u2);
+ child1 = child2 = 0;
+ }
+ else if (child1 != 0) {
+ if (child1->is_class()) {
+ if (ci == child1) {
+ warning(WARN_SYNTAX, "invalid cyclic class nesting");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(child1);
+ }
+ else {
+ int u1 = child1->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid character value in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1);
+ }
+ child1 = 0;
+ }
+ child1 = tok.get_char(true /* required */);
+ tok.next();
+ if (!child1) {
+ if (!tok.is_newline())
+ skip_line();
+ break;
+ }
+ }
+ if (child1 != 0) {
+ if (child1->is_class()) {
+ if (ci == child1) {
+ warning(WARN_SYNTAX, "invalid cyclic class nesting");
+ skip_line();
+ return;
+ }
+ ci->add_to_class(child1);
+ }
+ else {
+ int u1 = child1->get_unicode_code();
+ if (u1 < 0) {
+ warning(WARN_SYNTAX,
+ "invalid character value in class '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ ci->add_to_class(u1);
+ }
+ child1 = 0;
+ }
+ if (!ci->is_class()) {
+ warning(WARN_SYNTAX,
+ "empty class definition for '%1'",
+ nm.contents());
+ skip_line();
+ return;
+ }
+ (void)char_class_dictionary.lookup(nm, ci);
+ skip_line();
+}
+
+charinfo *token::get_char(bool required)
+{
+ if (type == TOKEN_CHAR)
+ return charset_table[c];
+ if (type == TOKEN_SPECIAL)
+ return get_charinfo(nm);
+ if (type == TOKEN_NUMBERED_CHAR)
+ return get_charinfo_by_number(val);
+ if (type == TOKEN_ESCAPE) {
+ if (escape_char != 0)
+ return charset_table[escape_char];
+ else {
+ // XXX: Is this possible? token::add_to_zero_width_node_list()
+ // and token::process() don't add this token type if the escape
+ // character is null. If not, this should be an assert(). Also
+ // see escape_off().
+ error("escaped 'e' used while escape sequences disabled");
+ return 0;
+ }
+ }
+ if (required) {
+ if (type == TOKEN_EOF || type == TOKEN_NEWLINE)
+ warning(WARN_MISSING, "missing ordinary or special character");
+ else
+ error("expected ordinary or special character, got %1",
+ description());
+ }
+ return 0;
+}
+
+charinfo *get_optional_char()
+{
+ while (tok.is_space())
+ tok.next();
+ charinfo *ci = tok.get_char();
+ if (!ci)
+ check_missing_character();
+ else
+ tok.next();
+ return ci;
+}
+
+void check_missing_character()
+{
+ if (!tok.is_newline() && !tok.is_eof() && !tok.is_right_brace()
+ && !tok.is_tab())
+ error("expected ordinary or special character, got %1; treated as"
+ " missing", tok.description());
+}
+
+// this is for \Z
+
+int token::add_to_zero_width_node_list(node **pp)
+{
+ hunits w;
+ int s;
+ node *n = 0;
+ switch (type) {
+ case TOKEN_CHAR:
+ *pp = (*pp)->add_char(charset_table[c], curenv, &w, &s);
+ break;
+ case TOKEN_DUMMY:
+ n = new dummy_node;
+ break;
+ case TOKEN_ESCAPE:
+ if (escape_char != 0)
+ *pp = (*pp)->add_char(charset_table[escape_char], curenv, &w, &s);
+ break;
+ case TOKEN_HYPHEN_INDICATOR:
+ *pp = (*pp)->add_discretionary_hyphen();
+ break;
+ case TOKEN_ITALIC_CORRECTION:
+ *pp = (*pp)->add_italic_correction(&w);
+ break;
+ case TOKEN_LEFT_BRACE:
+ break;
+ case TOKEN_MARK_INPUT:
+ set_number_reg(nm, curenv->get_input_line_position().to_units());
+ break;
+ case TOKEN_NODE:
+ case TOKEN_HORIZONTAL_SPACE:
+ n = nd;
+ nd = 0;
+ break;
+ case TOKEN_NUMBERED_CHAR:
+ *pp = (*pp)->add_char(get_charinfo_by_number(val), curenv, &w, &s);
+ break;
+ case TOKEN_RIGHT_BRACE:
+ break;
+ case TOKEN_SPACE:
+ n = new hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_SPECIAL:
+ *pp = (*pp)->add_char(get_charinfo(nm), curenv, &w, &s);
+ break;
+ case TOKEN_STRETCHABLE_SPACE:
+ n = new unbreakable_space_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ n = new space_char_hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color());
+ break;
+ case TOKEN_TRANSPARENT_DUMMY:
+ n = new transparent_dummy_node;
+ break;
+ case TOKEN_ZERO_WIDTH_BREAK:
+ n = new space_node(H0, curenv->get_fill_color());
+ n->freeze_space();
+ n->is_escape_colon();
+ break;
+ default:
+ return 0;
+ }
+ if (n) {
+ n->next = *pp;
+ *pp = n;
+ }
+ return 1;
+}
+
+void token::process()
+{
+ if (possibly_handle_first_page_transition())
+ return;
+ switch (type) {
+ case TOKEN_BACKSPACE:
+ curenv->add_node(new hmotion_node(-curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_CHAR:
+ curenv->add_char(charset_table[c]);
+ break;
+ case TOKEN_DUMMY:
+ curenv->add_node(new dummy_node);
+ break;
+ case TOKEN_EMPTY:
+ assert(0);
+ break;
+ case TOKEN_EOF:
+ assert(0);
+ break;
+ case TOKEN_ESCAPE:
+ if (escape_char != 0)
+ curenv->add_char(charset_table[escape_char]);
+ break;
+ case TOKEN_BEGIN_TRAP:
+ case TOKEN_END_TRAP:
+ case TOKEN_PAGE_EJECTOR:
+ // these are all handled in process_input_stack()
+ break;
+ case TOKEN_HYPHEN_INDICATOR:
+ curenv->add_hyphen_indicator();
+ break;
+ case TOKEN_INTERRUPT:
+ curenv->interrupt();
+ break;
+ case TOKEN_ITALIC_CORRECTION:
+ curenv->add_italic_correction();
+ break;
+ case TOKEN_LEADER:
+ curenv->handle_tab(1);
+ break;
+ case TOKEN_LEFT_BRACE:
+ break;
+ case TOKEN_MARK_INPUT:
+ set_number_reg(nm, curenv->get_input_line_position().to_units());
+ break;
+ case TOKEN_NEWLINE:
+ curenv->newline();
+ break;
+ case TOKEN_NODE:
+ case TOKEN_HORIZONTAL_SPACE:
+ curenv->add_node(nd);
+ nd = 0;
+ break;
+ case TOKEN_NUMBERED_CHAR:
+ curenv->add_char(get_charinfo_by_number(val));
+ break;
+ case TOKEN_REQUEST:
+ // handled in process_input_stack()
+ break;
+ case TOKEN_RIGHT_BRACE:
+ break;
+ case TOKEN_SPACE:
+ curenv->space();
+ break;
+ case TOKEN_SPECIAL:
+ curenv->add_char(get_charinfo(nm));
+ break;
+ case TOKEN_SPREAD:
+ curenv->spread();
+ break;
+ case TOKEN_STRETCHABLE_SPACE:
+ curenv->add_node(new unbreakable_space_node(curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_UNSTRETCHABLE_SPACE:
+ curenv->add_node(new space_char_hmotion_node(curenv->get_space_width(),
+ curenv->get_fill_color()));
+ break;
+ case TOKEN_TAB:
+ curenv->handle_tab(0);
+ break;
+ case TOKEN_TRANSPARENT:
+ break;
+ case TOKEN_TRANSPARENT_DUMMY:
+ curenv->add_node(new transparent_dummy_node);
+ break;
+ case TOKEN_ZERO_WIDTH_BREAK:
+ {
+ node *tmp = new space_node(H0, curenv->get_fill_color());
+ tmp->freeze_space();
+ tmp->is_escape_colon();
+ curenv->add_node(tmp);
+ break;
+ }
+ default:
+ assert(0);
+ }
+}
+
+class nargs_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *nargs_reg::get_string()
+{
+ return i_to_a(input_stack::nargs());
+}
+
+class lineno_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *lineno_reg::get_string()
+{
+ int line;
+ const char *file;
+ if (!input_stack::get_location(0, &file, &line))
+ line = 0;
+ return i_to_a(line);
+}
+
+class writable_lineno_reg : public general_reg {
+public:
+ writable_lineno_reg();
+ void set_value(units);
+ bool get_value(units *);
+};
+
+writable_lineno_reg::writable_lineno_reg()
+{
+}
+
+bool writable_lineno_reg::get_value(units *res)
+{
+ int line;
+ const char *file;
+ if (!input_stack::get_location(0, &file, &line))
+ return false;
+ *res = line;
+ return true;
+}
+
+void writable_lineno_reg::set_value(units n)
+{
+ input_stack::set_location(0, n);
+}
+
+class filename_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *filename_reg::get_string()
+{
+ int line;
+ const char *file;
+ if (input_stack::get_location(0, &file, &line))
+ return file;
+ else
+ return 0;
+}
+
+class break_flag_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *break_flag_reg::get_string()
+{
+ return i_to_a(input_stack::get_break_flag());
+}
+
+class readonly_text_register : public reg {
+ const char *s;
+public:
+ readonly_text_register(const char *);
+ const char *get_string();
+};
+
+readonly_text_register::readonly_text_register(const char *p) : s(p)
+{
+}
+
+const char *readonly_text_register::get_string()
+{
+ return s;
+}
+
+readonly_register::readonly_register(int *q) : p(q)
+{
+}
+
+const char *readonly_register::get_string()
+{
+ return i_to_a(*p);
+}
+
+void abort_request()
+{
+ int c;
+ if (tok.is_eof())
+ c = EOF;
+ else if (tok.is_newline())
+ c = '\n';
+ else {
+ while ((c = get_copy(0)) == ' ')
+ ;
+ }
+ if (!(c == EOF || c == '\n')) {
+ for (; c != '\n' && c != EOF; c = get_copy(0))
+ fputs(asciify(c), stderr);
+ fputc('\n', stderr);
+ }
+ fflush(stderr);
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+char *read_string()
+{
+ int len = 256;
+ char *s = new char[len];
+ int c;
+ while ((c = get_copy(0)) == ' ')
+ ;
+ int i = 0;
+ while (c != '\n' && c != EOF) {
+ if (!is_invalid_input_char(c)) {
+ if (i + 2 > len) {
+ char *tem = s;
+ s = new char[len*2];
+ memcpy(s, tem, len);
+ len *= 2;
+ delete[] tem;
+ }
+ s[i++] = c;
+ }
+ c = get_copy(0);
+ }
+ s[i] = '\0';
+ tok.next();
+ if (i == 0) {
+ delete[] s;
+ return 0;
+ }
+ return s;
+}
+
+void pipe_output()
+{
+ if (!unsafe_flag) {
+ error("'pi' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+#ifdef POPEN_MISSING
+ error("pipes not available on this system");
+ skip_line();
+#else /* not POPEN_MISSING */
+ if (the_output) {
+ error("can't pipe: output already started");
+ skip_line();
+ }
+ else {
+ char *pc;
+ if ((pc = read_string()) == 0)
+ error("can't pipe to empty command");
+ if (pipe_command) {
+ char *s = new char[strlen(pipe_command) + strlen(pc) + 1 + 1];
+ strcpy(s, pipe_command);
+ strcat(s, "|");
+ strcat(s, pc);
+ delete[] pipe_command;
+ delete[] pc;
+ pipe_command = s;
+ }
+ else
+ pipe_command = pc;
+ }
+#endif /* not POPEN_MISSING */
+ }
+}
+
+static int system_status;
+
+void system_request()
+{
+ if (!unsafe_flag) {
+ error("'sy' request is not allowed in safer mode");
+ skip_line();
+ }
+ else {
+ char *command = read_string();
+ if (!command)
+ error("empty command");
+ else {
+ system_status = system(command);
+ delete[] command;
+ }
+ }
+}
+
+void copy_file()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(COPY_FILE_REQUEST);
+ return;
+ }
+ symbol filename = get_long_name(true /* required */);
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (!filename.is_null())
+ curdiv->copy_file(filename.contents());
+ tok.next();
+}
+
+#ifdef COLUMN
+
+void vjustify()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(VJUSTIFY_REQUEST);
+ return;
+ }
+ symbol type = get_long_name(true /* required */);
+ if (!type.is_null())
+ curdiv->vjustify(type);
+ skip_line();
+}
+
+#endif /* COLUMN */
+
+void transparent_file()
+{
+ if (curdiv == topdiv && topdiv->before_first_page) {
+ handle_initial_request(TRANSPARENT_FILE_REQUEST);
+ return;
+ }
+ symbol filename = get_long_name(true /* required */);
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (break_flag)
+ curenv->do_break();
+ if (!filename.is_null()) {
+ errno = 0;
+ FILE *fp = include_search_path.open_file_cautious(filename.contents());
+ if (!fp)
+ error("can't open '%1': %2", filename.contents(), strerror(errno));
+ else {
+ int bol = 1;
+ for (;;) {
+ int c = getc(fp);
+ if (c == EOF)
+ break;
+ if (is_invalid_input_char(c))
+ warning(WARN_INPUT, "invalid input character code %1", int(c));
+ else {
+ curdiv->transparent_output(c);
+ bol = c == '\n';
+ }
+ }
+ if (!bol)
+ curdiv->transparent_output('\n');
+ fclose(fp);
+ }
+ }
+ tok.next();
+}
+
+class page_range {
+ int first;
+ int last;
+public:
+ page_range *next;
+ page_range(int, int, page_range *);
+ int contains(int n);
+};
+
+page_range::page_range(int i, int j, page_range *p)
+: first(i), last(j), next(p)
+{
+}
+
+int page_range::contains(int n)
+{
+ return n >= first && (last <= 0 || n <= last);
+}
+
+page_range *output_page_list = 0;
+
+int in_output_page_list(int n)
+{
+ if (!output_page_list)
+ return 1;
+ for (page_range *p = output_page_list; p; p = p->next)
+ if (p->contains(n))
+ return 1;
+ return 0;
+}
+
+static void parse_output_page_list(char *p)
+{
+ for (;;) {
+ int i;
+ if (*p == '-')
+ i = 1;
+ else if (csdigit(*p)) {
+ i = 0;
+ do
+ i = i*10 + *p++ - '0';
+ while (csdigit(*p));
+ }
+ else
+ break;
+ int j;
+ if (*p == '-') {
+ p++;
+ j = 0;
+ if (csdigit(*p)) {
+ do
+ j = j*10 + *p++ - '0';
+ while (csdigit(*p));
+ }
+ }
+ else
+ j = i;
+ if (j == 0)
+ last_page_number = -1;
+ else if (last_page_number >= 0 && j > last_page_number)
+ last_page_number = j;
+ output_page_list = new page_range(i, j, output_page_list);
+ if (*p != ',')
+ break;
+ ++p;
+ }
+ if (*p != '\0') {
+ error("bad output page list");
+ output_page_list = 0;
+ }
+}
+
+static FILE *open_mac_file(const char *mac, char **path)
+{
+ // Try `mac`.tmac first, then tmac.`mac`. Expect ENOENT errors.
+ char *s1 = new char[strlen(mac)+strlen(MACRO_POSTFIX)+1];
+ strcpy(s1, mac);
+ strcat(s1, MACRO_POSTFIX);
+ FILE *fp = mac_path->open_file(s1, path);
+ if (!fp && ENOENT != errno)
+ error("can't open macro file '%1': %2", s1, strerror(errno));
+ delete[] s1;
+ if (!fp) {
+ char *s2 = new char[strlen(mac)+strlen(MACRO_PREFIX)+1];
+ strcpy(s2, MACRO_PREFIX);
+ strcat(s2, mac);
+ fp = mac_path->open_file(s2, path);
+ if (!fp && ENOENT != errno)
+ error("can't open macro file '%1': %2", s2, strerror(errno));
+ delete[] s2;
+ }
+ return fp;
+}
+
+static void process_macro_file(const char *mac)
+{
+ char *path;
+ FILE *fp = open_mac_file(mac, &path);
+ if (!fp)
+ fatal("unable to open macro file for -m argument '%1'", mac);
+ const char *s = symbol(path).contents();
+ free(path);
+ input_stack::push(new file_iterator(fp, s));
+ tok.next();
+ process_input_stack();
+}
+
+static void process_startup_file(const char *filename)
+{
+ char *path;
+ search_path *orig_mac_path = mac_path;
+ mac_path = &config_macro_path;
+ FILE *fp = mac_path->open_file(filename, &path);
+ if (fp) {
+ input_stack::push(new file_iterator(fp, symbol(path).contents()));
+ free(path);
+ tok.next();
+ process_input_stack();
+ }
+ mac_path = orig_mac_path;
+}
+
+void do_macro_source(bool quietly)
+{
+ symbol nm = get_long_name(true /* required */);
+ if (nm.is_null())
+ skip_line();
+ else {
+ while (!tok.is_newline() && !tok.is_eof())
+ tok.next();
+ char *path;
+ FILE *fp = mac_path->open_file(nm.contents(), &path);
+ // .mso cannot go through open_mac_file, which handles the -m option
+ // and expects only an identifier like "s" or "an", not a file name.
+ // We need to do it here manually: If we have tmac.FOOBAR, try
+ // FOOBAR.tmac and vice versa.
+ if (!fp) {
+ const char *fn = nm.contents();
+ size_t fnlen = strlen(fn);
+ if (strncasecmp(fn, MACRO_PREFIX, sizeof(MACRO_PREFIX) - 1) == 0) {
+ char *s = new char[fnlen + sizeof(MACRO_POSTFIX)];
+ strcpy(s, fn + sizeof(MACRO_PREFIX) - 1);
+ strcat(s, MACRO_POSTFIX);
+ fp = mac_path->open_file(s, &path);
+ delete[] s;
+ }
+ if (!fp) {
+ if (strncasecmp(fn + fnlen - sizeof(MACRO_POSTFIX) + 1,
+ MACRO_POSTFIX, sizeof(MACRO_POSTFIX) - 1) == 0) {
+ char *s = new char[fnlen + sizeof(MACRO_PREFIX)];
+ strcpy(s, MACRO_PREFIX);
+ strncat(s, fn, fnlen - sizeof(MACRO_POSTFIX) + 1);
+ fp = mac_path->open_file(s, &path);
+ delete[] s;
+ }
+ }
+ }
+ if (fp) {
+ input_stack::push(new file_iterator(fp, symbol(path).contents()));
+ free(path);
+ }
+ else
+ // Suppress diagnostic only if we're operating quietly and it's an
+ // expected problem.
+ if (!quietly && (ENOENT == errno))
+ warning(WARN_FILE, "can't open macro file '%1': %2",
+ nm.contents(), strerror(errno));
+ tok.next();
+ }
+}
+
+// .mso
+
+void macro_source()
+{
+ do_macro_source(false /* not quietly (if WARN_FILE enabled) */ );
+}
+
+// .msoquiet: like .mso, but silently ignore files that can't be opened
+// due to their nonexistence
+
+void macro_source_quietly()
+{
+ do_macro_source(true /* quietly */ );
+}
+
+static void process_input_file(const char *name)
+{
+ FILE *fp;
+ if (strcmp(name, "-") == 0) {
+ clearerr(stdin);
+ fp = stdin;
+ }
+ else {
+ errno = 0;
+ fp = include_search_path.open_file_cautious(name);
+ if (!fp)
+ fatal("can't open '%1': %2", name, strerror(errno));
+ }
+ input_stack::push(new file_iterator(fp, name));
+ tok.next();
+ process_input_stack();
+}
+
+// make sure the_input is empty before calling this
+
+static int evaluate_expression(const char *expr, units *res)
+{
+ input_stack::push(make_temp_iterator(expr));
+ tok.next();
+ int success = get_number(res, 'u');
+ while (input_stack::get(0) != EOF)
+ ;
+ return success;
+}
+
+static void do_register_assignment(const char *s)
+{
+ const char *p = strchr(s, '=');
+ if (!p) {
+ char buf[2];
+ buf[0] = s[0];
+ buf[1] = 0;
+ units n;
+ if (evaluate_expression(s + 1, &n))
+ set_number_reg(buf, n);
+ }
+ else {
+ char *buf = new char[p - s + 1];
+ memcpy(buf, s, p - s);
+ buf[p - s] = 0;
+ units n;
+ if (evaluate_expression(p + 1, &n))
+ set_number_reg(buf, n);
+ delete[] buf;
+ }
+}
+
+static void set_string(const char *name, const char *value)
+{
+ macro *m = new macro;
+ for (const char *p = value; *p; p++)
+ if (!is_invalid_input_char((unsigned char)*p))
+ m->append(*p);
+ request_dictionary.define(name, m);
+}
+
+static void do_string_assignment(const char *s)
+{
+ const char *p = strchr(s, '=');
+ if (!p) {
+ char buf[2];
+ buf[0] = s[0];
+ buf[1] = 0;
+ set_string(buf, s + 1);
+ }
+ else {
+ char *buf = new char[p - s + 1];
+ memcpy(buf, s, p - s);
+ buf[p - s] = 0;
+ set_string(buf, p + 1);
+ delete[] buf;
+ }
+}
+
+struct string_list {
+ const char *s;
+ string_list *next;
+ string_list(const char *ss) : s(ss), next(0) {}
+};
+
+#if 0
+static void prepend_string(const char *s, string_list **p)
+{
+ string_list *l = new string_list(s);
+ l->next = *p;
+ *p = l;
+}
+#endif
+
+static void add_string(const char *s, string_list **p)
+{
+ while (*p)
+ p = &((*p)->next);
+ *p = new string_list(s);
+}
+
+void usage(FILE *stream, const char *prog)
+{
+ fprintf(stream,
+"usage: %s [-abcCEiRUz] [-d ct] [-d string=text] [-f font-family]"
+" [-F font-directory] [-I inclusion-directory] [-m macro-package]"
+" [-M macro-directory] [-n page-number] [-o page-list]"
+" [-r cnumeric-expression] [-r register=numeric-expression]"
+" [-T output-device] [-w warning-category] [-W warning-category]"
+" [file ...]\n"
+"usage: %s {-v | --version}\n"
+"usage: %s {-h | --help}\n",
+ prog, prog, prog);
+}
+
+int main(int argc, char **argv)
+{
+ program_name = argv[0];
+ static char stderr_buf[BUFSIZ];
+ setbuf(stderr, stderr_buf);
+ int c;
+ string_list *macros = 0;
+ string_list *register_assignments = 0;
+ string_list *string_assignments = 0;
+ int iflag = 0;
+ int tflag = 0;
+ int fflag = 0;
+ int nflag = 0;
+ int no_rc = 0; // don't process troffrc and troffrc-end
+ int next_page_number = 0; // pacify compiler
+ opterr = 0;
+ hresolution = vresolution = 1;
+ // restore $PATH if called from groff
+ char* groff_path = getenv("GROFF_PATH__");
+ if (groff_path) {
+ string e = "PATH";
+ e += '=';
+ if (*groff_path)
+ e += groff_path;
+ e += '\0';
+ if (putenv(strsave(e.contents())))
+ fatal("putenv failed");
+ }
+ setlocale(LC_CTYPE, "");
+ static const struct option long_options[] = {
+ { "help", no_argument, 0, CHAR_MAX + 1 },
+ { "version", no_argument, 0, 'v' },
+ { 0, 0, 0, 0 }
+ };
+#if defined(DEBUGGING)
+#define DEBUG_OPTION "D"
+#else
+#define DEBUG_OPTION ""
+#endif
+ while ((c = getopt_long(argc, argv,
+ "abciI:vw:W:zCEf:m:n:o:r:d:F:M:T:tqs:RU"
+ DEBUG_OPTION, long_options, 0))
+ != EOF)
+ switch(c) {
+ case 'v':
+ {
+ printf("GNU troff (groff) version %s\n", Version_string);
+ exit(0);
+ break;
+ }
+ case 'I':
+ // Search path for .psbb files
+ // and most other non-system input files.
+ include_search_path.command_line_dir(optarg);
+ break;
+ case 'T':
+ device = optarg;
+ tflag = 1;
+ is_html = (strcmp(device, "html") == 0);
+ break;
+ case 'C':
+ compatible_flag = 1;
+ // fall through
+ case 'c':
+ color_flag = 0;
+ break;
+ case 'M':
+ macro_path.command_line_dir(optarg);
+ safer_macro_path.command_line_dir(optarg);
+ config_macro_path.command_line_dir(optarg);
+ break;
+ case 'F':
+ font::command_line_font_dir(optarg);
+ break;
+ case 'm':
+ add_string(optarg, &macros);
+ break;
+ case 'E':
+ inhibit_errors = 1;
+ break;
+ case 'R':
+ no_rc = 1;
+ break;
+ case 'w':
+ enable_warning(optarg);
+ break;
+ case 'W':
+ disable_warning(optarg);
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'b':
+ backtrace_flag = 1;
+ break;
+ case 'a':
+ ascii_output_flag = 1;
+ break;
+ case 'z':
+ suppress_output_flag = 1;
+ break;
+ case 'n':
+ if (sscanf(optarg, "%d", &next_page_number) == 1)
+ nflag++;
+ else
+ error("bad page number");
+ break;
+ case 'o':
+ parse_output_page_list(optarg);
+ break;
+ case 'd':
+ if (*optarg == '\0')
+ error("'-d' requires non-empty argument");
+ else if (*optarg == '=')
+ error("malformed argument to '-d'; string name cannot be empty"
+ " or contain an equals sign");
+ else
+ add_string(optarg, &string_assignments);
+ break;
+ case 'r':
+ if (*optarg == '\0')
+ error("'-r' requires non-empty argument");
+ else if (*optarg == '=')
+ error("malformed argument to '-r'; register name cannot be"
+ " empty or contain an equals sign");
+ else
+ add_string(optarg, &register_assignments);
+ break;
+ case 'f':
+ default_family = symbol(optarg);
+ fflag = 1;
+ break;
+ case 'q':
+ case 's':
+ case 't':
+ // silently ignore these
+ break;
+ case 'U':
+ unsafe_flag = 1; // unsafe behaviour
+ break;
+#if defined(DEBUGGING)
+ case 'D':
+ debug_state = 1;
+ break;
+#endif
+ case CHAR_MAX + 1: // --help
+ usage(stdout, argv[0]);
+ exit(0);
+ break;
+ case '?':
+ usage(stderr, argv[0]);
+ exit(1);
+ break; // never reached
+ default:
+ assert(0);
+ }
+ if (unsafe_flag)
+ mac_path = &macro_path;
+ set_string(".T", device);
+ init_charset_table();
+ init_hpf_code_table();
+ if (0 /* nullptr */ == font::load_desc())
+ fatal("cannot load 'DESC' description file for device '%1'",
+ device);
+ units_per_inch = font::res;
+ hresolution = font::hor;
+ vresolution = font::vert;
+ sizescale = font::sizescale;
+ device_has_tcommand = font::has_tcommand;
+ warn_scale = (double)units_per_inch;
+ warn_scaling_indicator = 'i';
+ if (!fflag && font::family != 0 && *font::family != '\0')
+ default_family = symbol(font::family);
+ font_size::init_size_table(font::sizes);
+ int i;
+ int j = 1;
+ if (font::style_table)
+ for (i = 0; font::style_table[i]; i++)
+ // Mounting a style can't actually fail due to a bad style name;
+ // that's not determined until the full font name is resolved.
+ // The DESC file also can't provoke a problem by requesting over a
+ // thousand slots in the style table.
+ if (!mount_style(j++, symbol(font::style_table[i])))
+ warning(WARN_FONT, "cannot mount style '%1' directed by 'DESC'"
+ " file for device '%2'", font::style_table[i], device);
+ for (i = 0; font::font_name_table[i]; i++, j++)
+ // In the DESC file, a font name of 0 (zero) means "leave this
+ // position empty".
+ if (strcmp(font::font_name_table[i], "0") != 0)
+ if (!mount_font(j, symbol(font::font_name_table[i])))
+ warning(WARN_FONT, "cannot mount font '%1' directed by 'DESC'"
+ " file for device '%2'", font::font_name_table[i],
+ device);
+ curdiv = topdiv = new top_level_diversion;
+ if (nflag)
+ topdiv->set_next_page_number(next_page_number);
+ init_input_requests();
+ init_env_requests();
+ init_div_requests();
+#ifdef COLUMN
+ init_column_requests();
+#endif /* COLUMN */
+ init_node_requests();
+ register_dictionary.define(".T", new readonly_text_register(tflag ? "1" : "0"));
+ init_registers();
+ init_reg_requests();
+ init_hyphen_requests();
+ init_environments();
+ while (string_assignments) {
+ do_string_assignment(string_assignments->s);
+ string_list *tem = string_assignments;
+ string_assignments = string_assignments->next;
+ delete tem;
+ }
+ while (register_assignments) {
+ do_register_assignment(register_assignments->s);
+ string_list *tem = register_assignments;
+ register_assignments = register_assignments->next;
+ delete tem;
+ }
+ if (!no_rc)
+ process_startup_file(INITIAL_STARTUP_FILE);
+ while (macros) {
+ process_macro_file(macros->s);
+ string_list *tem = macros;
+ macros = macros->next;
+ delete tem;
+ }
+ if (!no_rc)
+ process_startup_file(FINAL_STARTUP_FILE);
+ for (i = optind; i < argc; i++)
+ process_input_file(argv[i]);
+ if (optind >= argc || iflag)
+ process_input_file("-");
+ exit_troff();
+ return 0; // not reached
+}
+
+void warn_request()
+{
+ int n;
+ if (has_arg() && get_integer(&n)) {
+ if (n & ~WARN_TOTAL) {
+ warning(WARN_RANGE, "warning mask must be between 0 and %1", WARN_TOTAL);
+ n &= WARN_TOTAL;
+ }
+ warning_mask = n;
+ }
+ else
+ warning_mask = WARN_TOTAL;
+ skip_line();
+}
+
+static void init_registers()
+{
+#ifdef LONG_FOR_TIME_T
+ long
+#else /* not LONG_FOR_TIME_T */
+ time_t
+#endif /* not LONG_FOR_TIME_T */
+ t = current_time();
+ struct tm *tt = localtime(&t);
+ set_number_reg("seconds", int(tt->tm_sec));
+ set_number_reg("minutes", int(tt->tm_min));
+ set_number_reg("hours", int(tt->tm_hour));
+ set_number_reg("dw", int(tt->tm_wday + 1));
+ set_number_reg("dy", int(tt->tm_mday));
+ set_number_reg("mo", int(tt->tm_mon + 1));
+ set_number_reg("year", int(1900 + tt->tm_year));
+ set_number_reg("yr", int(tt->tm_year));
+ set_number_reg("$$", getpid());
+ register_dictionary.define(".A",
+ new readonly_text_register(ascii_output_flag
+ ? "1"
+ : "0"));
+}
+
+/*
+ * registers associated with \O
+ */
+
+static int output_reg_minx_contents = -1;
+static int output_reg_miny_contents = -1;
+static int output_reg_maxx_contents = -1;
+static int output_reg_maxy_contents = -1;
+
+void check_output_limits(int x, int y)
+{
+ if ((output_reg_minx_contents == -1) || (x < output_reg_minx_contents))
+ output_reg_minx_contents = x;
+ if (x > output_reg_maxx_contents)
+ output_reg_maxx_contents = x;
+ if ((output_reg_miny_contents == -1) || (y < output_reg_miny_contents))
+ output_reg_miny_contents = y;
+ if (y > output_reg_maxy_contents)
+ output_reg_maxy_contents = y;
+}
+
+void reset_output_registers()
+{
+ output_reg_minx_contents = -1;
+ output_reg_miny_contents = -1;
+ output_reg_maxx_contents = -1;
+ output_reg_maxy_contents = -1;
+}
+
+void get_output_registers(int *minx, int *miny, int *maxx, int *maxy)
+{
+ *minx = output_reg_minx_contents;
+ *miny = output_reg_miny_contents;
+ *maxx = output_reg_maxx_contents;
+ *maxy = output_reg_maxy_contents;
+}
+
+void init_input_requests()
+{
+ init_request("ab", abort_request);
+ init_request("als", alias_macro);
+ init_request("am", append_macro);
+ init_request("am1", append_nocomp_macro);
+ init_request("ami", append_indirect_macro);
+ init_request("ami1", append_indirect_nocomp_macro);
+ init_request("as", append_string);
+ init_request("as1", append_nocomp_string);
+ init_request("asciify", asciify_macro);
+ init_request("backtrace", backtrace_request);
+ init_request("blm", blank_line_macro);
+ init_request("break", while_break_request);
+ init_request("cf", copy_file);
+ init_request("cflags", char_flags);
+ init_request("char", define_character);
+ init_request("chop", chop_macro);
+ init_request("class", define_class);
+ init_request("close", close_request);
+ init_request("color", activate_color);
+ init_request("composite", composite_request);
+ init_request("continue", while_continue_request);
+ init_request("cp", compatible);
+ init_request("de", define_macro);
+ init_request("de1", define_nocomp_macro);
+ init_request("defcolor", define_color);
+ init_request("dei", define_indirect_macro);
+ init_request("dei1", define_indirect_nocomp_macro);
+ init_request("device", device_request);
+ init_request("devicem", device_macro_request);
+ init_request("do", do_request);
+ init_request("ds", define_string);
+ init_request("ds1", define_nocomp_string);
+ init_request("ec", set_escape_char);
+ init_request("ecr", restore_escape_char);
+ init_request("ecs", save_escape_char);
+ init_request("el", else_request);
+ init_request("em", eoi_macro);
+ init_request("eo", escape_off);
+ init_request("ex", exit_request);
+ init_request("fchar", define_fallback_character);
+#ifdef WIDOW_CONTROL
+ init_request("fpl", flush_pending_lines);
+#endif /* WIDOW_CONTROL */
+ init_request("hcode", hyphenation_code);
+ init_request("hpfcode", hyphenation_patterns_file_code);
+ init_request("ie", if_else_request);
+ init_request("if", if_request);
+ init_request("ig", ignore);
+ init_request("length", length_request);
+ init_request("lf", line_file);
+ init_request("lsm", leading_spaces_macro);
+ init_request("mso", macro_source);
+ init_request("msoquiet", macro_source_quietly);
+ init_request("nop", nop_request);
+ init_request("nroff", nroff_request);
+ init_request("nx", next_file);
+ init_request("open", open_request);
+ init_request("opena", opena_request);
+ init_request("output", output_request);
+ init_request("pc", set_page_character);
+ init_request("pi", pipe_output);
+ init_request("pm", print_macros);
+ init_request("psbb", ps_bbox_request);
+#ifndef POPEN_MISSING
+ init_request("pso", pipe_source);
+#endif /* not POPEN_MISSING */
+ init_request("rchar", remove_character);
+ init_request("rd", read_request);
+ init_request("return", return_macro_request);
+ init_request("rm", remove_macro);
+ init_request("rn", rename_macro);
+ init_request("schar", define_special_character);
+ init_request("shift", shift);
+ init_request("so", source);
+ init_request("soquiet", source_quietly);
+ init_request("spreadwarn", spreadwarn_request);
+ init_request("stringdown", stringdown_request);
+ init_request("stringup", stringup_request);
+ init_request("substring", substring_request);
+ init_request("sy", system_request);
+ init_request("tag", tag);
+ init_request("taga", taga);
+ init_request("tm", terminal);
+ init_request("tm1", terminal1);
+ init_request("tmc", terminal_continue);
+ init_request("tr", translate);
+ init_request("trf", transparent_file);
+ init_request("trin", translate_input);
+ init_request("trnt", translate_no_transparent);
+ init_request("troff", troff_request);
+ init_request("unformat", unformat_macro);
+#ifdef COLUMN
+ init_request("vj", vjustify);
+#endif /* COLUMN */
+ init_request("warn", warn_request);
+ init_request("warnscale", warnscale_request);
+ init_request("while", while_request);
+ init_request("write", write_request);
+ init_request("writec", write_request_continue);
+ init_request("writem", write_macro_request);
+ register_dictionary.define(".$", new nargs_reg);
+ register_dictionary.define(".br", new break_flag_reg);
+ register_dictionary.define(".C", new readonly_register(&compatible_flag));
+ register_dictionary.define(".cp", new readonly_register(&do_old_compatible_flag));
+ register_dictionary.define(".O", new variable_reg(&begin_level));
+ register_dictionary.define(".c", new lineno_reg);
+ register_dictionary.define(".color", new readonly_register(&color_flag));
+ register_dictionary.define(".F", new filename_reg);
+ register_dictionary.define(".g", new readonly_text_register("1"));
+ register_dictionary.define(".H", new readonly_register(&hresolution));
+ register_dictionary.define(".R", new readonly_text_register("10000"));
+ register_dictionary.define(".U", new readonly_register(&unsafe_flag));
+ register_dictionary.define(".V", new readonly_register(&vresolution));
+ register_dictionary.define(".warn", new readonly_register(&warning_mask));
+ extern const char *major_version;
+ register_dictionary.define(".x", new readonly_text_register(major_version));
+ extern const char *revision;
+ register_dictionary.define(".Y", new readonly_text_register(revision));
+ extern const char *minor_version;
+ register_dictionary.define(".y", new readonly_text_register(minor_version));
+ register_dictionary.define("c.", new writable_lineno_reg);
+ register_dictionary.define("llx", new variable_reg(&llx_reg_contents));
+ register_dictionary.define("lly", new variable_reg(&lly_reg_contents));
+ register_dictionary.define("lsn", new variable_reg(&leading_spaces_number));
+ register_dictionary.define("lss", new variable_reg(&leading_spaces_space));
+ register_dictionary.define("opmaxx",
+ new variable_reg(&output_reg_maxx_contents));
+ register_dictionary.define("opmaxy",
+ new variable_reg(&output_reg_maxy_contents));
+ register_dictionary.define("opminx",
+ new variable_reg(&output_reg_minx_contents));
+ register_dictionary.define("opminy",
+ new variable_reg(&output_reg_miny_contents));
+ register_dictionary.define("slimit",
+ new variable_reg(&input_stack::limit));
+ register_dictionary.define("systat", new variable_reg(&system_status));
+ register_dictionary.define("urx", new variable_reg(&urx_reg_contents));
+ register_dictionary.define("ury", new variable_reg(&ury_reg_contents));
+}
+
+object_dictionary request_dictionary(501);
+
+void init_request(const char *s, REQUEST_FUNCP f)
+{
+ request_dictionary.define(s, new request(f));
+}
+
+static request_or_macro *lookup_request(symbol nm)
+{
+ assert(!nm.is_null());
+ request_or_macro *p = (request_or_macro *)request_dictionary.lookup(nm);
+ if (p == 0) {
+ warning(WARN_MAC, "macro '%1' not defined", nm.contents());
+ p = new macro;
+ request_dictionary.define(nm, p);
+ }
+ return p;
+}
+
+node *charinfo_to_node_list(charinfo *ci, const environment *envp)
+{
+ // Don't interpret character definitions in compatible mode.
+ int old_compatible_flag = compatible_flag;
+ compatible_flag = 0;
+ int old_escape_char = escape_char;
+ escape_char = '\\';
+ macro *mac = ci->set_macro(0);
+ assert(mac != 0);
+ environment *oldenv = curenv;
+ environment env(envp);
+ curenv = &env;
+ curenv->set_composite();
+ token old_tok = tok;
+ input_stack::add_boundary();
+ string_iterator *si =
+ new string_iterator(*mac, "composite character", ci->nm);
+ input_stack::push(si);
+ // we don't use process_input_stack, because we don't want to recognise
+ // requests
+ for (;;) {
+ tok.next();
+ if (tok.is_eof())
+ break;
+ if (tok.is_newline()) {
+ error("composite character mustn't contain newline");
+ while (!tok.is_eof())
+ tok.next();
+ break;
+ }
+ else
+ tok.process();
+ }
+ node *n = curenv->extract_output_line();
+ input_stack::remove_boundary();
+ ci->set_macro(mac);
+ tok = old_tok;
+ curenv = oldenv;
+ compatible_flag = old_compatible_flag;
+ escape_char = old_escape_char;
+ have_input = 0;
+ return n;
+}
+
+static node *read_draw_node()
+{
+ token start;
+ start.next();
+ if (!start.usable_as_delimiter(true /* report error */)){
+ do {
+ tok.next();
+ } while (tok != start && !tok.is_newline() && !tok.is_eof());
+ }
+ else {
+ tok.next();
+ if (tok == start)
+ error("missing argument");
+ else {
+ unsigned char type = tok.ch();
+ if (type == 'F') {
+ read_color_draw_node(start);
+ return 0;
+ }
+ tok.next();
+ int maxpoints = 10;
+ hvpair *point = new hvpair[maxpoints];
+ int npoints = 0;
+ int no_last_v = 0;
+ bool err = false;
+ int i;
+ for (i = 0; tok != start; i++) {
+ if (i == maxpoints) {
+ hvpair *oldpoint = point;
+ point = new hvpair[maxpoints*2];
+ for (int j = 0; j < maxpoints; j++)
+ point[j] = oldpoint[j];
+ maxpoints *= 2;
+ delete[] oldpoint;
+ }
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in drawing"
+ " escape sequence (got %1)", tok.description());
+ err = true;
+ break;
+ }
+ if (!get_hunits(&point[i].h,
+ type == 'f' || type == 't' ? 'u' : 'm')) {
+ err = true;
+ break;
+ }
+ ++npoints;
+ tok.skip();
+ point[i].v = V0;
+ if (tok == start) {
+ no_last_v = 1;
+ break;
+ }
+ if (!get_vunits(&point[i].v, 'v')) {
+ err = false;
+ break;
+ }
+ tok.skip();
+ }
+ while (tok != start && !tok.is_newline() && !tok.is_eof())
+ tok.next();
+ if (!err) {
+ switch (type) {
+ case 'l':
+ if (npoints != 1 || no_last_v) {
+ error("two arguments needed for line");
+ npoints = 1;
+ }
+ break;
+ case 'c':
+ if (npoints != 1 || !no_last_v) {
+ error("one argument needed for circle");
+ npoints = 1;
+ point[0].v = V0;
+ }
+ break;
+ case 'e':
+ if (npoints != 1 || no_last_v) {
+ error("two arguments needed for ellipse");
+ npoints = 1;
+ }
+ break;
+ case 'a':
+ if (npoints != 2 || no_last_v) {
+ error("four arguments needed for arc");
+ npoints = 2;
+ }
+ break;
+ case '~':
+ if (no_last_v)
+ error("even number of arguments needed for spline");
+ break;
+ case 'f':
+ if (npoints != 1 || !no_last_v) {
+ error("one argument needed for gray shade");
+ npoints = 1;
+ point[0].v = V0;
+ }
+ default:
+ // silently pass it through
+ break;
+ }
+ draw_node *dn = new draw_node(type, point, npoints,
+ curenv->get_font_size(),
+ curenv->get_glyph_color(),
+ curenv->get_fill_color());
+ delete[] point;
+ return dn;
+ }
+ else {
+ delete[] point;
+ }
+ }
+ }
+ return 0;
+}
+
+static void read_color_draw_node(token &start)
+{
+ tok.next();
+ if (tok == start) {
+ error("missing color scheme");
+ return;
+ }
+ unsigned char scheme = tok.ch();
+ tok.next();
+ color *col = 0;
+ char end = start.ch();
+ switch (scheme) {
+ case 'c':
+ col = read_cmy(end);
+ break;
+ case 'd':
+ col = &default_color;
+ break;
+ case 'g':
+ col = read_gray(end);
+ break;
+ case 'k':
+ col = read_cmyk(end);
+ break;
+ case 'r':
+ col = read_rgb(end);
+ break;
+ }
+ if (col)
+ curenv->set_fill_color(col);
+ while (tok != start) {
+ if (tok.is_newline() || tok.is_eof()) {
+ warning(WARN_DELIM, "missing closing delimiter in color space"
+ " drawing escape sequence (got %1)", tok.description());
+ input_stack::push(make_temp_iterator("\n"));
+ break;
+ }
+ tok.next();
+ }
+ have_input = 1;
+}
+
+static struct {
+ const char *name;
+ int mask;
+} warning_table[] = {
+ { "char", WARN_CHAR },
+ { "range", WARN_RANGE },
+ { "break", WARN_BREAK },
+ { "delim", WARN_DELIM },
+ { "el", WARN_EL },
+ { "scale", WARN_SCALE },
+ { "number", WARN_NUMBER },
+ { "syntax", WARN_SYNTAX },
+ { "tab", WARN_TAB },
+ { "right-brace", WARN_RIGHT_BRACE },
+ { "missing", WARN_MISSING },
+ { "input", WARN_INPUT },
+ { "escape", WARN_ESCAPE },
+ { "space", WARN_SPACE },
+ { "font", WARN_FONT },
+ { "di", WARN_DI },
+ { "mac", WARN_MAC },
+ { "reg", WARN_REG },
+ { "ig", WARN_IG },
+ { "color", WARN_COLOR },
+ { "file", WARN_FILE },
+ { "all", WARN_TOTAL & ~(WARN_DI | WARN_MAC | WARN_REG) },
+ { "w", WARN_TOTAL },
+ { "default", DEFAULT_WARNING_MASK },
+};
+
+static int lookup_warning(const char *name)
+{
+ for (unsigned int i = 0;
+ i < sizeof(warning_table)/sizeof(warning_table[0]);
+ i++)
+ if (strcmp(name, warning_table[i].name) == 0)
+ return warning_table[i].mask;
+ return 0;
+}
+
+static void enable_warning(const char *name)
+{
+ int mask = lookup_warning(name);
+ if (mask)
+ warning_mask |= mask;
+ else
+ error("unrecognized warning category '%1'", name);
+}
+
+static void disable_warning(const char *name)
+{
+ int mask = lookup_warning(name);
+ if (mask)
+ warning_mask &= ~mask;
+ else
+ error("unrecognized warning category '%1'", name);
+}
+
+static void copy_mode_error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (ignoring) {
+ static const char prefix[] = "(in ignored input) ";
+ char *s = new char[sizeof(prefix) + strlen(format)];
+ strcpy(s, prefix);
+ strcat(s, format);
+ warning(WARN_IG, s, arg1, arg2, arg3);
+ delete[] s;
+ }
+ else
+ error(format, arg1, arg2, arg3);
+}
+
+enum error_type { DEBUG, WARNING, OUTPUT_WARNING, ERROR, FATAL };
+
+static void do_error(error_type type,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ const char *filename;
+ int lineno;
+ if (inhibit_errors && type < FATAL)
+ return;
+ if (backtrace_flag)
+ input_stack::backtrace();
+ if (!get_file_line(&filename, &lineno))
+ filename = 0;
+ if (filename) {
+ if (program_name)
+ errprint("%1:", program_name);
+ errprint("%1:%2: ", filename, lineno);
+ }
+ else if (program_name)
+ fprintf(stderr, "%s: ", program_name);
+ switch (type) {
+ case FATAL:
+ fputs("fatal error: ", stderr);
+ break;
+ case ERROR:
+ fputs("error: ", stderr);
+ break;
+ case WARNING:
+ fputs("warning: ", stderr);
+ break;
+ case DEBUG:
+ fputs("debug: ", stderr);
+ break;
+ case OUTPUT_WARNING:
+ double fromtop = topdiv->get_vertical_position().to_units() / warn_scale;
+ fprintf(stderr, "warning [p %d, %.1f%c",
+ topdiv->get_page_number(), fromtop, warn_scaling_indicator);
+ if (topdiv != curdiv) {
+ double fromtop1 = curdiv->get_vertical_position().to_units()
+ / warn_scale;
+ fprintf(stderr, ", div '%s', %.1f%c",
+ curdiv->get_diversion_name(), fromtop1, warn_scaling_indicator);
+ }
+ fprintf(stderr, "]: ");
+ break;
+ }
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+ if (type == FATAL)
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+void debug(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(DEBUG, format, arg1, arg2, arg3);
+}
+
+int warning(warning_type t,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if ((t & warning_mask) != 0) {
+ do_error(WARNING, format, arg1, arg2, arg3);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int output_warning(warning_type t,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if ((t & warning_mask) != 0) {
+ do_error(OUTPUT_WARNING, format, arg1, arg2, arg3);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void error(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(ERROR, format, arg1, arg2, arg3);
+}
+
+void fatal(const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ do_error(FATAL, format, arg1, arg2, arg3);
+}
+
+void fatal_with_file_and_line(const char *filename, int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" fatal error: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+ cleanup_and_exit(EXIT_FAILURE);
+}
+
+void error_with_file_and_line(const char *filename, int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" error: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+void debug_with_file_and_line(const char *filename,
+ int lineno,
+ const char *format,
+ const errarg &arg1,
+ const errarg &arg2,
+ const errarg &arg3)
+{
+ if (program_name)
+ fprintf(stderr, "%s:", program_name);
+ fprintf(stderr, "%s:", filename);
+ if (lineno > 0)
+ fprintf(stderr, "%d:", lineno);
+ fputs(" debug: ", stderr);
+ errprint(format, arg1, arg2, arg3);
+ fputc('\n', stderr);
+ fflush(stderr);
+}
+
+dictionary charinfo_dictionary(501);
+
+charinfo *get_charinfo(symbol nm)
+{
+ void *p = charinfo_dictionary.lookup(nm);
+ if (p != 0)
+ return (charinfo *)p;
+ charinfo *cp = new charinfo(nm);
+ (void)charinfo_dictionary.lookup(nm, cp);
+ return cp;
+}
+
+int charinfo::next_index = 0;
+
+charinfo::charinfo(symbol s)
+: translation(0), mac(0), special_translation(TRANSLATE_NONE),
+ hyphenation_code(0), flags(0), ascii_code(0), asciify_code(0),
+ not_found(0), transparent_translate(1), translate_input(0),
+ mode(CHAR_NORMAL), nm(s)
+{
+ index = next_index++;
+ number = -1;
+ get_flags();
+}
+
+int charinfo::get_unicode_code()
+{
+ if (ascii_code != '\0')
+ return ascii_code;
+ return glyph_to_unicode(this);
+}
+
+void charinfo::set_hyphenation_code(unsigned char c)
+{
+ hyphenation_code = c;
+}
+
+void charinfo::set_translation(charinfo *ci, int tt, int ti)
+{
+ translation = ci;
+ if (ci && ti) {
+ if (hyphenation_code != 0)
+ ci->set_hyphenation_code(hyphenation_code);
+ if (asciify_code != 0)
+ ci->set_asciify_code(asciify_code);
+ else if (ascii_code != 0)
+ ci->set_asciify_code(ascii_code);
+ ci->set_translation_input();
+ }
+ special_translation = TRANSLATE_NONE;
+ transparent_translate = tt;
+}
+
+// Recompute flags for all entries in the charinfo dictionary.
+void get_flags()
+{
+ dictionary_iterator iter(charinfo_dictionary);
+ charinfo *ci;
+ symbol s;
+ while (iter.get(&s, (void **)&ci)) {
+ assert(!s.is_null());
+ ci->get_flags();
+ }
+ class_flag = 0;
+}
+
+// Get the union of all flags affecting this charinfo.
+void charinfo::get_flags()
+{
+ dictionary_iterator iter(char_class_dictionary);
+ charinfo *ci;
+ symbol s;
+ while (iter.get(&s, (void **)&ci)) {
+ assert(!s.is_null());
+ if (ci->contains(get_unicode_code())) {
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "charinfo::get_flags %p %s %d\n",
+ (void *)ci, ci->nm.contents(), ci->flags);
+#endif
+ flags |= ci->flags;
+ }
+ }
+}
+
+void charinfo::set_special_translation(int c, int tt)
+{
+ special_translation = c;
+ translation = 0;
+ transparent_translate = tt;
+}
+
+void charinfo::set_ascii_code(unsigned char c)
+{
+ ascii_code = c;
+}
+
+void charinfo::set_asciify_code(unsigned char c)
+{
+ asciify_code = c;
+}
+
+macro *charinfo::set_macro(macro *m)
+{
+ macro *tem = mac;
+ mac = m;
+ return tem;
+}
+
+macro *charinfo::setx_macro(macro *m, char_mode cm)
+{
+ macro *tem = mac;
+ mac = m;
+ mode = cm;
+ return tem;
+}
+
+void charinfo::set_number(int n)
+{
+ assert(n >= 0);
+ number = n;
+}
+
+int charinfo::get_number()
+{
+ assert(number >= 0);
+ return number;
+}
+
+bool charinfo::contains(int c, bool already_called)
+{
+ if (already_called) {
+ warning(WARN_SYNTAX,
+ "cyclic nested class detected while processing character code %1",
+ c);
+ return false;
+ }
+ std::vector<std::pair<int, int> >::const_iterator ranges_iter;
+ ranges_iter = ranges.begin();
+ while (ranges_iter != ranges.end()) {
+ if (c >= ranges_iter->first && c <= ranges_iter->second) {
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "charinfo::contains(%d)\n", c);
+#endif
+ return true;
+ }
+ ++ranges_iter;
+ }
+
+ std::vector<charinfo *>::const_iterator nested_iter;
+ nested_iter = nested_classes.begin();
+ while (nested_iter != nested_classes.end()) {
+ if ((*nested_iter)->contains(c, true))
+ return true;
+ ++nested_iter;
+ }
+
+ return false;
+}
+
+bool charinfo::contains(symbol s, bool already_called)
+{
+ if (already_called) {
+ warning(WARN_SYNTAX,
+ "cyclic nested class detected while processing symbol %1",
+ s.contents());
+ return false;
+ }
+ const char *unicode = glyph_name_to_unicode(s.contents());
+ if (unicode != 0 && strchr(unicode, '_') == 0) {
+ char *ignore;
+ int c = (int)strtol(unicode, &ignore, 16);
+ return contains(c, true);
+ }
+ else
+ return false;
+}
+
+bool charinfo::contains(charinfo *, bool)
+{
+ // TODO
+ return false;
+}
+
+symbol UNNAMED_SYMBOL("---");
+
+// For numbered characters not between 0 and 255, we make a symbol out
+// of the number and store them in this dictionary.
+
+dictionary numbered_charinfo_dictionary(11);
+
+charinfo *get_charinfo_by_number(int n)
+{
+ static charinfo *number_table[256];
+
+ if (n >= 0 && n < 256) {
+ charinfo *ci = number_table[n];
+ if (!ci) {
+ ci = new charinfo(UNNAMED_SYMBOL);
+ ci->set_number(n);
+ number_table[n] = ci;
+ }
+ return ci;
+ }
+ else {
+ symbol ns(i_to_a(n));
+ charinfo *ci = (charinfo *)numbered_charinfo_dictionary.lookup(ns);
+ if (!ci) {
+ ci = new charinfo(UNNAMED_SYMBOL);
+ ci->set_number(n);
+ (void)numbered_charinfo_dictionary.lookup(ns, ci);
+ }
+ return ci;
+ }
+}
+
+// This overrides the same function from libgroff; while reading font
+// definition files it puts single-letter glyph names into
+// 'charset_table' and converts glyph names of the form '\x' ('x' a
+// single letter) into 'x'. Consequently, symbol("x") refers to glyph
+// name '\x', not 'x'.
+
+glyph *name_to_glyph(const char *nm)
+{
+ charinfo *ci;
+ if (nm[1] == 0)
+ ci = charset_table[nm[0] & 0xff];
+ else if (nm[0] == '\\' && nm[2] == 0)
+ ci = get_charinfo(symbol(nm + 1));
+ else
+ ci = get_charinfo(symbol(nm));
+ return ci->as_glyph();
+}
+
+glyph *number_to_glyph(int n)
+{
+ return get_charinfo_by_number(n)->as_glyph();
+}
+
+const char *glyph_to_name(glyph *g)
+{
+ charinfo *ci = (charinfo *)g; // Every glyph is actually a charinfo.
+ return (ci->nm != UNNAMED_SYMBOL ? ci->nm.contents() : 0);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/input.h b/src/roff/troff/input.h
new file mode 100644
index 0000000..e78124f
--- /dev/null
+++ b/src/roff/troff/input.h
@@ -0,0 +1,120 @@
+/* Copyright (C) 2001-2022 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* special character codes */
+
+#ifndef IS_EBCDIC_HOST
+
+const int ESCAPE_QUESTION = 015;
+const int BEGIN_TRAP = 016;
+const int END_TRAP = 017;
+const int PAGE_EJECTOR = 020;
+const int ESCAPE_NEWLINE = 021;
+const int ESCAPE_AMPERSAND = 022;
+const int ESCAPE_UNDERSCORE = 023;
+const int ESCAPE_BAR = 024;
+const int ESCAPE_CIRCUMFLEX = 025;
+const int ESCAPE_LEFT_BRACE = 026;
+const int ESCAPE_RIGHT_BRACE = 027;
+const int ESCAPE_LEFT_QUOTE = 030;
+const int ESCAPE_RIGHT_QUOTE = 031;
+const int ESCAPE_HYPHEN = 032;
+const int ESCAPE_BANG = 033;
+const int ESCAPE_c = 034;
+const int ESCAPE_e = 035;
+const int ESCAPE_PERCENT = 036;
+const int ESCAPE_SPACE = 037;
+
+const int INPUT_DELETE = 0177;
+
+const int TITLE_REQUEST = 0200;
+const int COPY_FILE_REQUEST = 0201;
+const int TRANSPARENT_FILE_REQUEST = 0202;
+#ifdef COLUMN
+const int VJUSTIFY_REQUEST = 0203;
+#endif /* COLUMN */
+const int ESCAPE_E = 0204;
+const int LAST_PAGE_EJECTOR = 0205;
+const int ESCAPE_RIGHT_PARENTHESIS = 0206;
+const int ESCAPE_TILDE = 0207;
+const int ESCAPE_COLON = 0210;
+const int PUSH_GROFF_MODE = 0211;
+const int PUSH_COMP_MODE = 0212;
+const int POP_GROFFCOMP_MODE = 0213;
+const int BEGIN_QUOTE = 0214;
+const int END_QUOTE = 0215;
+const int DOUBLE_QUOTE = 0216;
+const int INPUT_NO_BREAK_SPACE = 0240;
+const int INPUT_SOFT_HYPHEN= 0255;
+
+#else /* IS_EBCDIC_HOST */
+
+const int INPUT_DELETE = 007;
+
+const int ESCAPE_QUESTION = 010;
+const int BEGIN_TRAP = 011;
+const int END_TRAP = 013;
+const int PAGE_EJECTOR = 015;
+const int ESCAPE_NEWLINE = 016;
+const int ESCAPE_AMPERSAND = 017;
+const int ESCAPE_UNDERSCORE = 020;
+const int ESCAPE_BAR = 021;
+const int ESCAPE_CIRCUMFLEX = 022;
+const int ESCAPE_LEFT_BRACE = 023;
+const int ESCAPE_RIGHT_BRACE = 024;
+const int ESCAPE_LEFT_QUOTE = 027;
+const int ESCAPE_RIGHT_QUOTE = 030;
+const int ESCAPE_HYPHEN = 031;
+const int ESCAPE_BANG = 032;
+const int ESCAPE_c = 033;
+const int ESCAPE_e = 034;
+const int ESCAPE_PERCENT = 035;
+const int ESCAPE_SPACE = 036;
+
+const int TITLE_REQUEST = 060;
+const int COPY_FILE_REQUEST = 061;
+const int TRANSPARENT_FILE_REQUEST = 062;
+#ifdef COLUMN
+const int VJUSTIFY_REQUEST = 063;
+#endif /* COLUMN */
+const int ESCAPE_E = 064;
+const int LAST_PAGE_EJECTOR = 065;
+const int ESCAPE_RIGHT_PARENTHESIS = 066;
+const int ESCAPE_TILDE = 067;
+const int ESCAPE_COLON = 070;
+const int PUSH_GROFF_MODE = 071;
+const int PUSH_COMP_MODE = 072;
+const int POP_GROFFCOMP_MODE = 073;
+const int BEGIN_QUOTE = 074;
+const int END_QUOTE = 075;
+const int DOUBLE_QUOTE = 076;
+
+const int INPUT_NO_BREAK_SPACE = 0101;
+const int INPUT_SOFT_HYPHEN= 0312;
+
+#endif /* IS_EBCDIC_HOST */
+
+extern void do_glyph_color(symbol);
+extern void do_fill_color(symbol);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/mtsm.cpp b/src/roff/troff/mtsm.cpp
new file mode 100644
index 0000000..058b9b1
--- /dev/null
+++ b/src/roff/troff/mtsm.cpp
@@ -0,0 +1,651 @@
+/* Copyright (C) 2003-2020 Free Software Foundation, Inc.
+ Written by Gaius Mulley (gaius@glam.ac.uk)
+
+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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+// mtsm: minimum troff state machine
+
+extern int debug_state;
+
+#include "troff.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+
+#if defined(DEBUGGING)
+static int no_of_statems = 0;
+#endif
+
+int_value::int_value()
+: value(0), is_known(0)
+{
+}
+
+int_value::~int_value()
+{
+}
+
+void int_value::diff(FILE *fp, const char *s, int_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs(" ", fp);
+ fputs(i_to_a(compare.value), fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ if (debug_state)
+ fflush(fp);
+ }
+}
+
+void int_value::set(int v)
+{
+ is_known = 1;
+ value = v;
+}
+
+void int_value::unset()
+{
+ is_known = 0;
+}
+
+void int_value::set_if_unknown(int v)
+{
+ if (!is_known)
+ set(v);
+}
+
+int int_value::differs(int_value compare)
+{
+ return compare.is_known
+ && (!is_known || value != compare.value);
+}
+
+bool_value::bool_value()
+{
+}
+
+bool_value::~bool_value()
+{
+}
+
+void bool_value::diff(FILE *fp, const char *s, bool_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ if (debug_state)
+ fflush(fp);
+ }
+}
+
+units_value::units_value()
+{
+}
+
+units_value::~units_value()
+{
+}
+
+void units_value::diff(FILE *fp, const char *s, units_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs(" ", fp);
+ fputs(i_to_a(compare.value), fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ if (debug_state)
+ fflush(fp);
+ }
+}
+
+void units_value::set(hunits v)
+{
+ is_known = 1;
+ value = v.to_units();
+}
+
+int units_value::differs(units_value compare)
+{
+ return compare.is_known
+ && (!is_known || value != compare.value);
+}
+
+string_value::string_value()
+: value(string("")), is_known(0)
+{
+}
+
+string_value::~string_value()
+{
+}
+
+void string_value::diff(FILE *fp, const char *s, string_value compare)
+{
+ if (differs(compare)) {
+ fputs("x X ", fp);
+ fputs(s, fp);
+ fputs(" ", fp);
+ fputs(compare.value.contents(), fp);
+ fputs("\n", fp);
+ value = compare.value;
+ is_known = 1;
+ }
+}
+
+void string_value::set(string v)
+{
+ is_known = 1;
+ value = v;
+}
+
+void string_value::unset()
+{
+ is_known = 0;
+}
+
+int string_value::differs(string_value compare)
+{
+ return compare.is_known
+ && (!is_known || value != compare.value);
+}
+
+statem::statem()
+{
+#if defined(DEBUGGING)
+ issue_no = no_of_statems;
+ no_of_statems++;
+#endif
+}
+
+statem::statem(statem *copy)
+{
+ int i;
+ for (i = 0; i < LAST_BOOL; i++)
+ bool_values[i] = copy->bool_values[i];
+ for (i = 0; i < LAST_INT; i++)
+ int_values[i] = copy->int_values[i];
+ for (i = 0; i < LAST_UNITS; i++)
+ units_values[i] = copy->units_values[i];
+ for (i = 0; i < LAST_STRING; i++)
+ string_values[i] = copy->string_values[i];
+#if defined(DEBUGGING)
+ issue_no = copy->issue_no;
+#endif
+}
+
+statem::~statem()
+{
+}
+
+void statem::flush(FILE *fp, statem *compare)
+{
+ int_values[MTSM_FI].diff(fp, "devtag:.fi",
+ compare->int_values[MTSM_FI]);
+ int_values[MTSM_RJ].diff(fp, "devtag:.rj",
+ compare->int_values[MTSM_RJ]);
+ int_values[MTSM_SP].diff(fp, "devtag:.sp",
+ compare->int_values[MTSM_SP]);
+ units_values[MTSM_IN].diff(fp, "devtag:.in",
+ compare->units_values[MTSM_IN]);
+ units_values[MTSM_LL].diff(fp, "devtag:.ll",
+ compare->units_values[MTSM_LL]);
+ units_values[MTSM_PO].diff(fp, "devtag:.po",
+ compare->units_values[MTSM_PO]);
+ string_values[MTSM_TA].diff(fp, "devtag:.ta",
+ compare->string_values[MTSM_TA]);
+ units_values[MTSM_TI].diff(fp, "devtag:.ti",
+ compare->units_values[MTSM_TI]);
+ int_values[MTSM_CE].diff(fp, "devtag:.ce",
+ compare->int_values[MTSM_CE]);
+ bool_values[MTSM_EOL].diff(fp, "devtag:.eol",
+ compare->bool_values[MTSM_EOL]);
+ bool_values[MTSM_BR].diff(fp, "devtag:.br",
+ compare->bool_values[MTSM_BR]);
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "compared state %d\n", compare->issue_no);
+ fflush(stderr);
+ }
+#endif
+}
+
+void statem::add_tag(int_value_state t, int v)
+{
+ int_values[t].set(v);
+}
+
+void statem::add_tag(units_value_state t, hunits v)
+{
+ units_values[t].set(v);
+}
+
+void statem::add_tag(bool_value_state t)
+{
+ bool_values[t].set(1);
+}
+
+void statem::add_tag(string_value_state t, string v)
+{
+ string_values[t].set(v);
+}
+
+void statem::add_tag_if_unknown(int_value_state t, int v)
+{
+ int_values[t].set_if_unknown(v);
+}
+
+void statem::sub_tag_ce()
+{
+ int_values[MTSM_CE].unset();
+}
+
+/*
+ * add_tag_ta - add the tab settings to the minimum troff state machine
+ */
+
+void statem::add_tag_ta()
+{
+ if (is_html) {
+ string s = string("");
+ hunits d, l;
+ enum tab_type t;
+ do {
+ t = curenv->tabs.distance_to_next_tab(l, &d);
+ l += d;
+ switch (t) {
+ case TAB_LEFT:
+ s += " L ";
+ s += as_string(l.to_units());
+ break;
+ case TAB_CENTER:
+ s += " C ";
+ s += as_string(l.to_units());
+ break;
+ case TAB_RIGHT:
+ s += " R ";
+ s += as_string(l.to_units());
+ break;
+ case TAB_NONE:
+ break;
+ }
+ } while (t != TAB_NONE && l < curenv->get_line_length());
+ s += '\0';
+ string_values[MTSM_TA].set(s);
+ }
+}
+
+void statem::update(statem *older, statem *newer, int_value_state t)
+{
+ if (newer->int_values[t].differs(older->int_values[t])
+ && !newer->int_values[t].is_known)
+ newer->int_values[t].set(older->int_values[t].value);
+}
+
+void statem::update(statem *older, statem *newer, units_value_state t)
+{
+ if (newer->units_values[t].differs(older->units_values[t])
+ && !newer->units_values[t].is_known)
+ newer->units_values[t].set(older->units_values[t].value);
+}
+
+void statem::update(statem *older, statem *newer, bool_value_state t)
+{
+ if (newer->bool_values[t].differs(older->bool_values[t])
+ && !newer->bool_values[t].is_known)
+ newer->bool_values[t].set(older->bool_values[t].value);
+}
+
+void statem::update(statem *older, statem *newer, string_value_state t)
+{
+ if (newer->string_values[t].differs(older->string_values[t])
+ && !newer->string_values[t].is_known)
+ newer->string_values[t].set(older->string_values[t].value);
+}
+
+void statem::merge(statem *newer, statem *older)
+{
+ if (newer == 0 || older == 0)
+ return;
+ update(older, newer, MTSM_EOL);
+ update(older, newer, MTSM_BR);
+ update(older, newer, MTSM_FI);
+ update(older, newer, MTSM_LL);
+ update(older, newer, MTSM_PO);
+ update(older, newer, MTSM_RJ);
+ update(older, newer, MTSM_SP);
+ update(older, newer, MTSM_TA);
+ update(older, newer, MTSM_TI);
+ update(older, newer, MTSM_CE);
+}
+
+stack::stack()
+: next(0), state(0)
+{
+}
+
+stack::stack(statem *s, stack *n)
+: next(n), state(s)
+{
+}
+
+stack::~stack()
+{
+ if (state)
+ delete state;
+ if (next)
+ delete next;
+}
+
+mtsm::mtsm()
+: sp(0)
+{
+ driver = new statem();
+}
+
+mtsm::~mtsm()
+{
+ delete driver;
+ if (sp)
+ delete sp;
+}
+
+/*
+ * push_state - push the current troff state and use 'n' as
+ * the new troff state.
+ */
+
+void mtsm::push_state(statem *n)
+{
+ if (is_html) {
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "--> state %d pushed\n", n->issue_no);
+ fflush(stderr);
+ }
+#endif
+ sp = new stack(n, sp);
+ }
+}
+
+void mtsm::pop_state()
+{
+ if (is_html) {
+#if defined(DEBUGGING)
+ if (debug_state) {
+ fprintf(stderr, "--> state popped\n");
+ fflush(stderr);
+ }
+#endif
+ if (sp == 0)
+ fatal("empty state machine stack");
+ sp->state = 0;
+ stack *t = sp;
+ sp = sp->next;
+ t->next = 0;
+ delete t;
+ }
+}
+
+/*
+ * inherit - scan the stack and collects inherited values.
+ */
+
+void mtsm::inherit(statem *s, int reset_bool)
+{
+ if (sp && sp->state) {
+ if (s->units_values[MTSM_IN].is_known
+ && sp->state->units_values[MTSM_IN].is_known)
+ s->units_values[MTSM_IN].value += sp->state->units_values[MTSM_IN].value;
+ s->update(sp->state, s, MTSM_FI);
+ s->update(sp->state, s, MTSM_LL);
+ s->update(sp->state, s, MTSM_PO);
+ s->update(sp->state, s, MTSM_RJ);
+ s->update(sp->state, s, MTSM_TA);
+ s->update(sp->state, s, MTSM_TI);
+ s->update(sp->state, s, MTSM_CE);
+ if (sp->state->bool_values[MTSM_BR].is_known
+ && sp->state->bool_values[MTSM_BR].value) {
+ if (reset_bool)
+ sp->state->bool_values[MTSM_BR].set(0);
+ s->bool_values[MTSM_BR].set(1);
+#if defined(DEBUGGING)
+ if (debug_state)
+ fprintf(stderr, "inherited br from pushed state %d\n",
+ sp->state->issue_no);
+#endif
+ }
+ else if (s->bool_values[MTSM_BR].is_known
+ && s->bool_values[MTSM_BR].value)
+ if (! s->int_values[MTSM_CE].is_known)
+ s->bool_values[MTSM_BR].unset();
+ if (sp->state->bool_values[MTSM_EOL].is_known
+ && sp->state->bool_values[MTSM_EOL].value) {
+ if (reset_bool)
+ sp->state->bool_values[MTSM_EOL].set(0);
+ s->bool_values[MTSM_EOL].set(1);
+ }
+ }
+}
+
+void mtsm::flush(FILE *fp, statem *s, string tag_list)
+{
+ if (is_html && s) {
+ inherit(s, 1);
+ driver->flush(fp, s);
+ // Set rj, ce, ti to unknown if they were known and
+ // we have seen an eol or br. This ensures that these values
+ // are emitted during the next glyph (as they step from n..0
+ // at each newline).
+ if ((driver->bool_values[MTSM_EOL].is_known
+ && driver->bool_values[MTSM_EOL].value)
+ || (driver->bool_values[MTSM_BR].is_known
+ && driver->bool_values[MTSM_BR].value)) {
+ if (driver->units_values[MTSM_TI].is_known)
+ driver->units_values[MTSM_TI].is_known = 0;
+ if (driver->int_values[MTSM_RJ].is_known
+ && driver->int_values[MTSM_RJ].value > 0)
+ driver->int_values[MTSM_RJ].is_known = 0;
+ if (driver->int_values[MTSM_CE].is_known
+ && driver->int_values[MTSM_CE].value > 0)
+ driver->int_values[MTSM_CE].is_known = 0;
+ }
+ // reset the boolean values
+ driver->bool_values[MTSM_BR].set(0);
+ driver->bool_values[MTSM_EOL].set(0);
+ // reset space value
+ driver->int_values[MTSM_SP].set(0);
+ // lastly write out any direct tag entries
+ if (tag_list != string("")) {
+ string t = tag_list + '\0';
+ fputs(t.contents(), fp);
+ }
+ }
+}
+
+/*
+ * display_state - dump out a synopsis of the state to stderr.
+ */
+
+void statem::display_state()
+{
+ fprintf(stderr, " <state ");
+ if (bool_values[MTSM_BR].is_known) {
+ if (bool_values[MTSM_BR].value)
+ fprintf(stderr, "[br]");
+ else
+ fprintf(stderr, "[!br]");
+ }
+ if (bool_values[MTSM_EOL].is_known) {
+ if (bool_values[MTSM_EOL].value)
+ fprintf(stderr, "[eol]");
+ else
+ fprintf(stderr, "[!eol]");
+ }
+ if (int_values[MTSM_SP].is_known) {
+ if (int_values[MTSM_SP].value)
+ fprintf(stderr, "[sp %d]", int_values[MTSM_SP].value);
+ else
+ fprintf(stderr, "[!sp]");
+ }
+ fprintf(stderr, ">");
+ fflush(stderr);
+}
+
+int mtsm::has_changed(int_value_state t, statem *s)
+{
+ return driver->int_values[t].differs(s->int_values[t]);
+}
+
+int mtsm::has_changed(units_value_state t, statem *s)
+{
+ return driver->units_values[t].differs(s->units_values[t]);
+}
+
+int mtsm::has_changed(bool_value_state t, statem *s)
+{
+ return driver->bool_values[t].differs(s->bool_values[t]);
+}
+
+int mtsm::has_changed(string_value_state t, statem *s)
+{
+ return driver->string_values[t].differs(s->string_values[t]);
+}
+
+int mtsm::changed(statem *s)
+{
+ if (s == 0 || !is_html)
+ return 0;
+ s = new statem(s);
+ inherit(s, 0);
+ int result = has_changed(MTSM_EOL, s)
+ || has_changed(MTSM_BR, s)
+ || has_changed(MTSM_FI, s)
+ || has_changed(MTSM_IN, s)
+ || has_changed(MTSM_LL, s)
+ || has_changed(MTSM_PO, s)
+ || has_changed(MTSM_RJ, s)
+ || has_changed(MTSM_SP, s)
+ || has_changed(MTSM_TA, s)
+ || has_changed(MTSM_CE, s);
+ delete s;
+ return result;
+}
+
+void mtsm::add_tag(FILE *fp, string s)
+{
+ fflush(fp);
+ s += '\0';
+ fputs(s.contents(), fp);
+}
+
+/*
+ * state_set class
+ */
+
+state_set::state_set()
+: boolset(0), intset(0), unitsset(0), stringset(0)
+{
+}
+
+state_set::~state_set()
+{
+}
+
+void state_set::incl(bool_value_state b)
+{
+ boolset |= 1 << (int)b;
+}
+
+void state_set::incl(int_value_state i)
+{
+ intset |= 1 << (int)i;
+}
+
+void state_set::incl(units_value_state u)
+{
+ unitsset |= 1 << (int)u;
+}
+
+void state_set::incl(string_value_state s)
+{
+ stringset |= 1 << (int)s;
+}
+
+void state_set::excl(bool_value_state b)
+{
+ boolset &= ~(1 << (int)b);
+}
+
+void state_set::excl(int_value_state i)
+{
+ intset &= ~(1 << (int)i);
+}
+
+void state_set::excl(units_value_state u)
+{
+ unitsset &= ~(1 << (int)u);
+}
+
+void state_set::excl(string_value_state s)
+{
+ stringset &= ~(1 << (int)s);
+}
+
+int state_set::is_in(bool_value_state b)
+{
+ return (boolset & (1 << (int)b)) != 0;
+}
+
+int state_set::is_in(int_value_state i)
+{
+ return (intset & (1 << (int)i)) != 0;
+}
+
+int state_set::is_in(units_value_state u)
+{
+ return (unitsset & (1 << (int)u)) != 0;
+}
+
+int state_set::is_in(string_value_state s)
+{
+ return (stringset & (1 << (int)s)) != 0;
+}
+
+void state_set::add(units_value_state, int n)
+{
+ unitsset += n;
+}
+
+units state_set::val(units_value_state)
+{
+ return unitsset;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/mtsm.h b/src/roff/troff/mtsm.h
new file mode 100644
index 0000000..cfca73d
--- /dev/null
+++ b/src/roff/troff/mtsm.h
@@ -0,0 +1,165 @@
+// -*- C++ -*-
+/* Copyright (C) 2003-2020 Free Software Foundation, Inc.
+ *
+ * mtsm.h
+ *
+ * written by Gaius Mulley (gaius@glam.ac.uk)
+ *
+ * provides a minimal troff state machine which is necessary to
+ * emit meta tags for the post-grohtml device driver.
+ */
+
+/*
+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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct int_value {
+ int value;
+ int is_known;
+ int_value();
+ ~int_value();
+ void diff(FILE *, const char *, int_value);
+ int differs(int_value);
+ void set(int);
+ void unset();
+ void set_if_unknown(int);
+};
+
+struct bool_value : public int_value {
+ bool_value();
+ ~bool_value();
+ void diff(FILE *, const char *, bool_value);
+};
+
+struct units_value : public int_value {
+ units_value();
+ ~units_value();
+ void diff(FILE *, const char *, units_value);
+ int differs(units_value);
+ void set(hunits);
+};
+
+struct string_value {
+ string value;
+ int is_known;
+ string_value();
+ ~string_value();
+ void diff(FILE *, const char *, string_value);
+ int differs(string_value);
+ void set(string);
+ void unset();
+};
+
+enum bool_value_state {
+ MTSM_EOL,
+ MTSM_BR,
+ LAST_BOOL
+};
+enum int_value_state {
+ MTSM_FI,
+ MTSM_RJ,
+ MTSM_CE,
+ MTSM_SP,
+ LAST_INT
+};
+enum units_value_state {
+ MTSM_IN,
+ MTSM_LL,
+ MTSM_PO,
+ MTSM_TI,
+ LAST_UNITS
+};
+enum string_value_state {
+ MTSM_TA,
+ LAST_STRING
+};
+
+struct statem {
+#if defined(DEBUGGING)
+ int issue_no;
+#endif
+ bool_value bool_values[LAST_BOOL];
+ int_value int_values[LAST_INT];
+ units_value units_values[LAST_UNITS];
+ string_value string_values[LAST_STRING];
+ statem();
+ statem(statem *);
+ ~statem();
+ void flush(FILE *, statem *);
+ int changed(statem *);
+ void merge(statem *, statem *);
+ void add_tag(int_value_state, int);
+ void add_tag(bool_value_state);
+ void add_tag(units_value_state, hunits);
+ void add_tag(string_value_state, string);
+ void sub_tag_ce();
+ void add_tag_if_unknown(int_value_state, int);
+ void add_tag_ta();
+ void display_state();
+ void update(statem *, statem *, int_value_state);
+ void update(statem *, statem *, bool_value_state);
+ void update(statem *, statem *, units_value_state);
+ void update(statem *, statem *, string_value_state);
+};
+
+struct stack {
+ stack *next;
+ statem *state;
+ stack();
+ stack(statem *, stack *);
+ ~stack();
+};
+
+class mtsm {
+ statem *driver;
+ stack *sp;
+ int has_changed(int_value_state, statem *);
+ int has_changed(bool_value_state, statem *);
+ int has_changed(units_value_state, statem *);
+ int has_changed(string_value_state, statem *);
+ void inherit(statem *, int);
+public:
+ mtsm();
+ ~mtsm();
+ void push_state(statem *);
+ void pop_state();
+ void flush(FILE *, statem *, string);
+ int changed(statem *);
+ void add_tag(FILE *, string);
+};
+
+class state_set {
+ int boolset;
+ int intset;
+ int unitsset;
+ int stringset;
+public:
+ state_set();
+ ~state_set();
+ void incl(bool_value_state);
+ void incl(int_value_state);
+ void incl(units_value_state);
+ void incl(string_value_state);
+ void excl(bool_value_state);
+ void excl(int_value_state);
+ void excl(units_value_state);
+ void excl(string_value_state);
+ int is_in(bool_value_state);
+ int is_in(int_value_state);
+ int is_in(units_value_state);
+ int is_in(string_value_state);
+ void add(units_value_state, int);
+ units val(units_value_state);
+};
diff --git a/src/roff/troff/node.cpp b/src/roff/troff/node.cpp
new file mode 100644
index 0000000..d17198d
--- /dev/null
+++ b/src/roff/troff/node.cpp
@@ -0,0 +1,6656 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+extern int debug_state;
+
+#include "troff.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "dictionary.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "request.h"
+#include "node.h"
+#include "token.h"
+#include "div.h"
+#include "reg.h"
+#include "font.h"
+#include "charinfo.h"
+#include "input.h"
+#include "geometry.h"
+
+#include "nonposix.h"
+
+#ifdef _POSIX_VERSION
+
+#include <sys/wait.h>
+
+#else /* not _POSIX_VERSION */
+
+/* traditional Unix */
+
+#define WIFEXITED(s) (((s) & 0377) == 0)
+#define WEXITSTATUS(s) (((s) >> 8) & 0377)
+#define WTERMSIG(s) ((s) & 0177)
+#define WIFSTOPPED(s) (((s) & 0377) == 0177)
+#define WSTOPSIG(s) (((s) >> 8) & 0377)
+#define WIFSIGNALED(s) (((s) & 0377) != 0 && (((s) & 0377) != 0177))
+
+#endif /* not _POSIX_VERSION */
+
+// declarations to avoid friend name injections
+class tfont;
+class tfont_spec;
+tfont *make_tfont(tfont_spec &);
+
+
+/*
+ * how many boundaries of images have been written? Useful for
+ * debugging grohtml
+ */
+
+int image_no = 0;
+static int suppress_start_page = 0;
+
+#define STORE_WIDTH 1
+
+symbol HYPHEN_SYMBOL("hy");
+
+// Character used when a hyphen is inserted at a line break.
+static charinfo *soft_hyphen_char;
+
+enum constant_space_type {
+ CONSTANT_SPACE_NONE,
+ CONSTANT_SPACE_RELATIVE,
+ CONSTANT_SPACE_ABSOLUTE
+ };
+
+struct special_font_list {
+ int n;
+ special_font_list *next;
+};
+
+special_font_list *global_special_fonts;
+static int global_ligature_mode = 1;
+static int global_kern_mode = 1;
+
+class track_kerning_function {
+ int non_zero;
+ units min_size;
+ hunits min_amount;
+ units max_size;
+ hunits max_amount;
+public:
+ track_kerning_function();
+ track_kerning_function(units, hunits, units, hunits);
+ int operator==(const track_kerning_function &);
+ int operator!=(const track_kerning_function &);
+ hunits compute(int point_size);
+};
+
+struct font_lookup_info {
+ int position;
+ int requested_position;
+ char *requested_name;
+ font_lookup_info();
+};
+
+font_lookup_info::font_lookup_info() : position(-1),
+ requested_position(-1), requested_name(0)
+{
+}
+
+// embolden fontno when this is the current font
+
+struct conditional_bold {
+ conditional_bold *next;
+ int fontno;
+ hunits offset;
+ conditional_bold(int, hunits, conditional_bold * = 0);
+};
+
+class font_info {
+ tfont *last_tfont;
+ int number;
+ font_size last_size;
+ int last_height;
+ int last_slant;
+ symbol internal_name;
+ symbol external_name;
+ font *fm;
+ char is_bold;
+ hunits bold_offset;
+ track_kerning_function track_kern;
+ constant_space_type is_constant_spaced;
+ units constant_space;
+ int last_ligature_mode;
+ int last_kern_mode;
+ conditional_bold *cond_bold_list;
+ void flush();
+public:
+ special_font_list *sf;
+ font_info(symbol, int, symbol, font *);
+ int contains(charinfo *);
+ void set_bold(hunits);
+ void unbold();
+ void set_conditional_bold(int, hunits);
+ void conditional_unbold(int);
+ void set_track_kern(track_kerning_function &);
+ void set_constant_space(constant_space_type, units = 0);
+ int is_named(symbol);
+ symbol get_name();
+ tfont *get_tfont(font_size, int, int, int);
+ hunits get_space_width(font_size, int);
+ hunits get_narrow_space_width(font_size);
+ hunits get_half_narrow_space_width(font_size);
+ int get_bold(hunits *);
+ int is_special();
+ int is_style();
+ void set_zoom(int);
+ int get_zoom();
+ friend symbol get_font_name(int, environment *);
+ friend symbol get_style_name(int);
+};
+
+class tfont_spec {
+protected:
+ symbol name;
+ int input_position;
+ font *fm;
+ font_size size;
+ char is_bold;
+ char is_constant_spaced;
+ int ligature_mode;
+ int kern_mode;
+ hunits bold_offset;
+ hunits track_kern; // add this to the width
+ hunits constant_space_width;
+ int height;
+ int slant;
+public:
+ tfont_spec(symbol, int, font *, font_size, int, int);
+ tfont_spec(const tfont_spec &spec) { *this = spec; }
+ tfont_spec plain();
+ int operator==(const tfont_spec &);
+ friend tfont *font_info::get_tfont(font_size fs, int, int, int);
+};
+
+class tfont : public tfont_spec {
+ static tfont *tfont_list;
+ tfont *next;
+ tfont *plain_version;
+public:
+ tfont(tfont_spec &);
+ int contains(charinfo *);
+ hunits get_width(charinfo *c);
+ int get_bold(hunits *);
+ int get_constant_space(hunits *);
+ hunits get_track_kern();
+ tfont *get_plain();
+ font_size get_size();
+ int get_zoom();
+ symbol get_name();
+ charinfo *get_lig(charinfo *c1, charinfo *c2);
+ int get_kern(charinfo *c1, charinfo *c2, hunits *res);
+ int get_input_position();
+ int get_character_type(charinfo *);
+ int get_height();
+ int get_slant();
+ vunits get_char_height(charinfo *);
+ vunits get_char_depth(charinfo *);
+ hunits get_char_skew(charinfo *);
+ hunits get_italic_correction(charinfo *);
+ hunits get_left_italic_correction(charinfo *);
+ hunits get_subscript_correction(charinfo *);
+ friend tfont *make_tfont(tfont_spec &);
+};
+
+inline int env_definite_font(environment *env)
+{
+ return env->get_family()->make_definite(env->get_font());
+}
+
+/* font_info functions */
+
+static font_info **font_table = 0;
+static int font_table_size = 0;
+
+font_info::font_info(symbol nm, int n, symbol enm, font *f)
+: last_tfont(0), number(n), last_size(0),
+ internal_name(nm), external_name(enm), fm(f),
+ is_bold(0), is_constant_spaced(CONSTANT_SPACE_NONE), last_ligature_mode(1),
+ last_kern_mode(1), cond_bold_list(0), sf(0)
+{
+}
+
+inline int font_info::contains(charinfo *ci)
+{
+ return fm != 0 && fm->contains(ci->as_glyph());
+}
+
+inline int font_info::is_special()
+{
+ return fm != 0 && fm->is_special();
+}
+
+inline int font_info::is_style()
+{
+ return fm == 0;
+}
+
+void font_info::set_zoom(int zoom)
+{
+ assert(fm != 0);
+ fm->set_zoom(zoom);
+}
+
+inline int font_info::get_zoom()
+{
+ if (is_style())
+ return 0;
+ return fm->get_zoom();
+}
+
+tfont *make_tfont(tfont_spec &spec)
+{
+ for (tfont *p = tfont::tfont_list; p; p = p->next)
+ if (*p == spec)
+ return p;
+ return new tfont(spec);
+}
+
+int env_get_zoom(environment *env)
+{
+ int fontno = env->get_family()->make_definite(env->get_font());
+ return font_table[fontno]->get_zoom();
+}
+
+// this is the current_font, fontno is where we found the character,
+// presumably a special font
+
+tfont *font_info::get_tfont(font_size fs, int height, int slant, int fontno)
+{
+ if (last_tfont == 0 || fs != last_size
+ || height != last_height || slant != last_slant
+ || global_ligature_mode != last_ligature_mode
+ || global_kern_mode != last_kern_mode
+ || fontno != number) {
+ font_info *f = font_table[fontno];
+ tfont_spec spec(f->external_name, f->number, f->fm, fs, height, slant);
+ for (conditional_bold *p = cond_bold_list; p; p = p->next)
+ if (p->fontno == fontno) {
+ spec.is_bold = 1;
+ spec.bold_offset = p->offset;
+ break;
+ }
+ if (!spec.is_bold && is_bold) {
+ spec.is_bold = 1;
+ spec.bold_offset = bold_offset;
+ }
+ spec.track_kern = track_kern.compute(fs.to_scaled_points());
+ spec.ligature_mode = global_ligature_mode;
+ spec.kern_mode = global_kern_mode;
+ switch (is_constant_spaced) {
+ case CONSTANT_SPACE_NONE:
+ break;
+ case CONSTANT_SPACE_ABSOLUTE:
+ spec.is_constant_spaced = 1;
+ spec.constant_space_width = constant_space;
+ break;
+ case CONSTANT_SPACE_RELATIVE:
+ spec.is_constant_spaced = 1;
+ spec.constant_space_width
+ = scale(constant_space*fs.to_scaled_points(),
+ units_per_inch,
+ 36*72*sizescale);
+ break;
+ default:
+ assert(0);
+ }
+ if (fontno != number)
+ return make_tfont(spec);
+ // save font for comparison purposes
+ last_tfont = make_tfont(spec);
+ // save font related values not contained in tfont
+ last_size = fs;
+ last_height = height;
+ last_slant = slant;
+ last_ligature_mode = global_ligature_mode;
+ last_kern_mode = global_kern_mode;
+ }
+ return last_tfont;
+}
+
+int font_info::get_bold(hunits *res)
+{
+ if (is_bold) {
+ *res = bold_offset;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+void font_info::unbold()
+{
+ if (is_bold) {
+ is_bold = 0;
+ flush();
+ }
+}
+
+void font_info::set_bold(hunits offset)
+{
+ if (!is_bold || offset != bold_offset) {
+ is_bold = 1;
+ bold_offset = offset;
+ flush();
+ }
+}
+
+void font_info::set_conditional_bold(int fontno, hunits offset)
+{
+ for (conditional_bold *p = cond_bold_list; p; p = p->next)
+ if (p->fontno == fontno) {
+ if (offset != p->offset) {
+ p->offset = offset;
+ flush();
+ }
+ return;
+ }
+ cond_bold_list = new conditional_bold(fontno, offset, cond_bold_list);
+}
+
+conditional_bold::conditional_bold(int f, hunits h, conditional_bold *x)
+: next(x), fontno(f), offset(h)
+{
+}
+
+void font_info::conditional_unbold(int fontno)
+{
+ for (conditional_bold **p = &cond_bold_list; *p; p = &(*p)->next)
+ if ((*p)->fontno == fontno) {
+ conditional_bold *tem = *p;
+ *p = (*p)->next;
+ delete tem;
+ flush();
+ return;
+ }
+}
+
+void font_info::set_constant_space(constant_space_type type, units x)
+{
+ if (type != is_constant_spaced
+ || (type != CONSTANT_SPACE_NONE && x != constant_space)) {
+ flush();
+ is_constant_spaced = type;
+ constant_space = x;
+ }
+}
+
+void font_info::set_track_kern(track_kerning_function &tk)
+{
+ if (track_kern != tk) {
+ track_kern = tk;
+ flush();
+ }
+}
+
+void font_info::flush()
+{
+ last_tfont = 0;
+}
+
+int font_info::is_named(symbol s)
+{
+ return internal_name == s;
+}
+
+symbol font_info::get_name()
+{
+ return internal_name;
+}
+
+symbol get_font_name(int fontno, environment *env)
+{
+ symbol f = font_table[fontno]->get_name();
+ if (font_table[fontno]->is_style()) {
+ return concat(env->get_family()->nm, f);
+ }
+ return f;
+}
+
+symbol get_style_name(int fontno)
+{
+ if (font_table[fontno]->is_style())
+ return font_table[fontno]->get_name();
+ else
+ return EMPTY_SYMBOL;
+}
+
+hunits font_info::get_space_width(font_size fs, int space_sz)
+{
+ if (is_constant_spaced == CONSTANT_SPACE_NONE)
+ return scale(hunits(fm->get_space_width(fs.to_scaled_points())),
+ space_sz, 12);
+ else if (is_constant_spaced == CONSTANT_SPACE_ABSOLUTE)
+ return constant_space;
+ else
+ return scale(constant_space*fs.to_scaled_points(),
+ units_per_inch, 36*72*sizescale);
+}
+
+hunits font_info::get_narrow_space_width(font_size fs)
+{
+ charinfo *ci = get_charinfo(symbol("|"));
+ if (fm->contains(ci->as_glyph()))
+ return hunits(fm->get_width(ci->as_glyph(), fs.to_scaled_points()));
+ else
+ return hunits(fs.to_units()/6);
+}
+
+hunits font_info::get_half_narrow_space_width(font_size fs)
+{
+ charinfo *ci = get_charinfo(symbol("^"));
+ if (fm->contains(ci->as_glyph()))
+ return hunits(fm->get_width(ci->as_glyph(), fs.to_scaled_points()));
+ else
+ return hunits(fs.to_units()/12);
+}
+
+/* tfont */
+
+tfont_spec::tfont_spec(symbol nm, int n, font *f,
+ font_size s, int h, int sl)
+: name(nm), input_position(n), fm(f), size(s),
+ is_bold(0), is_constant_spaced(0), ligature_mode(1), kern_mode(1),
+ height(h), slant(sl)
+{
+ if (height == size.to_scaled_points())
+ height = 0;
+}
+
+int tfont_spec::operator==(const tfont_spec &spec)
+{
+ if (fm == spec.fm
+ && size == spec.size
+ && input_position == spec.input_position
+ && name == spec.name
+ && height == spec.height
+ && slant == spec.slant
+ && (is_bold
+ ? (spec.is_bold && bold_offset == spec.bold_offset)
+ : !spec.is_bold)
+ && track_kern == spec.track_kern
+ && (is_constant_spaced
+ ? (spec.is_constant_spaced
+ && constant_space_width == spec.constant_space_width)
+ : !spec.is_constant_spaced)
+ && ligature_mode == spec.ligature_mode
+ && kern_mode == spec.kern_mode)
+ return 1;
+ else
+ return 0;
+}
+
+tfont_spec tfont_spec::plain()
+{
+ return tfont_spec(name, input_position, fm, size, height, slant);
+}
+
+hunits tfont::get_width(charinfo *c)
+{
+ if (is_constant_spaced)
+ return constant_space_width;
+ else if (is_bold)
+ return (hunits(fm->get_width(c->as_glyph(), size.to_scaled_points()))
+ + track_kern + bold_offset);
+ else
+ return (hunits(fm->get_width(c->as_glyph(), size.to_scaled_points()))
+ + track_kern);
+}
+
+vunits tfont::get_char_height(charinfo *c)
+{
+ vunits v = fm->get_height(c->as_glyph(), size.to_scaled_points());
+ if (height != 0 && height != size.to_scaled_points())
+ return scale(v, height, size.to_scaled_points());
+ else
+ return v;
+}
+
+vunits tfont::get_char_depth(charinfo *c)
+{
+ vunits v = fm->get_depth(c->as_glyph(), size.to_scaled_points());
+ if (height != 0 && height != size.to_scaled_points())
+ return scale(v, height, size.to_scaled_points());
+ else
+ return v;
+}
+
+hunits tfont::get_char_skew(charinfo *c)
+{
+ return hunits(fm->get_skew(c->as_glyph(), size.to_scaled_points(), slant));
+}
+
+hunits tfont::get_italic_correction(charinfo *c)
+{
+ return hunits(fm->get_italic_correction(c->as_glyph(), size.to_scaled_points()));
+}
+
+hunits tfont::get_left_italic_correction(charinfo *c)
+{
+ return hunits(fm->get_left_italic_correction(c->as_glyph(),
+ size.to_scaled_points()));
+}
+
+hunits tfont::get_subscript_correction(charinfo *c)
+{
+ return hunits(fm->get_subscript_correction(c->as_glyph(),
+ size.to_scaled_points()));
+}
+
+inline int tfont::get_input_position()
+{
+ return input_position;
+}
+
+inline int tfont::contains(charinfo *ci)
+{
+ return fm->contains(ci->as_glyph());
+}
+
+inline int tfont::get_character_type(charinfo *ci)
+{
+ return fm->get_character_type(ci->as_glyph());
+}
+
+inline int tfont::get_bold(hunits *res)
+{
+ if (is_bold) {
+ *res = bold_offset;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+inline int tfont::get_constant_space(hunits *res)
+{
+ if (is_constant_spaced) {
+ *res = constant_space_width;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+inline hunits tfont::get_track_kern()
+{
+ return track_kern;
+}
+
+inline tfont *tfont::get_plain()
+{
+ return plain_version;
+}
+
+inline font_size tfont::get_size()
+{
+ return size;
+}
+
+inline int tfont::get_zoom()
+{
+ return fm->get_zoom();
+}
+
+inline symbol tfont::get_name()
+{
+ return name;
+}
+
+inline int tfont::get_height()
+{
+ return height;
+}
+
+inline int tfont::get_slant()
+{
+ return slant;
+}
+
+symbol SYMBOL_ff("ff");
+symbol SYMBOL_fi("fi");
+symbol SYMBOL_fl("fl");
+symbol SYMBOL_Fi("Fi");
+symbol SYMBOL_Fl("Fl");
+
+charinfo *tfont::get_lig(charinfo *c1, charinfo *c2)
+{
+ if (ligature_mode == 0)
+ return 0;
+ charinfo *ci = 0;
+ if (c1->get_ascii_code() == 'f') {
+ switch (c2->get_ascii_code()) {
+ case 'f':
+ if (fm->has_ligature(font::LIG_ff))
+ ci = get_charinfo(SYMBOL_ff);
+ break;
+ case 'i':
+ if (fm->has_ligature(font::LIG_fi))
+ ci = get_charinfo(SYMBOL_fi);
+ break;
+ case 'l':
+ if (fm->has_ligature(font::LIG_fl))
+ ci = get_charinfo(SYMBOL_fl);
+ break;
+ }
+ }
+ else if (ligature_mode != 2 && c1->nm == SYMBOL_ff) {
+ switch (c2->get_ascii_code()) {
+ case 'i':
+ if (fm->has_ligature(font::LIG_ffi))
+ ci = get_charinfo(SYMBOL_Fi);
+ break;
+ case 'l':
+ if (fm->has_ligature(font::LIG_ffl))
+ ci = get_charinfo(SYMBOL_Fl);
+ break;
+ }
+ }
+ if (ci != 0 && fm->contains(ci->as_glyph()))
+ return ci;
+ return 0;
+}
+
+inline int tfont::get_kern(charinfo *c1, charinfo *c2, hunits *res)
+{
+ if (kern_mode == 0)
+ return 0;
+ else {
+ int n = fm->get_kern(c1->as_glyph(),
+ c2->as_glyph(),
+ size.to_scaled_points());
+ if (n) {
+ *res = hunits(n);
+ return 1;
+ }
+ else
+ return 0;
+ }
+}
+
+tfont *tfont::tfont_list = 0;
+
+tfont::tfont(tfont_spec &spec) : tfont_spec(spec)
+{
+ next = tfont_list;
+ tfont_list = this;
+ tfont_spec plain_spec = plain();
+ tfont *p;
+ for (p = tfont_list; p; p = p->next)
+ if (*p == plain_spec) {
+ plain_version = p;
+ break;
+ }
+ if (!p)
+ plain_version = new tfont(plain_spec);
+}
+
+/* output_file */
+
+class real_output_file : public output_file {
+#ifndef POPEN_MISSING
+ int piped;
+#endif
+ int printing; // decision via optional page list
+ int output_on; // \O[0] or \O[1] escape sequences
+ virtual void really_transparent_char(unsigned char) = 0;
+ virtual void really_print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits width) = 0;
+ virtual void really_begin_page(int pageno, vunits page_length) = 0;
+ virtual void really_copy_file(hunits x, vunits y, const char *filename);
+ virtual void really_put_filename(const char *, int);
+ virtual void really_on();
+ virtual void really_off();
+public:
+ FILE *fp;
+ real_output_file();
+ ~real_output_file();
+ void flush();
+ void transparent_char(unsigned char);
+ void print_line(hunits x, vunits y, node *n, vunits before, vunits after, hunits width);
+ void begin_page(int pageno, vunits page_length);
+ void put_filename(const char *, int);
+ void on();
+ void off();
+ int is_on();
+ int is_printing();
+ void copy_file(hunits x, vunits y, const char *filename);
+};
+
+class suppress_output_file : public real_output_file {
+public:
+ suppress_output_file();
+ void really_transparent_char(unsigned char);
+ void really_print_line(hunits x, vunits y, node *n, vunits, vunits, hunits width);
+ void really_begin_page(int pageno, vunits page_length);
+};
+
+class ascii_output_file : public real_output_file {
+public:
+ ascii_output_file();
+ void really_transparent_char(unsigned char);
+ void really_print_line(hunits x, vunits y, node *n, vunits, vunits, hunits width);
+ void really_begin_page(int pageno, vunits page_length);
+ void outc(unsigned char c);
+ void outs(const char *s);
+};
+
+void ascii_output_file::outc(unsigned char c)
+{
+ fputc(c, fp);
+}
+
+void ascii_output_file::outs(const char *s)
+{
+ fputc('<', fp);
+ if (s)
+ fputs(s, fp);
+ fputc('>', fp);
+}
+
+struct hvpair;
+
+class troff_output_file : public real_output_file {
+ units hpos;
+ units vpos;
+ units output_vpos;
+ units output_hpos;
+ int force_motion;
+ int current_size;
+ int current_slant;
+ int current_height;
+ tfont *current_tfont;
+ color *current_fill_color;
+ color *current_glyph_color;
+ int current_font_number;
+ symbol *font_position;
+ int nfont_positions;
+ enum { TBUF_SIZE = 256 };
+ char tbuf[TBUF_SIZE];
+ int tbuf_len;
+ int tbuf_kern;
+ int begun_page;
+ int cur_div_level;
+ string tag_list;
+ void do_motion();
+ void put(char c);
+ void put(unsigned char c);
+ void put(int i);
+ void put(unsigned int i);
+ void put(const char *s);
+ void set_font(tfont *tf);
+ void flush_tbuf();
+public:
+ troff_output_file();
+ ~troff_output_file();
+ void trailer(vunits page_length);
+ void put_char(charinfo *, tfont *, color *, color *);
+ void put_char_width(charinfo *, tfont *, color *, color *, hunits, hunits);
+ void right(hunits);
+ void down(vunits);
+ void moveto(hunits, vunits);
+ void start_special(tfont *, color *, color *, int = 0);
+ void start_special();
+ void special_char(unsigned char c);
+ void end_special();
+ void word_marker();
+ void really_transparent_char(unsigned char c);
+ void really_print_line(hunits x, vunits y, node *n, vunits before, vunits after, hunits width);
+ void really_begin_page(int pageno, vunits page_length);
+ void really_copy_file(hunits x, vunits y, const char *filename);
+ void really_put_filename(const char *, int);
+ void really_on();
+ void really_off();
+ void draw(char, hvpair *, int, font_size, color *, color *);
+ void determine_line_limits (char code, hvpair *point, int npoints);
+ void check_charinfo(tfont *tf, charinfo *ci);
+ void glyph_color(color *c);
+ void fill_color(color *c);
+ int get_hpos() { return hpos; }
+ int get_vpos() { return vpos; }
+ void add_to_tag_list(string s);
+ friend void space_char_hmotion_node::tprint(troff_output_file *);
+ friend void unbreakable_space_node::tprint(troff_output_file *);
+};
+
+static void put_string(const char *s, FILE *fp)
+{
+ for (; *s != '\0'; ++s)
+ putc(*s, fp);
+}
+
+inline void troff_output_file::put(char c)
+{
+ putc(c, fp);
+}
+
+inline void troff_output_file::put(unsigned char c)
+{
+ putc(c, fp);
+}
+
+inline void troff_output_file::put(const char *s)
+{
+ put_string(s, fp);
+}
+
+inline void troff_output_file::put(int i)
+{
+ put_string(i_to_a(i), fp);
+}
+
+inline void troff_output_file::put(unsigned int i)
+{
+ put_string(ui_to_a(i), fp);
+}
+
+void troff_output_file::start_special(tfont *tf, color *gcol, color *fcol,
+ int no_init_string)
+{
+ set_font(tf);
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ if (!no_init_string)
+ put("x X ");
+}
+
+void troff_output_file::start_special()
+{
+ flush_tbuf();
+ do_motion();
+ put("x X ");
+}
+
+void troff_output_file::special_char(unsigned char c)
+{
+ put(c);
+ if (c == '\n')
+ put('+');
+}
+
+void troff_output_file::end_special()
+{
+ put('\n');
+}
+
+inline void troff_output_file::moveto(hunits h, vunits v)
+{
+ hpos = h.to_units();
+ vpos = v.to_units();
+}
+
+void troff_output_file::really_print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits)
+{
+ moveto(x, y);
+ while (n != 0) {
+ // Check whether we should push the current troff state and use
+ // the state at the start of the invocation of this diversion.
+ if (n->div_nest_level > cur_div_level && n->push_state) {
+ state.push_state(n->push_state);
+ cur_div_level = n->div_nest_level;
+ }
+ // Has the current diversion level decreased? Then we must pop the
+ // troff state.
+ while (n->div_nest_level < cur_div_level) {
+ state.pop_state();
+ cur_div_level = n->div_nest_level;
+ }
+ // Now check whether the state has changed.
+ if ((is_on() || n->force_tprint())
+ && (state.changed(n->state) || n->is_tag() || n->is_special)) {
+ flush_tbuf();
+ do_motion();
+ force_motion = 1;
+ flush();
+ state.flush(fp, n->state, tag_list);
+ tag_list = string("");
+ flush();
+ }
+ n->tprint(this);
+ n = n->next;
+ }
+ flush_tbuf();
+ // This ensures that transparent throughput will have a more predictable
+ // position.
+ do_motion();
+ force_motion = 1;
+ hpos = 0;
+ put('n');
+ put(before.to_units());
+ put(' ');
+ put(after.to_units());
+ put('\n');
+}
+
+inline void troff_output_file::word_marker()
+{
+ flush_tbuf();
+ if (is_on())
+ put('w');
+}
+
+inline void troff_output_file::right(hunits n)
+{
+ hpos += n.to_units();
+}
+
+inline void troff_output_file::down(vunits n)
+{
+ vpos += n.to_units();
+}
+
+void troff_output_file::do_motion()
+{
+ if (force_motion) {
+ put('V');
+ put(vpos);
+ put('\n');
+ put('H');
+ put(hpos);
+ put('\n');
+ }
+ else {
+ if (hpos != output_hpos) {
+ units n = hpos - output_hpos;
+ if (n > 0 && n < hpos) {
+ put('h');
+ put(n);
+ }
+ else {
+ put('H');
+ put(hpos);
+ }
+ put('\n');
+ }
+ if (vpos != output_vpos) {
+ units n = vpos - output_vpos;
+ if (n > 0 && n < vpos) {
+ put('v');
+ put(n);
+ }
+ else {
+ put('V');
+ put(vpos);
+ }
+ put('\n');
+ }
+ }
+ output_vpos = vpos;
+ output_hpos = hpos;
+ force_motion = 0;
+}
+
+void troff_output_file::flush_tbuf()
+{
+ if (!is_on()) {
+ tbuf_len = 0;
+ return;
+ }
+
+ if (tbuf_len == 0)
+ return;
+ if (tbuf_kern == 0)
+ put('t');
+ else {
+ put('u');
+ put(tbuf_kern);
+ put(' ');
+ }
+ check_output_limits(hpos, vpos);
+ check_output_limits(hpos, vpos - current_size);
+
+ for (int i = 0; i < tbuf_len; i++)
+ put(tbuf[i]);
+ put('\n');
+ tbuf_len = 0;
+}
+
+void troff_output_file::check_charinfo(tfont *tf, charinfo *ci)
+{
+ if (!is_on())
+ return;
+
+ int height = tf->get_char_height(ci).to_units();
+ int width = tf->get_width(ci).to_units()
+ + tf->get_italic_correction(ci).to_units();
+ int depth = tf->get_char_depth(ci).to_units();
+ check_output_limits(output_hpos, output_vpos - height);
+ check_output_limits(output_hpos + width, output_vpos + depth);
+}
+
+void troff_output_file::put_char_width(charinfo *ci, tfont *tf,
+ color *gcol, color *fcol,
+ hunits w, hunits k)
+{
+ int kk = k.to_units();
+ if (!is_on()) {
+ flush_tbuf();
+ hpos += w.to_units() + kk;
+ return;
+ }
+ set_font(tf);
+ unsigned char c = ci->get_ascii_code();
+ if (c == '\0') {
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ check_charinfo(tf, ci);
+ if (ci->numbered()) {
+ put('N');
+ put(ci->get_number());
+ }
+ else {
+ put('C');
+ const char *s = ci->nm.contents();
+ if (s[1] == 0) {
+ put('\\');
+ put(s[0]);
+ }
+ else
+ put(s);
+ }
+ put('\n');
+ hpos += w.to_units() + kk;
+ }
+ else if (device_has_tcommand) {
+ if (tbuf_len > 0 && hpos == output_hpos && vpos == output_vpos
+ && (!gcol || gcol == current_glyph_color)
+ && (!fcol || fcol == current_fill_color)
+ && kk == tbuf_kern
+ && tbuf_len < TBUF_SIZE) {
+ check_charinfo(tf, ci);
+ tbuf[tbuf_len++] = c;
+ output_hpos += w.to_units() + kk;
+ hpos = output_hpos;
+ return;
+ }
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ check_charinfo(tf, ci);
+ tbuf[tbuf_len++] = c;
+ output_hpos += w.to_units() + kk;
+ tbuf_kern = kk;
+ hpos = output_hpos;
+ }
+ else {
+ // flush_tbuf();
+ int n = hpos - output_hpos;
+ check_charinfo(tf, ci);
+ // check_output_limits(output_hpos, output_vpos);
+ if (vpos == output_vpos
+ && (!gcol || gcol == current_glyph_color)
+ && (!fcol || fcol == current_fill_color)
+ && n > 0 && n < 100 && !force_motion) {
+ put(char(n/10 + '0'));
+ put(char(n%10 + '0'));
+ put(c);
+ output_hpos = hpos;
+ }
+ else {
+ glyph_color(gcol);
+ fill_color(fcol);
+ do_motion();
+ put('c');
+ put(c);
+ }
+ hpos += w.to_units() + kk;
+ }
+}
+
+void troff_output_file::put_char(charinfo *ci, tfont *tf,
+ color *gcol, color *fcol)
+{
+ flush_tbuf();
+ if (!is_on())
+ return;
+ set_font(tf);
+ unsigned char c = ci->get_ascii_code();
+ if (c == '\0') {
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ if (ci->numbered()) {
+ put('N');
+ put(ci->get_number());
+ }
+ else {
+ put('C');
+ const char *s = ci->nm.contents();
+ if (s[1] == 0) {
+ put('\\');
+ put(s[0]);
+ }
+ else
+ put(s);
+ }
+ put('\n');
+ }
+ else {
+ int n = hpos - output_hpos;
+ if (vpos == output_vpos
+ && (!gcol || gcol == current_glyph_color)
+ && (!fcol || fcol == current_fill_color)
+ && n > 0 && n < 100) {
+ put(char(n/10 + '0'));
+ put(char(n%10 + '0'));
+ put(c);
+ output_hpos = hpos;
+ }
+ else {
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ put('c');
+ put(c);
+ }
+ }
+}
+
+// set_font calls 'flush_tbuf' if necessary.
+
+void troff_output_file::set_font(tfont *tf)
+{
+ if (current_tfont == tf)
+ return;
+ flush_tbuf();
+ int n = tf->get_input_position();
+ symbol nm = tf->get_name();
+ if (n >= nfont_positions || font_position[n] != nm) {
+ put("x font ");
+ put(n);
+ put(' ');
+ put(nm.contents());
+ put('\n');
+ if (n >= nfont_positions) {
+ int old_nfont_positions = nfont_positions;
+ symbol *old_font_position = font_position;
+ nfont_positions *= 3;
+ nfont_positions /= 2;
+ if (nfont_positions <= n)
+ nfont_positions = n + 10;
+ font_position = new symbol[nfont_positions];
+ memcpy(font_position, old_font_position,
+ old_nfont_positions*sizeof(symbol));
+ delete[] old_font_position;
+ }
+ font_position[n] = nm;
+ }
+ if (current_font_number != n) {
+ put('f');
+ put(n);
+ put('\n');
+ current_font_number = n;
+ }
+ int zoom = tf->get_zoom();
+ int size;
+ if (zoom)
+ size = scale(tf->get_size().to_scaled_points(),
+ zoom, 1000);
+ else
+ size = tf->get_size().to_scaled_points();
+ if (current_size != size) {
+ put('s');
+ put(size);
+ put('\n');
+ current_size = size;
+ }
+ int slant = tf->get_slant();
+ if (current_slant != slant) {
+ put("x Slant ");
+ put(slant);
+ put('\n');
+ current_slant = slant;
+ }
+ int height = tf->get_height();
+ if (current_height != height) {
+ put("x Height ");
+ put(height == 0 ? current_size : height);
+ put('\n');
+ current_height = height;
+ }
+ current_tfont = tf;
+}
+
+// fill_color calls 'flush_tbuf' and 'do_motion' if necessary.
+
+void troff_output_file::fill_color(color *col)
+{
+ if (!col || current_fill_color == col)
+ return;
+ current_fill_color = col;
+ if (!color_flag)
+ return;
+ flush_tbuf();
+ do_motion();
+ put("DF");
+ unsigned int components[4];
+ color_scheme cs;
+ cs = col->get_components(components);
+ switch (cs) {
+ case DEFAULT:
+ put('d');
+ break;
+ case RGB:
+ put("r ");
+ put(Red);
+ put(' ');
+ put(Green);
+ put(' ');
+ put(Blue);
+ break;
+ case CMY:
+ put("c ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ break;
+ case CMYK:
+ put("k ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ put(' ');
+ put(Black);
+ break;
+ case GRAY:
+ put("g ");
+ put(Gray);
+ break;
+ }
+ put('\n');
+}
+
+// glyph_color calls 'flush_tbuf' and 'do_motion' if necessary.
+
+void troff_output_file::glyph_color(color *col)
+{
+ if (!col || current_glyph_color == col)
+ return;
+ current_glyph_color = col;
+ if (!color_flag)
+ return;
+ flush_tbuf();
+ // grotty doesn't like a color command if the vertical position is zero.
+ do_motion();
+ put("m");
+ unsigned int components[4];
+ color_scheme cs;
+ cs = col->get_components(components);
+ switch (cs) {
+ case DEFAULT:
+ put('d');
+ break;
+ case RGB:
+ put("r ");
+ put(Red);
+ put(' ');
+ put(Green);
+ put(' ');
+ put(Blue);
+ break;
+ case CMY:
+ put("c ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ break;
+ case CMYK:
+ put("k ");
+ put(Cyan);
+ put(' ');
+ put(Magenta);
+ put(' ');
+ put(Yellow);
+ put(' ');
+ put(Black);
+ break;
+ case GRAY:
+ put("g ");
+ put(Gray);
+ break;
+ }
+ put('\n');
+}
+
+void troff_output_file::add_to_tag_list(string s)
+{
+ if (tag_list == string(""))
+ tag_list = s;
+ else {
+ tag_list += string("\n");
+ tag_list += s;
+ }
+}
+
+// determine_line_limits - works out the smallest box which will contain
+// the entity, code, built from the point array.
+void troff_output_file::determine_line_limits(char code, hvpair *point,
+ int npoints)
+{
+ int i, x, y;
+
+ if (!is_on())
+ return;
+
+ switch (code) {
+ case 'c':
+ case 'C':
+ // only the h field is used when defining a circle
+ check_output_limits(output_hpos,
+ output_vpos - point[0].h.to_units()/2);
+ check_output_limits(output_hpos + point[0].h.to_units(),
+ output_vpos + point[0].h.to_units()/2);
+ break;
+ case 'E':
+ case 'e':
+ check_output_limits(output_hpos,
+ output_vpos - point[0].v.to_units()/2);
+ check_output_limits(output_hpos + point[0].h.to_units(),
+ output_vpos + point[0].v.to_units()/2);
+ break;
+ case 'P':
+ case 'p':
+ x = output_hpos;
+ y = output_vpos;
+ check_output_limits(x, y);
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ break;
+ case 't':
+ x = output_hpos;
+ y = output_vpos;
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ break;
+ case 'a':
+ double c[2];
+ int p[4];
+ int minx, miny, maxx, maxy;
+ x = output_hpos;
+ y = output_vpos;
+ p[0] = point[0].h.to_units();
+ p[1] = point[0].v.to_units();
+ p[2] = point[1].h.to_units();
+ p[3] = point[1].v.to_units();
+ if (adjust_arc_center(p, c)) {
+ check_output_arc_limits(x, y,
+ p[0], p[1], p[2], p[3],
+ c[0], c[1],
+ &minx, &maxx, &miny, &maxy);
+ check_output_limits(minx, miny);
+ check_output_limits(maxx, maxy);
+ break;
+ }
+ // fall through
+ case 'l':
+ x = output_hpos;
+ y = output_vpos;
+ check_output_limits(x, y);
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ break;
+ default:
+ x = output_hpos;
+ y = output_vpos;
+ for (i = 0; i < npoints; i++) {
+ x += point[i].h.to_units();
+ y += point[i].v.to_units();
+ check_output_limits(x, y);
+ }
+ }
+}
+
+void troff_output_file::draw(char code, hvpair *point, int npoints,
+ font_size fsize, color *gcol, color *fcol)
+{
+ int i;
+ glyph_color(gcol);
+ fill_color(fcol);
+ flush_tbuf();
+ do_motion();
+ if (is_on()) {
+ int size = fsize.to_scaled_points();
+ if (current_size != size) {
+ put('s');
+ put(size);
+ put('\n');
+ current_size = size;
+ current_tfont = 0;
+ }
+ put('D');
+ put(code);
+ if (code == 'c') {
+ put(' ');
+ put(point[0].h.to_units());
+ }
+ else
+ for (i = 0; i < npoints; i++) {
+ put(' ');
+ put(point[i].h.to_units());
+ put(' ');
+ put(point[i].v.to_units());
+ }
+ determine_line_limits(code, point, npoints);
+ }
+
+ for (i = 0; i < npoints; i++)
+ output_hpos += point[i].h.to_units();
+ hpos = output_hpos;
+ if (code != 'e') {
+ for (i = 0; i < npoints; i++)
+ output_vpos += point[i].v.to_units();
+ vpos = output_vpos;
+ }
+ if (is_on())
+ put('\n');
+}
+
+void troff_output_file::really_on()
+{
+ flush_tbuf();
+ force_motion = 1;
+ do_motion();
+}
+
+void troff_output_file::really_off()
+{
+ flush_tbuf();
+}
+
+void troff_output_file::really_put_filename(const char *filename, int po)
+{
+ flush_tbuf();
+ put("x F ");
+ if (po)
+ put("<");
+ put(filename);
+ if (po)
+ put(">");
+ put('\n');
+}
+
+void troff_output_file::really_begin_page(int pageno, vunits page_length)
+{
+ flush_tbuf();
+ if (begun_page) {
+ if (page_length > V0) {
+ put('V');
+ put(page_length.to_units());
+ put('\n');
+ }
+ }
+ else
+ begun_page = 1;
+ current_tfont = 0;
+ current_font_number = -1;
+ current_size = 0;
+ // current_height = 0;
+ // current_slant = 0;
+ hpos = 0;
+ vpos = 0;
+ output_hpos = 0;
+ output_vpos = 0;
+ force_motion = 1;
+ for (int i = 0; i < nfont_positions; i++)
+ font_position[i] = NULL_SYMBOL;
+ put('p');
+ put(pageno);
+ put('\n');
+}
+
+void troff_output_file::really_copy_file(hunits x, vunits y,
+ const char *filename)
+{
+ moveto(x, y);
+ flush_tbuf();
+ do_motion();
+ errno = 0;
+ FILE *ifp = include_search_path.open_file_cautious(filename);
+ if (ifp == 0)
+ error("can't open '%1': %2", filename, strerror(errno));
+ else {
+ int c;
+ while ((c = getc(ifp)) != EOF)
+ put(char(c));
+ fclose(ifp);
+ }
+ force_motion = 1;
+ current_size = 0;
+ current_tfont = 0;
+ current_font_number = -1;
+ for (int i = 0; i < nfont_positions; i++)
+ font_position[i] = NULL_SYMBOL;
+}
+
+void troff_output_file::really_transparent_char(unsigned char c)
+{
+ put(c);
+}
+
+troff_output_file::~troff_output_file()
+{
+ delete[] font_position;
+}
+
+void troff_output_file::trailer(vunits page_length)
+{
+ flush_tbuf();
+ if (page_length > V0) {
+ put("x trailer\n");
+ put('V');
+ put(page_length.to_units());
+ put('\n');
+ }
+ put("x stop\n");
+}
+
+troff_output_file::troff_output_file()
+: current_slant(0), current_height(0), current_fill_color(0),
+ current_glyph_color(0), nfont_positions(10), tbuf_len(0), begun_page(0),
+ cur_div_level(0)
+{
+ font_position = new symbol[nfont_positions];
+ put("x T ");
+ put(device);
+ put('\n');
+ put("x res ");
+ put(units_per_inch);
+ put(' ');
+ put(hresolution);
+ put(' ');
+ put(vresolution);
+ put('\n');
+ put("x init\n");
+}
+
+/* output_file */
+
+output_file *the_output = 0;
+
+output_file::output_file()
+{
+ is_dying = false;
+}
+
+output_file::~output_file()
+{
+}
+
+void output_file::trailer(vunits)
+{
+}
+
+void output_file::put_filename(const char *, int)
+{
+}
+
+void output_file::on()
+{
+}
+
+void output_file::off()
+{
+}
+
+real_output_file::real_output_file()
+: printing(0), output_on(1)
+{
+#ifndef POPEN_MISSING
+ if (pipe_command) {
+ if ((fp = popen(pipe_command, POPEN_WT)) != 0) {
+ piped = 1;
+ return;
+ }
+ error("pipe open failed: %1", strerror(errno));
+ }
+ piped = 0;
+#endif /* not POPEN_MISSING */
+ fp = stdout;
+}
+
+real_output_file::~real_output_file()
+{
+ if (!fp)
+ return;
+ // Prevent destructor from recursing; see div.cpp:cleanup_and_exit().
+ is_dying = true;
+ // To avoid looping, set fp to 0 before calling fatal().
+ if (ferror(fp)) {
+ fp = 0;
+ fatal("error on output file stream");
+ }
+ else if (fflush(fp) < 0) {
+ fp = 0;
+ fatal("unable to flush output file: %1", strerror(errno));
+ }
+#ifndef POPEN_MISSING
+ if (piped) {
+ int result = pclose(fp);
+ fp = 0;
+ if (result < 0)
+ fatal("unable to close pipe: %1", strerror(errno));
+ if (!WIFEXITED(result))
+ error("output process '%1' got fatal signal %2",
+ pipe_command,
+ WIFSIGNALED(result) ? WTERMSIG(result) : WSTOPSIG(result));
+ else {
+ int exit_status = WEXITSTATUS(result);
+ if (exit_status != 0)
+ error("output process '%1' exited with status %2",
+ pipe_command, exit_status);
+ }
+ }
+ else
+#endif /* not POPEN MISSING */
+ if (fclose(fp) < 0) {
+ fp = 0;
+ fatal("unable to close output file: %1", strerror(errno));
+ }
+}
+
+void real_output_file::flush()
+{
+ // To avoid looping, set fp to 0 before calling fatal().
+ if (fflush(fp) < 0) {
+ fp = 0;
+ fatal("unable to flush output file: %1", strerror(errno));
+ }
+}
+
+int real_output_file::is_printing()
+{
+ return printing;
+}
+
+void real_output_file::begin_page(int pageno, vunits page_length)
+{
+ printing = in_output_page_list(pageno);
+ if (printing)
+ really_begin_page(pageno, page_length);
+}
+
+void real_output_file::copy_file(hunits x, vunits y, const char *filename)
+{
+ if (printing && output_on)
+ really_copy_file(x, y, filename);
+ check_output_limits(x.to_units(), y.to_units());
+}
+
+void real_output_file::transparent_char(unsigned char c)
+{
+ if (printing && output_on)
+ really_transparent_char(c);
+}
+
+void real_output_file::print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits width)
+{
+ if (printing)
+ really_print_line(x, y, n, before, after, width);
+ delete_node_list(n);
+}
+
+void real_output_file::really_copy_file(hunits, vunits, const char *)
+{
+ // do nothing
+}
+
+void real_output_file::put_filename(const char *filename, int po)
+{
+ really_put_filename(filename, po);
+}
+
+void real_output_file::really_put_filename(const char *, int)
+{
+}
+
+void real_output_file::on()
+{
+ really_on();
+ if (output_on == 0)
+ output_on = 1;
+}
+
+void real_output_file::off()
+{
+ really_off();
+ output_on = 0;
+}
+
+int real_output_file::is_on()
+{
+ return output_on;
+}
+
+void real_output_file::really_on()
+{
+}
+
+void real_output_file::really_off()
+{
+}
+
+/* ascii_output_file */
+
+void ascii_output_file::really_transparent_char(unsigned char c)
+{
+ putc(c, fp);
+}
+
+void ascii_output_file::really_print_line(hunits, vunits, node *n,
+ vunits, vunits, hunits)
+{
+ while (n != 0) {
+ n->ascii_print(this);
+ n = n->next;
+ }
+ fputc('\n', fp);
+}
+
+void ascii_output_file::really_begin_page(int /*pageno*/, vunits /*page_length*/)
+{
+ fputs("<beginning of page>\n", fp);
+}
+
+ascii_output_file::ascii_output_file()
+{
+}
+
+/* suppress_output_file */
+
+suppress_output_file::suppress_output_file()
+{
+}
+
+void suppress_output_file::really_print_line(hunits, vunits, node *, vunits, vunits, hunits)
+{
+}
+
+void suppress_output_file::really_begin_page(int, vunits)
+{
+}
+
+void suppress_output_file::really_transparent_char(unsigned char)
+{
+}
+
+/* glyphs, ligatures, kerns, discretionary breaks */
+
+class charinfo_node : public node {
+protected:
+ charinfo *ci;
+public:
+ charinfo_node(charinfo *, statem *, int, node * = 0);
+ int ends_sentence();
+ int overlaps_vertically();
+ int overlaps_horizontally();
+};
+
+charinfo_node::charinfo_node(charinfo *c, statem *s, int pop, node *x)
+: node(x, s, pop), ci(c)
+{
+}
+
+int charinfo_node::ends_sentence()
+{
+ if (ci->ends_sentence())
+ return 1;
+ else if (ci->transparent())
+ return 2;
+ else
+ return 0;
+}
+
+int charinfo_node::overlaps_horizontally()
+{
+ return ci->overlaps_horizontally();
+}
+
+int charinfo_node::overlaps_vertically()
+{
+ return ci->overlaps_vertically();
+}
+
+class glyph_node : public charinfo_node {
+protected:
+ tfont *tf;
+ color *gcol;
+ color *fcol; /* this is needed for grotty */
+#ifdef STORE_WIDTH
+ hunits wid;
+ glyph_node(charinfo *, tfont *, color *, color *, hunits,
+ statem *, int, node * = 0);
+#endif
+public:
+ glyph_node(charinfo *, tfont *, color *, color *,
+ statem *, int, node * = 0);
+ ~glyph_node() {}
+ node *copy();
+ node *merge_glyph_node(glyph_node *);
+ node *merge_self(node *);
+ hunits width();
+ node *last_char_node();
+ units size();
+ void vertical_extent(vunits *, vunits *);
+ hunits subscript_correction();
+ hunits italic_correction();
+ hunits left_italic_correction();
+ hunits skew();
+ hyphenation_type get_hyphenation_type();
+ tfont *get_tfont();
+ color *get_glyph_color();
+ color *get_fill_color();
+ void tprint(troff_output_file *);
+ void zero_width_tprint(troff_output_file *);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_self(node *, hyphen_list **);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int character_type();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void debug_node();
+};
+
+class ligature_node : public glyph_node {
+ node *n1;
+ node *n2;
+#ifdef STORE_WIDTH
+ ligature_node(charinfo *, tfont *, color *, color *, hunits,
+ node *, node *, statem *, int, node * = 0);
+#endif
+public:
+ void *operator new(size_t);
+ void operator delete(void *);
+ ligature_node(charinfo *, tfont *, color *, color *,
+ node *, node *, statem *, int, node * = 0);
+ ~ligature_node();
+ node *copy();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class kern_pair_node : public node {
+ hunits amount;
+ node *n1;
+ node *n2;
+public:
+ kern_pair_node(hunits, node *, node *, statem *, int, node * = 0);
+ ~kern_pair_node();
+ node *copy();
+ node *merge_glyph_node(glyph_node *);
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_discretionary_hyphen();
+ hunits width();
+ node *last_char_node();
+ hunits italic_correction();
+ hunits subscript_correction();
+ void tprint(troff_output_file *);
+ hyphenation_type get_hyphenation_type();
+ int ends_sentence();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void vertical_extent(vunits *, vunits *);
+};
+
+class dbreak_node : public node {
+ node *none;
+ node *pre;
+ node *post;
+public:
+ dbreak_node(node *, node *, statem *, int, node * = 0);
+ ~dbreak_node();
+ node *copy();
+ node *merge_glyph_node(glyph_node *);
+ node *add_discretionary_hyphen();
+ hunits width();
+ node *last_char_node();
+ hunits italic_correction();
+ hunits subscript_correction();
+ void tprint(troff_output_file *);
+ breakpoint *get_breakpoints(hunits width, int ns, breakpoint *rest = 0,
+ int is_inner = 0);
+ int nbreaks();
+ int ends_sentence();
+ void split(int, node **, node **);
+ hyphenation_type get_hyphenation_type();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+void *ligature_node::operator new(size_t n)
+{
+ return new char[n];
+}
+
+void ligature_node::operator delete(void *p)
+{
+ delete[] (char *)p;
+}
+
+glyph_node::glyph_node(charinfo *c, tfont *t, color *gc, color *fc,
+ statem *s, int pop, node *x)
+: charinfo_node(c, s, pop, x), tf(t), gcol(gc), fcol(fc)
+{
+#ifdef STORE_WIDTH
+ wid = tf->get_width(ci);
+#endif
+}
+
+#ifdef STORE_WIDTH
+glyph_node::glyph_node(charinfo *c, tfont *t,
+ color *gc, color *fc, hunits w,
+ statem *s, int pop, node *x)
+: charinfo_node(c, s, pop, x), tf(t), gcol(gc), fcol(fc), wid(w)
+{
+}
+#endif
+
+node *glyph_node::copy()
+{
+#ifdef STORE_WIDTH
+ return new glyph_node(ci, tf, gcol, fcol, wid, state, div_nest_level);
+#else
+ return new glyph_node(ci, tf, gcol, fcol, state, div_nest_level);
+#endif
+}
+
+node *glyph_node::merge_self(node *nd)
+{
+ return nd->merge_glyph_node(this);
+}
+
+int glyph_node::character_type()
+{
+ return tf->get_character_type(ci);
+}
+
+node *glyph_node::add_self(node *n, hyphen_list **p)
+{
+ assert(ci->get_hyphenation_code() == (*p)->hyphenation_code);
+ next = 0;
+ node *nn;
+ if (n == 0 || (nn = n->merge_glyph_node(this)) == 0) {
+ next = n;
+ nn = this;
+ }
+ if ((*p)->hyphen)
+ nn = nn->add_discretionary_hyphen();
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return nn;
+}
+
+units glyph_node::size()
+{
+ return tf->get_size().to_units();
+}
+
+hyphen_list *glyph_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ (*count)++;
+ return new hyphen_list(ci->get_hyphenation_code(), tail);
+}
+
+tfont *node::get_tfont()
+{
+ return 0;
+}
+
+tfont *glyph_node::get_tfont()
+{
+ return tf;
+}
+
+color *node::get_glyph_color()
+{
+ return 0;
+}
+
+color *glyph_node::get_glyph_color()
+{
+ return gcol;
+}
+
+color *node::get_fill_color()
+{
+ return 0;
+}
+
+color *glyph_node::get_fill_color()
+{
+ return fcol;
+}
+
+node *node::merge_glyph_node(glyph_node *)
+{
+ return 0;
+}
+
+node *glyph_node::merge_glyph_node(glyph_node *gn)
+{
+ if (tf == gn->tf && gcol == gn->gcol && fcol == gn->fcol) {
+ charinfo *lig;
+ if ((lig = tf->get_lig(ci, gn->ci)) != 0) {
+ node *next1 = next;
+ next = 0;
+ return new ligature_node(lig, tf, gcol, fcol, this, gn, state,
+ gn->div_nest_level, next1);
+ }
+ hunits kern;
+ if (tf->get_kern(ci, gn->ci, &kern)) {
+ node *next1 = next;
+ next = 0;
+ return new kern_pair_node(kern, this, gn, state,
+ gn->div_nest_level, next1);
+ }
+ }
+ return 0;
+}
+
+#ifdef STORE_WIDTH
+inline
+#endif
+hunits glyph_node::width()
+{
+#ifdef STORE_WIDTH
+ return wid;
+#else
+ return tf->get_width(ci);
+#endif
+}
+
+node *glyph_node::last_char_node()
+{
+ return this;
+}
+
+void glyph_node::vertical_extent(vunits *min, vunits *max)
+{
+ *min = -tf->get_char_height(ci);
+ *max = tf->get_char_depth(ci);
+}
+
+hunits glyph_node::skew()
+{
+ return tf->get_char_skew(ci);
+}
+
+hunits glyph_node::subscript_correction()
+{
+ return tf->get_subscript_correction(ci);
+}
+
+hunits glyph_node::italic_correction()
+{
+ return tf->get_italic_correction(ci);
+}
+
+hunits glyph_node::left_italic_correction()
+{
+ return tf->get_left_italic_correction(ci);
+}
+
+hyphenation_type glyph_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+void glyph_node::ascii_print(ascii_output_file *ascii)
+{
+ unsigned char c = ci->get_ascii_code();
+ if (c != 0)
+ ascii->outc(c);
+ else
+ ascii->outs(ci->nm.contents());
+}
+
+void glyph_node::debug_node()
+{
+ unsigned char c = ci->get_ascii_code();
+ fprintf(stderr, "{ %s [", type());
+ if (c)
+ fprintf(stderr, "%c", c);
+ else
+ fprintf(stderr, "%s", ci->nm.contents());
+ if (push_state)
+ fprintf(stderr, " <push_state>");
+ if (state)
+ state->display_state();
+ fprintf(stderr, " nest level %d", div_nest_level);
+ fprintf(stderr, "]}\n");
+ fflush(stderr);
+}
+
+ligature_node::ligature_node(charinfo *c, tfont *t, color *gc, color *fc,
+ node *gn1, node *gn2, statem *s,
+ int pop, node *x)
+: glyph_node(c, t, gc, fc, s, pop, x), n1(gn1), n2(gn2)
+{
+}
+
+#ifdef STORE_WIDTH
+ligature_node::ligature_node(charinfo *c, tfont *t, color *gc, color *fc,
+ hunits w, node *gn1, node *gn2, statem *s,
+ int pop, node *x)
+: glyph_node(c, t, gc, fc, w, s, pop, x), n1(gn1), n2(gn2)
+{
+}
+#endif
+
+ligature_node::~ligature_node()
+{
+ delete n1;
+ delete n2;
+}
+
+node *ligature_node::copy()
+{
+#ifdef STORE_WIDTH
+ return new ligature_node(ci, tf, gcol, fcol, wid, n1->copy(), n2->copy(),
+ state, div_nest_level);
+#else
+ return new ligature_node(ci, tf, gcol, fcol, n1->copy(), n2->copy(),
+ state, div_nest_level);
+#endif
+}
+
+void ligature_node::ascii_print(ascii_output_file *ascii)
+{
+ n1->ascii_print(ascii);
+ n2->ascii_print(ascii);
+}
+
+hyphen_list *ligature_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ hyphen_list *hl = n2->get_hyphen_list(tail, count);
+ return n1->get_hyphen_list(hl, count);
+}
+
+node *ligature_node::add_self(node *n, hyphen_list **p)
+{
+ n = n1->add_self(n, p);
+ n = n2->add_self(n, p);
+ n1 = n2 = 0;
+ delete this;
+ return n;
+}
+
+kern_pair_node::kern_pair_node(hunits n, node *first, node *second,
+ statem* s, int pop, node *x)
+: node(x, s, pop), amount(n), n1(first), n2(second)
+{
+}
+
+dbreak_node::dbreak_node(node *n, node *p, statem *s, int pop, node *x)
+: node(x, s, pop), none(n), pre(p), post(0)
+{
+}
+
+node *dbreak_node::merge_glyph_node(glyph_node *gn)
+{
+ glyph_node *gn2 = (glyph_node *)gn->copy();
+ node *new_none = none ? none->merge_glyph_node(gn) : 0;
+ node *new_post = post ? post->merge_glyph_node(gn2) : 0;
+ if (new_none == 0 && new_post == 0) {
+ delete gn2;
+ return 0;
+ }
+ if (new_none != 0)
+ none = new_none;
+ else {
+ gn->next = none;
+ none = gn;
+ }
+ if (new_post != 0)
+ post = new_post;
+ else {
+ gn2->next = post;
+ post = gn2;
+ }
+ return this;
+}
+
+node *kern_pair_node::merge_glyph_node(glyph_node *gn)
+{
+ node *nd = n2->merge_glyph_node(gn);
+ if (nd == 0)
+ return 0;
+ n2 = nd;
+ nd = n2->merge_self(n1);
+ if (nd) {
+ nd->next = next;
+ n1 = 0;
+ n2 = 0;
+ delete this;
+ return nd;
+ }
+ return this;
+}
+
+hunits kern_pair_node::italic_correction()
+{
+ return n2->italic_correction();
+}
+
+hunits kern_pair_node::subscript_correction()
+{
+ return n2->subscript_correction();
+}
+
+void kern_pair_node::vertical_extent(vunits *min, vunits *max)
+{
+ n1->vertical_extent(min, max);
+ vunits min2, max2;
+ n2->vertical_extent(&min2, &max2);
+ if (min2 < *min)
+ *min = min2;
+ if (max2 > *max)
+ *max = max2;
+}
+
+node *kern_pair_node::add_discretionary_hyphen()
+{
+ tfont *tf = n1->get_tfont();
+ if (tf) {
+ if (tf->contains(soft_hyphen_char)) {
+ color *gcol = n2->get_glyph_color();
+ color *fcol = n2->get_fill_color();
+ node *next1 = next;
+ next = 0;
+ node *n = copy();
+ glyph_node *gn = new glyph_node(soft_hyphen_char, tf, gcol, fcol,
+ state, div_nest_level);
+ node *nn = n->merge_glyph_node(gn);
+ if (nn == 0) {
+ gn->next = n;
+ nn = gn;
+ }
+ return new dbreak_node(this, nn, state, div_nest_level, next1);
+ }
+ }
+ return this;
+}
+
+kern_pair_node::~kern_pair_node()
+{
+ if (n1 != 0)
+ delete n1;
+ if (n2 != 0)
+ delete n2;
+}
+
+dbreak_node::~dbreak_node()
+{
+ delete_node_list(pre);
+ delete_node_list(post);
+ delete_node_list(none);
+}
+
+node *kern_pair_node::copy()
+{
+ return new kern_pair_node(amount, n1->copy(), n2->copy(), state,
+ div_nest_level);
+}
+
+node *copy_node_list(node *n)
+{
+ node *p = 0;
+ while (n != 0) {
+ node *nn = n->copy();
+ nn->next = p;
+ p = nn;
+ n = n->next;
+ }
+ while (p != 0) {
+ node *pp = p->next;
+ p->next = n;
+ n = p;
+ p = pp;
+ }
+ return n;
+}
+
+void delete_node_list(node *n)
+{
+ while (n != 0) {
+ node *tem = n;
+ n = n->next;
+ delete tem;
+ }
+}
+
+node *dbreak_node::copy()
+{
+ dbreak_node *p = new dbreak_node(copy_node_list(none), copy_node_list(pre),
+ state, div_nest_level);
+ p->post = copy_node_list(post);
+ return p;
+}
+
+hyphen_list *node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return tail;
+}
+
+hyphen_list *kern_pair_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ hyphen_list *hl = n2->get_hyphen_list(tail, count);
+ return n1->get_hyphen_list(hl, count);
+}
+
+class hyphen_inhibitor_node : public node {
+public:
+ hyphen_inhibitor_node(node * = 0);
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hyphenation_type get_hyphenation_type();
+};
+
+hyphen_inhibitor_node::hyphen_inhibitor_node(node *nd) : node(nd)
+{
+}
+
+node *hyphen_inhibitor_node::copy()
+{
+ return new hyphen_inhibitor_node;
+}
+
+int hyphen_inhibitor_node::same(node *)
+{
+ return 1;
+}
+
+const char *hyphen_inhibitor_node::type()
+{
+ return "hyphen_inhibitor_node";
+}
+
+int hyphen_inhibitor_node::force_tprint()
+{
+ return 0;
+}
+
+int hyphen_inhibitor_node::is_tag()
+{
+ return 0;
+}
+
+hyphenation_type hyphen_inhibitor_node::get_hyphenation_type()
+{
+ return HYPHEN_INHIBIT;
+}
+
+/* add_discretionary_hyphen methods */
+
+node *dbreak_node::add_discretionary_hyphen()
+{
+ if (post)
+ post = post->add_discretionary_hyphen();
+ if (none)
+ none = none->add_discretionary_hyphen();
+ return this;
+}
+
+node *node::add_discretionary_hyphen()
+{
+ tfont *tf = get_tfont();
+ if (!tf)
+ return new hyphen_inhibitor_node(this);
+ if (tf->contains(soft_hyphen_char)) {
+ color *gcol = get_glyph_color();
+ color *fcol = get_fill_color();
+ node *next1 = next;
+ next = 0;
+ node *n = copy();
+ glyph_node *gn = new glyph_node(soft_hyphen_char, tf, gcol, fcol,
+ state, div_nest_level);
+ node *n1 = n->merge_glyph_node(gn);
+ if (n1 == 0) {
+ gn->next = n;
+ n1 = gn;
+ }
+ return new dbreak_node(this, n1, state, div_nest_level, next1);
+ }
+ return this;
+}
+
+node *node::merge_self(node *)
+{
+ return 0;
+}
+
+node *node::add_self(node *n, hyphen_list ** /*p*/)
+{
+ next = n;
+ return this;
+}
+
+node *kern_pair_node::add_self(node *n, hyphen_list **p)
+{
+ n = n1->add_self(n, p);
+ n = n2->add_self(n, p);
+ n1 = n2 = 0;
+ delete this;
+ return n;
+}
+
+hunits node::width()
+{
+ return H0;
+}
+
+node *node::last_char_node()
+{
+ return 0;
+}
+
+int node::force_tprint()
+{
+ return 0;
+}
+
+int node::is_tag()
+{
+ return 0;
+}
+
+int node::get_break_code()
+{
+ return 0;
+}
+
+hunits hmotion_node::width()
+{
+ return n;
+}
+
+units node::size()
+{
+ return points_to_units(10);
+}
+
+void node::debug_node()
+{
+ fprintf(stderr, "{ %s ", type());
+ if (push_state)
+ fprintf(stderr, " <push_state>");
+ if (state)
+ fprintf(stderr, " <state>");
+ fprintf(stderr, " nest level %d", div_nest_level);
+ fprintf(stderr, " }\n");
+ fflush(stderr);
+}
+
+void node::debug_node_list()
+{
+ node *n = next;
+
+ debug_node();
+ while (n != 0) {
+ n->debug_node();
+ n = n->next;
+ }
+}
+
+hunits kern_pair_node::width()
+{
+ return n1->width() + n2->width() + amount;
+}
+
+node *kern_pair_node::last_char_node()
+{
+ node *nd = n2->last_char_node();
+ if (nd)
+ return nd;
+ return n1->last_char_node();
+}
+
+hunits dbreak_node::width()
+{
+ hunits x = H0;
+ for (node *n = none; n != 0; n = n->next)
+ x += n->width();
+ return x;
+}
+
+node *dbreak_node::last_char_node()
+{
+ for (node *n = none; n; n = n->next) {
+ node *last_node = n->last_char_node();
+ if (last_node)
+ return last_node;
+ }
+ return 0;
+}
+
+hunits dbreak_node::italic_correction()
+{
+ return none ? none->italic_correction() : H0;
+}
+
+hunits dbreak_node::subscript_correction()
+{
+ return none ? none->subscript_correction() : H0;
+}
+
+class italic_corrected_node : public node {
+ node *n;
+ hunits x;
+public:
+ italic_corrected_node(node *, hunits, statem *, int, node * = 0);
+ ~italic_corrected_node();
+ node *copy();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ hunits width();
+ node *last_char_node();
+ void vertical_extent(vunits *, vunits *);
+ int ends_sentence();
+ int overlaps_horizontally();
+ int overlaps_vertically();
+ int same(node *);
+ hyphenation_type get_hyphenation_type();
+ tfont *get_tfont();
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ int character_type();
+ void tprint(troff_output_file *);
+ hunits subscript_correction();
+ hunits skew();
+ node *add_self(node *, hyphen_list **);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+node *node::add_italic_correction(hunits *wd)
+{
+ hunits ic = italic_correction();
+ if (ic.is_zero())
+ return this;
+ else {
+ node *next1 = next;
+ next = 0;
+ *wd += ic;
+ return new italic_corrected_node(this, ic, state, div_nest_level, next1);
+ }
+}
+
+italic_corrected_node::italic_corrected_node(node *nn, hunits xx, statem *s,
+ int pop, node *p)
+: node(p, s, pop), n(nn), x(xx)
+{
+ assert(n != 0);
+}
+
+italic_corrected_node::~italic_corrected_node()
+{
+ delete n;
+}
+
+node *italic_corrected_node::copy()
+{
+ return new italic_corrected_node(n->copy(), x, state, div_nest_level);
+}
+
+hunits italic_corrected_node::width()
+{
+ return n->width() + x;
+}
+
+void italic_corrected_node::vertical_extent(vunits *min, vunits *max)
+{
+ n->vertical_extent(min, max);
+}
+
+void italic_corrected_node::tprint(troff_output_file *out)
+{
+ n->tprint(out);
+ out->right(x);
+}
+
+hunits italic_corrected_node::skew()
+{
+ return n->skew() - x/2;
+}
+
+hunits italic_corrected_node::subscript_correction()
+{
+ return n->subscript_correction() - x;
+}
+
+void italic_corrected_node::ascii_print(ascii_output_file *out)
+{
+ n->ascii_print(out);
+}
+
+int italic_corrected_node::ends_sentence()
+{
+ return n->ends_sentence();
+}
+
+int italic_corrected_node::overlaps_horizontally()
+{
+ return n->overlaps_horizontally();
+}
+
+int italic_corrected_node::overlaps_vertically()
+{
+ return n->overlaps_vertically();
+}
+
+node *italic_corrected_node::last_char_node()
+{
+ return n->last_char_node();
+}
+
+tfont *italic_corrected_node::get_tfont()
+{
+ return n->get_tfont();
+}
+
+hyphenation_type italic_corrected_node::get_hyphenation_type()
+{
+ return n->get_hyphenation_type();
+}
+
+node *italic_corrected_node::add_self(node *nd, hyphen_list **p)
+{
+ nd = n->add_self(nd, p);
+ hunits not_interested;
+ nd = nd->add_italic_correction(&not_interested);
+ n = 0;
+ delete this;
+ return nd;
+}
+
+hyphen_list *italic_corrected_node::get_hyphen_list(hyphen_list *tail,
+ int *count)
+{
+ return n->get_hyphen_list(tail, count);
+}
+
+int italic_corrected_node::character_type()
+{
+ return n->character_type();
+}
+
+class break_char_node : public node {
+ node *ch;
+ char break_code;
+ char prev_break_code;
+ color *col;
+public:
+ break_char_node(node *, int, int, color *, node * = 0);
+ break_char_node(node *, int, int, color *, statem *, int, node * = 0);
+ ~break_char_node();
+ node *copy();
+ hunits width();
+ vunits vertical_width();
+ node *last_char_node();
+ int character_type();
+ int ends_sentence();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ void tprint(troff_output_file *);
+ void zero_width_tprint(troff_output_file *);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ hyphenation_type get_hyphenation_type();
+ int overlaps_vertically();
+ int overlaps_horizontally();
+ units size();
+ tfont *get_tfont();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int get_break_code();
+};
+
+break_char_node::break_char_node(node *n, int bc, int pbc, color *c, node *x)
+: node(x), ch(n), break_code(bc), prev_break_code(pbc), col(c)
+{
+}
+
+break_char_node::break_char_node(node *n, int bc, int pbc, color *c,
+ statem *s, int pop, node *x)
+: node(x, s, pop), ch(n), break_code(bc), prev_break_code(pbc), col(c)
+{
+}
+
+break_char_node::~break_char_node()
+{
+ delete ch;
+}
+
+node *break_char_node::copy()
+{
+ return new break_char_node(ch->copy(), break_code, prev_break_code,
+ col, state, div_nest_level);
+}
+
+hunits break_char_node::width()
+{
+ return ch->width();
+}
+
+vunits break_char_node::vertical_width()
+{
+ return ch->vertical_width();
+}
+
+node *break_char_node::last_char_node()
+{
+ return ch->last_char_node();
+}
+
+int break_char_node::character_type()
+{
+ return ch->character_type();
+}
+
+int break_char_node::ends_sentence()
+{
+ return ch->ends_sentence();
+}
+
+enum break_char_type {
+ CAN_BREAK_BEFORE = 0x01,
+ CAN_BREAK_AFTER = 0x02,
+ IGNORE_HCODES = 0x04,
+ PROHIBIT_BREAK_BEFORE = 0x08,
+ PROHIBIT_BREAK_AFTER = 0x10,
+ INTER_CHAR_SPACE = 0x20
+};
+
+node *break_char_node::add_self(node *n, hyphen_list **p)
+{
+ int have_space_node = 0;
+ assert((*p)->hyphenation_code == 0);
+ if (break_code & CAN_BREAK_BEFORE) {
+ if ((*p)->breakable || break_code & IGNORE_HCODES) {
+ n = new space_node(H0, col, n);
+ n->freeze_space();
+ have_space_node = 1;
+ }
+ }
+ if (!have_space_node) {
+ if (prev_break_code & INTER_CHAR_SPACE
+ || prev_break_code & PROHIBIT_BREAK_AFTER) {
+ if (break_code & PROHIBIT_BREAK_BEFORE)
+ // stretchable zero-width space not implemented yet
+ ;
+ else {
+ // breakable, stretchable zero-width space not implemented yet
+ n = new space_node(H0, col, n);
+ n->freeze_space();
+ }
+ }
+ }
+ next = n;
+ n = this;
+ if (break_code & CAN_BREAK_AFTER) {
+ if ((*p)->breakable || break_code & IGNORE_HCODES) {
+ n = new space_node(H0, col, n);
+ n->freeze_space();
+ }
+ }
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return n;
+}
+
+hyphen_list *break_char_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+hyphenation_type break_char_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+void break_char_node::ascii_print(ascii_output_file *ascii)
+{
+ ch->ascii_print(ascii);
+}
+
+int break_char_node::overlaps_vertically()
+{
+ return ch->overlaps_vertically();
+}
+
+int break_char_node::overlaps_horizontally()
+{
+ return ch->overlaps_horizontally();
+}
+
+units break_char_node::size()
+{
+ return ch->size();
+}
+
+tfont *break_char_node::get_tfont()
+{
+ return ch->get_tfont();
+}
+
+node *extra_size_node::copy()
+{
+ return new extra_size_node(n, state, div_nest_level);
+}
+
+extra_size_node::extra_size_node(vunits i, statem *s, int pop)
+: node(0, s, pop), n(i)
+{
+}
+
+extra_size_node::extra_size_node(vunits i)
+: n(i)
+{
+}
+
+node *vertical_size_node::copy()
+{
+ return new vertical_size_node(n, state, div_nest_level);
+}
+
+vertical_size_node::vertical_size_node(vunits i, statem *s, int pop)
+: node(0, s, pop), n(i)
+{
+}
+
+vertical_size_node::vertical_size_node(vunits i)
+: n(i)
+{
+}
+
+node *hmotion_node::copy()
+{
+ return new hmotion_node(n, was_tab, unformat, col, state, div_nest_level);
+}
+
+node *space_char_hmotion_node::copy()
+{
+ return new space_char_hmotion_node(n, col, state, div_nest_level);
+}
+
+vmotion_node::vmotion_node(vunits i, color *c)
+: n(i), col(c)
+{
+}
+
+vmotion_node::vmotion_node(vunits i, color *c, statem *s, int pop)
+: node(0, s, pop), n(i), col(c)
+{
+}
+
+node *vmotion_node::copy()
+{
+ return new vmotion_node(n, col, state, div_nest_level);
+}
+
+node *dummy_node::copy()
+{
+ return new dummy_node;
+}
+
+node *transparent_dummy_node::copy()
+{
+ return new transparent_dummy_node;
+}
+
+hline_node::~hline_node()
+{
+ if (n)
+ delete n;
+}
+
+hline_node::hline_node(hunits i, node *c, node *nxt)
+: node(nxt), x(i), n(c)
+{
+}
+
+hline_node::hline_node(hunits i, node *c, statem *s, int pop, node *nxt)
+: node(nxt, s, pop), x(i), n(c)
+{
+}
+
+node *hline_node::copy()
+{
+ return new hline_node(x, n ? n->copy() : 0, state, div_nest_level);
+}
+
+hunits hline_node::width()
+{
+ return x < H0 ? H0 : x;
+}
+
+vline_node::vline_node(vunits i, node *c, node *nxt)
+: node(nxt), x(i), n(c)
+{
+}
+
+vline_node::vline_node(vunits i, node *c, statem *s, int pop, node *nxt)
+: node(nxt, s, pop), x(i), n(c)
+{
+}
+
+vline_node::~vline_node()
+{
+ if (n)
+ delete n;
+}
+
+node *vline_node::copy()
+{
+ return new vline_node(x, n ? n->copy() : 0, state, div_nest_level);
+}
+
+hunits vline_node::width()
+{
+ return n == 0 ? H0 : n->width();
+}
+
+zero_width_node::zero_width_node(node *nd, statem *s, int pop)
+: node(0, s, pop), n(nd)
+{
+}
+
+zero_width_node::zero_width_node(node *nd)
+: n(nd)
+{
+}
+
+zero_width_node::~zero_width_node()
+{
+ delete_node_list(n);
+}
+
+node *zero_width_node::copy()
+{
+ return new zero_width_node(copy_node_list(n), state, div_nest_level);
+}
+
+int node_list_character_type(node *p)
+{
+ int t = 0;
+ for (; p; p = p->next)
+ t |= p->character_type();
+ return t;
+}
+
+int zero_width_node::character_type()
+{
+ return node_list_character_type(n);
+}
+
+void node_list_vertical_extent(node *p, vunits *min, vunits *max)
+{
+ *min = V0;
+ *max = V0;
+ vunits cur_vpos = V0;
+ vunits v1, v2;
+ for (; p; p = p->next) {
+ p->vertical_extent(&v1, &v2);
+ v1 += cur_vpos;
+ if (v1 < *min)
+ *min = v1;
+ v2 += cur_vpos;
+ if (v2 > *max)
+ *max = v2;
+ cur_vpos += p->vertical_width();
+ }
+}
+
+void zero_width_node::vertical_extent(vunits *min, vunits *max)
+{
+ node_list_vertical_extent(n, min, max);
+}
+
+overstrike_node::overstrike_node()
+: list(0), max_width(H0)
+{
+}
+
+overstrike_node::overstrike_node(statem *s, int pop)
+: node(0, s, pop), list(0), max_width(H0)
+{
+}
+
+overstrike_node::~overstrike_node()
+{
+ delete_node_list(list);
+}
+
+node *overstrike_node::copy()
+{
+ overstrike_node *on = new overstrike_node(state, div_nest_level);
+ for (node *tem = list; tem; tem = tem->next)
+ on->overstrike(tem->copy());
+ return on;
+}
+
+void overstrike_node::overstrike(node *n)
+{
+ if (n == 0)
+ return;
+ hunits w = n->width();
+ if (w > max_width)
+ max_width = w;
+ node **p;
+ for (p = &list; *p; p = &(*p)->next)
+ ;
+ n->next = 0;
+ *p = n;
+}
+
+hunits overstrike_node::width()
+{
+ return max_width;
+}
+
+bracket_node::bracket_node()
+: list(0), max_width(H0)
+{
+}
+
+bracket_node::bracket_node(statem *s, int pop)
+: node(0, s, pop), list(0), max_width(H0)
+{
+}
+
+bracket_node::~bracket_node()
+{
+ delete_node_list(list);
+}
+
+node *bracket_node::copy()
+{
+ bracket_node *on = new bracket_node(state, div_nest_level);
+ node *last_node = 0;
+ node *tem;
+ if (list)
+ list->last = 0;
+ for (tem = list; tem; tem = tem->next) {
+ if (tem->next)
+ tem->next->last = tem;
+ last_node = tem;
+ }
+ for (tem = last_node; tem; tem = tem->last)
+ on->bracket(tem->copy());
+ return on;
+}
+
+void bracket_node::bracket(node *n)
+{
+ if (n == 0)
+ return;
+ hunits w = n->width();
+ if (w > max_width)
+ max_width = w;
+ n->next = list;
+ list = n;
+}
+
+hunits bracket_node::width()
+{
+ return max_width;
+}
+
+int node::nspaces()
+{
+ return 0;
+}
+
+int node::merge_space(hunits, hunits, hunits)
+{
+ return 0;
+}
+
+
+space_node::space_node(hunits nn, color *c, node *p)
+: node(p, 0, 0), n(nn), set(0), was_escape_colon(0), col(c)
+{
+}
+
+space_node::space_node(hunits nn, int s, int flag, color *c, statem *st,
+ int pop, node *p)
+: node(p, st, pop), n(nn), set(s), was_escape_colon(flag), col(c)
+{
+}
+
+#if 0
+space_node::~space_node()
+{
+}
+#endif
+
+node *space_node::copy()
+{
+ return new space_node(n, set, was_escape_colon, col, state, div_nest_level);
+}
+
+int space_node::force_tprint()
+{
+ return 0;
+}
+
+int space_node::is_tag()
+{
+ return 0;
+}
+
+int space_node::nspaces()
+{
+ return set ? 0 : 1;
+}
+
+int space_node::merge_space(hunits h, hunits, hunits)
+{
+ n += h;
+ return 1;
+}
+
+hunits space_node::width()
+{
+ return n;
+}
+
+void node::spread_space(int*, hunits*)
+{
+}
+
+void space_node::spread_space(int *n_spaces, hunits *desired_space)
+{
+ if (!set) {
+ assert(*n_spaces > 0);
+ if (*n_spaces == 1) {
+ n += *desired_space;
+ *desired_space = H0;
+ }
+ else {
+ hunits extra = *desired_space / *n_spaces;
+ *desired_space -= extra;
+ n += extra;
+ }
+ *n_spaces -= 1;
+ set = 1;
+ }
+}
+
+void node::freeze_space()
+{
+}
+
+void space_node::freeze_space()
+{
+ set = 1;
+}
+
+void node::is_escape_colon()
+{
+}
+
+void space_node::is_escape_colon()
+{
+ was_escape_colon = 1;
+}
+
+diverted_space_node::diverted_space_node(vunits d, statem *s, int pop,
+ node *p)
+: node(p, s, pop), n(d)
+{
+}
+
+diverted_space_node::diverted_space_node(vunits d, node *p)
+: node(p), n(d)
+{
+}
+
+node *diverted_space_node::copy()
+{
+ return new diverted_space_node(n, state, div_nest_level);
+}
+
+diverted_copy_file_node::diverted_copy_file_node(symbol s, statem *st,
+ int pop, node *p)
+: node(p, st, pop), filename(s)
+{
+}
+
+diverted_copy_file_node::diverted_copy_file_node(symbol s, node *p)
+: node(p), filename(s)
+{
+}
+
+node *diverted_copy_file_node::copy()
+{
+ return new diverted_copy_file_node(filename, state, div_nest_level);
+}
+
+int node::ends_sentence()
+{
+ return 0;
+}
+
+int kern_pair_node::ends_sentence()
+{
+ switch (n2->ends_sentence()) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ break;
+ default:
+ assert(0);
+ }
+ return n1->ends_sentence();
+}
+
+int node_list_ends_sentence(node *n)
+{
+ for (; n != 0; n = n->next)
+ switch (n->ends_sentence()) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ break;
+ default:
+ assert(0);
+ }
+ return 2;
+}
+
+int dbreak_node::ends_sentence()
+{
+ return node_list_ends_sentence(none);
+}
+
+int node::overlaps_horizontally()
+{
+ return 0;
+}
+
+int node::overlaps_vertically()
+{
+ return 0;
+}
+
+int node::discardable()
+{
+ return 0;
+}
+
+int space_node::discardable()
+{
+ return set ? 0 : 1;
+}
+
+vunits node::vertical_width()
+{
+ return V0;
+}
+
+vunits vline_node::vertical_width()
+{
+ return x;
+}
+
+vunits vmotion_node::vertical_width()
+{
+ return n;
+}
+
+int node::set_unformat_flag()
+{
+ return 1;
+}
+
+int node::character_type()
+{
+ return 0;
+}
+
+hunits node::subscript_correction()
+{
+ return H0;
+}
+
+hunits node::italic_correction()
+{
+ return H0;
+}
+
+hunits node::left_italic_correction()
+{
+ return H0;
+}
+
+hunits node::skew()
+{
+ return H0;
+}
+
+/* vertical_extent methods */
+
+void node::vertical_extent(vunits *min, vunits *max)
+{
+ vunits v = vertical_width();
+ if (v < V0) {
+ *min = v;
+ *max = V0;
+ }
+ else {
+ *max = v;
+ *min = V0;
+ }
+}
+
+void vline_node::vertical_extent(vunits *min, vunits *max)
+{
+ if (n == 0)
+ node::vertical_extent(min, max);
+ else {
+ vunits cmin, cmax;
+ n->vertical_extent(&cmin, &cmax);
+ vunits h = n->size();
+ if (x < V0) {
+ if (-x < h) {
+ *min = x;
+ *max = V0;
+ }
+ else {
+ // we print the first character and then move up, so
+ *max = cmax;
+ // we print the last character and then move up h
+ *min = cmin + h;
+ if (*min > V0)
+ *min = V0;
+ *min += x;
+ }
+ }
+ else {
+ if (x < h) {
+ *max = x;
+ *min = V0;
+ }
+ else {
+ // we move down by h and then print the first character, so
+ *min = cmin + h;
+ if (*min > V0)
+ *min = V0;
+ *max = x + cmax;
+ }
+ }
+ }
+}
+
+/* ascii_print methods */
+
+static void ascii_print_reverse_node_list(ascii_output_file *ascii, node *n)
+{
+ if (n == 0)
+ return;
+ ascii_print_reverse_node_list(ascii, n->next);
+ n->ascii_print(ascii);
+}
+
+void dbreak_node::ascii_print(ascii_output_file *ascii)
+{
+ ascii_print_reverse_node_list(ascii, none);
+}
+
+void kern_pair_node::ascii_print(ascii_output_file *ascii)
+{
+ n1->ascii_print(ascii);
+ n2->ascii_print(ascii);
+}
+
+void node::ascii_print(ascii_output_file *)
+{
+}
+
+void space_node::ascii_print(ascii_output_file *ascii)
+{
+ if (!n.is_zero())
+ ascii->outc(' ');
+}
+
+void hmotion_node::ascii_print(ascii_output_file *ascii)
+{
+ // this is pretty arbitrary
+ if (n >= points_to_units(2))
+ ascii->outc(' ');
+}
+
+void space_char_hmotion_node::ascii_print(ascii_output_file *ascii)
+{
+ ascii->outc(' ');
+}
+
+/* asciify methods */
+
+void node::asciify(macro *m)
+{
+ m->append(this);
+}
+
+void glyph_node::asciify(macro *m)
+{
+ unsigned char c = ci->get_asciify_code();
+ if (c == 0)
+ c = ci->get_ascii_code();
+ if (c != 0) {
+ m->append(c);
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+void kern_pair_node::asciify(macro *m)
+{
+ n1->asciify(m);
+ n2->asciify(m);
+ n1 = n2 = 0;
+ delete this;
+}
+
+static void asciify_reverse_node_list(macro *m, node *n)
+{
+ if (n == 0)
+ return;
+ asciify_reverse_node_list(m, n->next);
+ n->asciify(m);
+}
+
+void dbreak_node::asciify(macro *m)
+{
+ asciify_reverse_node_list(m, none);
+ none = 0;
+ delete this;
+}
+
+void ligature_node::asciify(macro *m)
+{
+ n1->asciify(m);
+ n2->asciify(m);
+ n1 = n2 = 0;
+ delete this;
+}
+
+void break_char_node::asciify(macro *m)
+{
+ ch->asciify(m);
+ ch = 0;
+ delete this;
+}
+
+void italic_corrected_node::asciify(macro *m)
+{
+ n->asciify(m);
+ n = 0;
+ delete this;
+}
+
+void left_italic_corrected_node::asciify(macro *m)
+{
+ if (n) {
+ n->asciify(m);
+ n = 0;
+ }
+ delete this;
+}
+
+void hmotion_node::asciify(macro *m)
+{
+ if (was_tab) {
+ m->append('\t');
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+space_char_hmotion_node::space_char_hmotion_node(hunits i, color *c,
+ statem *s, int pop,
+ node *nxt)
+: hmotion_node(i, c, s, pop, nxt)
+{
+}
+
+space_char_hmotion_node::space_char_hmotion_node(hunits i, color *c,
+ node *nxt)
+: hmotion_node(i, c, 0, 0, nxt)
+{
+}
+
+void space_char_hmotion_node::asciify(macro *m)
+{
+ m->append(ESCAPE_SPACE);
+ delete this;
+}
+
+void space_node::asciify(macro *m)
+{
+ if (was_escape_colon) {
+ m->append(ESCAPE_COLON);
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+void word_space_node::asciify(macro *m)
+{
+ for (width_list *w = orig_width; w; w = w->next)
+ m->append(' ');
+ delete this;
+}
+
+void unbreakable_space_node::asciify(macro *m)
+{
+ m->append(ESCAPE_TILDE);
+ delete this;
+}
+
+void line_start_node::asciify(macro *)
+{
+ delete this;
+}
+
+void vertical_size_node::asciify(macro *)
+{
+ delete this;
+}
+
+breakpoint *node::get_breakpoints(hunits /*width*/, int /*nspaces*/,
+ breakpoint *rest, int /*is_inner*/)
+{
+ return rest;
+}
+
+int node::nbreaks()
+{
+ return 0;
+}
+
+breakpoint *space_node::get_breakpoints(hunits wd, int ns,
+ breakpoint *rest, int is_inner)
+{
+ if (next && next->discardable())
+ return rest;
+ breakpoint *bp = new breakpoint;
+ bp->next = rest;
+ bp->width = wd;
+ bp->nspaces = ns;
+ bp->hyphenated = 0;
+ if (is_inner) {
+ assert(rest != 0);
+ bp->index = rest->index + 1;
+ bp->nd = rest->nd;
+ }
+ else {
+ bp->nd = this;
+ bp->index = 0;
+ }
+ return bp;
+}
+
+int space_node::nbreaks()
+{
+ if (next && next->discardable())
+ return 0;
+ else
+ return 1;
+}
+
+static breakpoint *node_list_get_breakpoints(node *p, hunits *widthp,
+ int ns, breakpoint *rest)
+{
+ if (p != 0) {
+ rest = p->get_breakpoints(*widthp,
+ ns,
+ node_list_get_breakpoints(p->next, widthp, ns,
+ rest),
+ 1);
+ *widthp += p->width();
+ }
+ return rest;
+}
+
+breakpoint *dbreak_node::get_breakpoints(hunits wd, int ns,
+ breakpoint *rest, int is_inner)
+{
+ breakpoint *bp = new breakpoint;
+ bp->next = rest;
+ bp->width = wd;
+ for (node *tem = pre; tem != 0; tem = tem->next)
+ bp->width += tem->width();
+ bp->nspaces = ns;
+ bp->hyphenated = 1;
+ if (is_inner) {
+ assert(rest != 0);
+ bp->index = rest->index + 1;
+ bp->nd = rest->nd;
+ }
+ else {
+ bp->nd = this;
+ bp->index = 0;
+ }
+ return node_list_get_breakpoints(none, &wd, ns, bp);
+}
+
+int dbreak_node::nbreaks()
+{
+ int i = 1;
+ for (node *tem = none; tem != 0; tem = tem->next)
+ i += tem->nbreaks();
+ return i;
+}
+
+void node::split(int /*where*/, node ** /*prep*/, node ** /*postp*/)
+{
+ assert(0);
+}
+
+void space_node::split(int where, node **pre, node **post)
+{
+ assert(where == 0);
+ *pre = next;
+ *post = 0;
+ delete this;
+}
+
+static void node_list_split(node *p, int *wherep, node **prep, node **postp)
+{
+ if (p == 0)
+ return;
+ int nb = p->nbreaks();
+ node_list_split(p->next, wherep, prep, postp);
+ if (*wherep < 0) {
+ p->next = *postp;
+ *postp = p;
+ }
+ else if (*wherep < nb) {
+ p->next = *prep;
+ p->split(*wherep, prep, postp);
+ }
+ else {
+ p->next = *prep;
+ *prep = p;
+ }
+ *wherep -= nb;
+}
+
+void dbreak_node::split(int where, node **prep, node **postp)
+{
+ assert(where >= 0);
+ if (where == 0) {
+ *postp = post;
+ post = 0;
+ if (pre == 0)
+ *prep = next;
+ else {
+ node *tem;
+ for (tem = pre; tem->next != 0; tem = tem->next)
+ ;
+ tem->next = next;
+ *prep = pre;
+ }
+ pre = 0;
+ delete this;
+ }
+ else {
+ *prep = next;
+ where -= 1;
+ node_list_split(none, &where, prep, postp);
+ none = 0;
+ delete this;
+ }
+}
+
+hyphenation_type node::get_hyphenation_type()
+{
+ return HYPHEN_BOUNDARY;
+}
+
+hyphenation_type dbreak_node::get_hyphenation_type()
+{
+ return HYPHEN_INHIBIT;
+}
+
+hyphenation_type kern_pair_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type dummy_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type transparent_dummy_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type hmotion_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type space_char_hmotion_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type overstrike_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+hyphenation_type space_node::get_hyphenation_type()
+{
+ if (was_escape_colon)
+ return HYPHEN_MIDDLE;
+ return HYPHEN_BOUNDARY;
+}
+
+hyphenation_type unbreakable_space_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+int node::interpret(macro *)
+{
+ return 0;
+}
+
+special_node::special_node(const macro &m, int n)
+: mac(m), no_init_string(n)
+{
+ font_size fs = curenv->get_font_size();
+ int char_height = curenv->get_char_height();
+ int char_slant = curenv->get_char_slant();
+ int fontno = env_definite_font(curenv);
+ tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, fontno);
+ if (curenv->is_composite())
+ tf = tf->get_plain();
+ gcol = curenv->get_glyph_color();
+ fcol = curenv->get_fill_color();
+ is_special = 1;
+}
+
+special_node::special_node(const macro &m, tfont *t,
+ color *gc, color *fc,
+ statem *s, int pop,
+ int n)
+: node(0, s, pop), mac(m), tf(t), gcol(gc), fcol(fc), no_init_string(n)
+{
+ is_special = 1;
+}
+
+int special_node::same(node *n)
+{
+ return mac == ((special_node *)n)->mac
+ && tf == ((special_node *)n)->tf
+ && gcol == ((special_node *)n)->gcol
+ && fcol == ((special_node *)n)->fcol
+ && no_init_string == ((special_node *)n)->no_init_string;
+}
+
+const char *special_node::type()
+{
+ return "special_node";
+}
+
+int special_node::ends_sentence()
+{
+ return 2;
+}
+
+int special_node::force_tprint()
+{
+ return 0;
+}
+
+int special_node::is_tag()
+{
+ return 0;
+}
+
+node *special_node::copy()
+{
+ return new special_node(mac, tf, gcol, fcol, state, div_nest_level,
+ no_init_string);
+}
+
+void special_node::tprint_start(troff_output_file *out)
+{
+ out->start_special(tf, gcol, fcol, no_init_string);
+}
+
+void special_node::tprint_char(troff_output_file *out, unsigned char c)
+{
+ out->special_char(c);
+}
+
+void special_node::tprint_end(troff_output_file *out)
+{
+ out->end_special();
+}
+
+tfont *special_node::get_tfont()
+{
+ return tf;
+}
+
+/* suppress_node */
+
+suppress_node::suppress_node(int on_or_off, int issue_limits)
+: is_on(on_or_off), emit_limits(issue_limits), filename(0), position(0),
+ image_id(0)
+{
+}
+
+suppress_node::suppress_node(symbol f, char p, int id)
+: is_on(2), emit_limits(0), filename(f), position(p), image_id(id)
+{
+ is_special = 1;
+}
+
+suppress_node::suppress_node(int issue_limits, int on_or_off,
+ symbol f, char p, int id,
+ statem *s, int pop)
+: node(0, s, pop), is_on(on_or_off), emit_limits(issue_limits), filename(f),
+ position(p), image_id(id)
+{
+}
+
+int suppress_node::same(node *n)
+{
+ return ((is_on == ((suppress_node *)n)->is_on)
+ && (emit_limits == ((suppress_node *)n)->emit_limits)
+ && (filename == ((suppress_node *)n)->filename)
+ && (position == ((suppress_node *)n)->position)
+ && (image_id == ((suppress_node *)n)->image_id));
+}
+
+const char *suppress_node::type()
+{
+ return "suppress_node";
+}
+
+node *suppress_node::copy()
+{
+ return new suppress_node(emit_limits, is_on, filename, position, image_id,
+ state, div_nest_level);
+}
+
+/* tag_node */
+
+tag_node::tag_node()
+: delayed(0)
+{
+ is_special = 1;
+}
+
+tag_node::tag_node(string s, int delay)
+: tag_string(s), delayed(delay)
+{
+ is_special = !delay;
+}
+
+tag_node::tag_node(string s, statem *st, int pop, int delay)
+: node(0, st, pop), tag_string(s), delayed(delay)
+{
+ is_special = !delay;
+}
+
+node *tag_node::copy()
+{
+ return new tag_node(tag_string, state, div_nest_level, delayed);
+}
+
+void tag_node::tprint(troff_output_file *out)
+{
+ if (delayed)
+ out->add_to_tag_list(tag_string);
+ else
+ out->state.add_tag(out->fp, tag_string);
+}
+
+int tag_node::same(node *nd)
+{
+ return tag_string == ((tag_node *)nd)->tag_string
+ && delayed == ((tag_node *)nd)->delayed;
+}
+
+const char *tag_node::type()
+{
+ return "tag_node";
+}
+
+int tag_node::force_tprint()
+{
+ return !delayed;
+}
+
+int tag_node::is_tag()
+{
+ return !delayed;
+}
+
+int tag_node::ends_sentence()
+{
+ return 2;
+}
+
+// Get contents of register `p` as integer.
+// Used only by suppress_node::tprint().
+static int get_register(const char *p)
+{
+ assert(p != 0 /* nullptr */);
+ reg *r = (reg *)register_dictionary.lookup(p);
+ assert(r != 0 /* nullptr */);
+ units value;
+ assert(r->get_value(&value));
+ return int(value);
+}
+
+// Get contents of register `p` as string.
+// Used only by suppress_node::tprint().
+static const char *get_string(const char *p)
+{
+ assert(p != 0 /* nullptr */);
+ reg *r = (reg *)register_dictionary.lookup(p);
+ assert(r != 0 /* nullptr */);
+ return r->get_string();
+}
+
+void suppress_node::put(troff_output_file *out, const char *s)
+{
+ int i = 0;
+ while (s[i] != (char)0) {
+ out->special_char(s[i]);
+ i++;
+ }
+}
+
+/*
+ * We need to remember the start of the image and its name (\O5). But
+ * we won't always need this information; for instance, \O2 is used to
+ * produce a bounding box with no associated image or position thereof.
+ */
+
+static char last_position = 0;
+static const char *image_filename = "";
+static size_t image_filename_len = 0;
+static int subimage_counter = 0;
+
+/*
+ * tprint - if (is_on == 2)
+ * remember current position (l, r, c, i) and filename
+ * else
+ * if (emit_limits)
+ * if (html)
+ * emit image tag
+ * else
+ * emit postscript bounds for image
+ * else
+ * if (suppress boolean differs from current state)
+ * alter state
+ * reset registers
+ * record current page
+ * set low water mark.
+ */
+
+void suppress_node::tprint(troff_output_file *out)
+{
+ int current_page = topdiv->get_page_number();
+ // Does the node have an associated position and file name?
+ if (is_on == 2) {
+ // Save them for future bounding box limits.
+ last_position = position;
+ image_filename = strsave(filename.contents());
+ image_filename_len = strlen(image_filename);
+ }
+ else { // is_on = 0 or 1
+ // Now check whether the suppress node requires us to issue limits.
+ if (emit_limits) {
+ const size_t namebuflen = 8192;
+ char name[namebuflen] = { '\0' };
+ // Jump through a flaming hoop to avoid a "format nonliteral"
+ // warning from blindly using sprintf...and avoid trouble from
+ // mischievous image stems.
+ //
+ // Keep this format string synced with pre-html:makeFileName().
+ const char format[] = "%d";
+ const size_t format_len = strlen(format);
+ const char *percent_position = strstr(image_filename, format);
+ if (percent_position) {
+ subimage_counter++;
+ assert(sizeof subimage_counter <= 8);
+ // A 64-bit signed int produces up to 19 decimal digits.
+ char *subimage_number = (char *)malloc(20); // 19 digits + \0
+ if (0 == subimage_number)
+ fatal("memory allocation failure");
+ // Replace the %d in the filename with this number.
+ size_t enough = image_filename_len + 19 - format_len;
+ char *new_name = (char *)malloc(enough);
+ if (0 == new_name)
+ fatal("memory allocation failure");
+ ptrdiff_t prefix_length = percent_position - image_filename;
+ strncpy(new_name, image_filename, prefix_length);
+ sprintf(subimage_number, "%d", subimage_counter);
+ size_t number_length = strlen(subimage_number);
+ strcpy(new_name + prefix_length, subimage_number);
+ // Skip over the format in the source string.
+ const char *suffix_src = image_filename + prefix_length
+ + format_len;
+ char *suffix_dst = new_name + prefix_length + number_length;
+ strcpy(suffix_dst, suffix_src);
+ // Ensure the new string fits with room for a terminal '\0'.
+ const size_t len = strlen(new_name);
+ if (len > (namebuflen - 1))
+ error("constructed file name in suppressed output escape"
+ " sequence is too long (>= %1 bytes); skipping image",
+ (int)namebuflen);
+ else
+ strncpy(name, new_name, (namebuflen - 1));
+ free(new_name);
+ free(subimage_number);
+ }
+ else {
+ if (image_filename_len > (namebuflen - 1))
+ error("file name in suppressed output escape sequence is too"
+ " long (>= %1 bytes); skipping image", (int)namebuflen);
+ else
+ strcpy(name, image_filename);
+ }
+ if (is_html) {
+ switch (last_position) {
+ case 'c':
+ out->start_special();
+ put(out, "devtag:.centered-image");
+ break;
+ case 'r':
+ out->start_special();
+ put(out, "devtag:.right-image");
+ break;
+ case 'l':
+ out->start_special();
+ put(out, "devtag:.left-image");
+ break;
+ case 'i':
+ ;
+ default:
+ ;
+ }
+ out->end_special();
+ out->start_special();
+ put(out, "devtag:.auto-image ");
+ put(out, name);
+ out->end_special();
+ }
+ else {
+ // postscript (or other device)
+ if (suppress_start_page > 0
+ && (current_page != suppress_start_page))
+ error("suppression limit registers span more than a page;"
+ " grohtml-info for image %1 will be wrong", image_no);
+ // if (topdiv->get_page_number() != suppress_start_page)
+ // fprintf(stderr, "end of image and topdiv page = %d and"
+ // " suppress_start_page = %d\n",
+ // topdiv->get_page_number(), suppress_start_page);
+
+ // `name` will contain a "%d" in which the image_no is placed.
+ fprintf(stderr,
+ "grohtml-info:page %d %d %d %d %d %d %s %d %d"
+ " %s:%s\n",
+ topdiv->get_page_number(),
+ get_register("opminx"), get_register("opminy"),
+ get_register("opmaxx"), get_register("opmaxy"),
+ // page offset + line length
+ get_register(".o") + get_register(".l"),
+ name, hresolution, vresolution, get_string(".F"),
+ get_string(".c"));
+ fflush(stderr);
+ }
+ }
+ else { // We are not emitting limits.
+ if (is_on) {
+ out->on();
+ reset_output_registers();
+ }
+ else
+ out->off();
+ suppress_start_page = current_page;
+ }
+ } // is_on
+}
+
+int suppress_node::force_tprint()
+{
+ return is_on;
+}
+
+int suppress_node::is_tag()
+{
+ return is_on;
+}
+
+hunits suppress_node::width()
+{
+ return H0;
+}
+
+/* composite_node */
+
+class composite_node : public charinfo_node {
+ node *n;
+ tfont *tf;
+public:
+ composite_node(node *, charinfo *, tfont *, statem *, int, node * = 0);
+ ~composite_node();
+ node *copy();
+ hunits width();
+ node *last_char_node();
+ units size();
+ void tprint(troff_output_file *);
+ hyphenation_type get_hyphenation_type();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_self(node *, hyphen_list **);
+ tfont *get_tfont();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void vertical_extent(vunits *, vunits *);
+ vunits vertical_width();
+};
+
+composite_node::composite_node(node *p, charinfo *c, tfont *t, statem *s,
+ int pop, node *x)
+: charinfo_node(c, s, pop, x), n(p), tf(t)
+{
+}
+
+composite_node::~composite_node()
+{
+ delete_node_list(n);
+}
+
+node *composite_node::copy()
+{
+ return new composite_node(copy_node_list(n), ci, tf, state, div_nest_level);
+}
+
+hunits composite_node::width()
+{
+ hunits x;
+ if (tf->get_constant_space(&x))
+ return x;
+ x = H0;
+ for (node *tem = n; tem; tem = tem->next)
+ x += tem->width();
+ hunits offset;
+ if (tf->get_bold(&offset))
+ x += offset;
+ x += tf->get_track_kern();
+ return x;
+}
+
+node *composite_node::last_char_node()
+{
+ return this;
+}
+
+vunits composite_node::vertical_width()
+{
+ vunits v = V0;
+ for (node *tem = n; tem; tem = tem->next)
+ v += tem->vertical_width();
+ return v;
+}
+
+units composite_node::size()
+{
+ return tf->get_size().to_units();
+}
+
+hyphenation_type composite_node::get_hyphenation_type()
+{
+ return HYPHEN_MIDDLE;
+}
+
+void composite_node::asciify(macro *m)
+{
+ unsigned char c = ci->get_asciify_code();
+ if (c == 0)
+ c = ci->get_ascii_code();
+ if (c != 0) {
+ m->append(c);
+ delete this;
+ }
+ else
+ m->append(this);
+}
+
+void composite_node::ascii_print(ascii_output_file *ascii)
+{
+ unsigned char c = ci->get_ascii_code();
+ if (c != 0)
+ ascii->outc(c);
+ else
+ ascii->outs(ci->nm.contents());
+
+}
+
+hyphen_list *composite_node::get_hyphen_list(hyphen_list *tail, int *count)
+{
+ (*count)++;
+ return new hyphen_list(ci->get_hyphenation_code(), tail);
+}
+
+node *composite_node::add_self(node *nn, hyphen_list **p)
+{
+ assert(ci->get_hyphenation_code() == (*p)->hyphenation_code);
+ next = nn;
+ nn = this;
+ if ((*p)->hyphen)
+ nn = nn->add_discretionary_hyphen();
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return nn;
+}
+
+tfont *composite_node::get_tfont()
+{
+ return tf;
+}
+
+node *reverse_node_list(node *n)
+{
+ node *r = 0;
+ while (n) {
+ node *tem = n;
+ n = n->next;
+ tem->next = r;
+ r = tem;
+ }
+ return r;
+}
+
+void composite_node::vertical_extent(vunits *minimum, vunits *maximum)
+{
+ n = reverse_node_list(n);
+ node_list_vertical_extent(n, minimum, maximum);
+ n = reverse_node_list(n);
+}
+
+width_list::width_list(hunits w, hunits s)
+: width(w), sentence_width(s), next(0)
+{
+}
+
+width_list::width_list(width_list *w)
+: width(w->width), sentence_width(w->sentence_width), next(0)
+{
+}
+
+word_space_node::word_space_node(hunits d, color *c, width_list *w, node *x)
+: space_node(d, c, x), orig_width(w), unformat(0)
+{
+}
+
+word_space_node::word_space_node(hunits d, int s, color *c, width_list *w,
+ int flag, statem *st, int pop, node *x)
+: space_node(d, s, 0, c, st, pop, x), orig_width(w), unformat(flag)
+{
+}
+
+word_space_node::~word_space_node()
+{
+ width_list *w = orig_width;
+ while (w != 0) {
+ width_list *tmp = w;
+ w = w->next;
+ delete tmp;
+ }
+}
+
+node *word_space_node::copy()
+{
+ assert(orig_width != 0);
+ width_list *w_old_curr = orig_width;
+ width_list *w_new_curr = new width_list(w_old_curr);
+ width_list *w_new = w_new_curr;
+ w_old_curr = w_old_curr->next;
+ while (w_old_curr != 0) {
+ w_new_curr->next = new width_list(w_old_curr);
+ w_new_curr = w_new_curr->next;
+ w_old_curr = w_old_curr->next;
+ }
+ return new word_space_node(n, set, col, w_new, unformat, state,
+ div_nest_level);
+}
+
+int word_space_node::set_unformat_flag()
+{
+ unformat = 1;
+ return 1;
+}
+
+void word_space_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->word_marker();
+ out->right(n);
+}
+
+int word_space_node::merge_space(hunits h, hunits sw, hunits ssw)
+{
+ n += h;
+ assert(orig_width != 0);
+ width_list *w = orig_width;
+ for (; w->next; w = w->next)
+ ;
+ w->next = new width_list(sw, ssw);
+ return 1;
+}
+
+unbreakable_space_node::unbreakable_space_node(hunits d, color *c, node *x)
+: word_space_node(d, c, 0, x)
+{
+}
+
+unbreakable_space_node::unbreakable_space_node(hunits d, int s,
+ color *c, statem *st, int pop,
+ node *x)
+: word_space_node(d, s, c, 0, 0, st, pop, x)
+{
+}
+
+node *unbreakable_space_node::copy()
+{
+ return new unbreakable_space_node(n, set, col, state, div_nest_level);
+}
+
+int unbreakable_space_node::force_tprint()
+{
+ return 0;
+}
+
+int unbreakable_space_node::is_tag()
+{
+ return 0;
+}
+
+breakpoint *unbreakable_space_node::get_breakpoints(hunits, int,
+ breakpoint *rest, int)
+{
+ return rest;
+}
+
+int unbreakable_space_node::nbreaks()
+{
+ return 0;
+}
+
+void unbreakable_space_node::split(int, node **, node **)
+{
+ assert(0);
+}
+
+int unbreakable_space_node::merge_space(hunits, hunits, hunits)
+{
+ return 0;
+}
+
+hvpair::hvpair()
+{
+}
+
+draw_node::draw_node(char c, hvpair *p, int np, font_size s,
+ color *gc, color *fc)
+: npoints(np), sz(s), gcol(gc), fcol(fc), code(c)
+{
+ point = new hvpair[npoints];
+ for (int i = 0; i < npoints; i++)
+ point[i] = p[i];
+}
+
+draw_node::draw_node(char c, hvpair *p, int np, font_size s,
+ color *gc, color *fc, statem *st, int pop)
+: node(0, st, pop), npoints(np), sz(s), gcol(gc), fcol(fc), code(c)
+{
+ point = new hvpair[npoints];
+ for (int i = 0; i < npoints; i++)
+ point[i] = p[i];
+}
+
+int draw_node::same(node *n)
+{
+ draw_node *nd = (draw_node *)n;
+ if (code != nd->code || npoints != nd->npoints || sz != nd->sz
+ || gcol != nd->gcol || fcol != nd->fcol)
+ return 0;
+ for (int i = 0; i < npoints; i++)
+ if (point[i].h != nd->point[i].h || point[i].v != nd->point[i].v)
+ return 0;
+ return 1;
+}
+
+const char *draw_node::type()
+{
+ return "draw_node";
+}
+
+int draw_node::force_tprint()
+{
+ return 0;
+}
+
+int draw_node::is_tag()
+{
+ return 0;
+}
+
+draw_node::~draw_node()
+{
+ if (point)
+ delete[] point;
+}
+
+hunits draw_node::width()
+{
+ hunits x = H0;
+ for (int i = 0; i < npoints; i++)
+ x += point[i].h;
+ return x;
+}
+
+vunits draw_node::vertical_width()
+{
+ if (code == 'e')
+ return V0;
+ vunits x = V0;
+ for (int i = 0; i < npoints; i++)
+ x += point[i].v;
+ return x;
+}
+
+node *draw_node::copy()
+{
+ return new draw_node(code, point, npoints, sz, gcol, fcol, state,
+ div_nest_level);
+}
+
+void draw_node::tprint(troff_output_file *out)
+{
+ out->draw(code, point, npoints, sz, gcol, fcol);
+}
+
+/* tprint methods */
+
+void glyph_node::tprint(troff_output_file *out)
+{
+ tfont *ptf = tf->get_plain();
+ if (ptf == tf)
+ out->put_char_width(ci, ptf, gcol, fcol, width(), H0);
+ else {
+ hunits offset;
+ int bold = tf->get_bold(&offset);
+ hunits w = ptf->get_width(ci);
+ hunits k = H0;
+ hunits x;
+ int cs = tf->get_constant_space(&x);
+ if (cs) {
+ x -= w;
+ if (bold)
+ x -= offset;
+ hunits x2 = x/2;
+ out->right(x2);
+ k = x - x2;
+ }
+ else
+ k = tf->get_track_kern();
+ if (bold) {
+ out->put_char(ci, ptf, gcol, fcol);
+ out->right(offset);
+ }
+ out->put_char_width(ci, ptf, gcol, fcol, w, k);
+ }
+}
+
+void glyph_node::zero_width_tprint(troff_output_file *out)
+{
+ tfont *ptf = tf->get_plain();
+ hunits offset;
+ int bold = tf->get_bold(&offset);
+ hunits x;
+ int cs = tf->get_constant_space(&x);
+ if (cs) {
+ x -= ptf->get_width(ci);
+ if (bold)
+ x -= offset;
+ x = x/2;
+ out->right(x);
+ }
+ out->put_char(ci, ptf, gcol, fcol);
+ if (bold) {
+ out->right(offset);
+ out->put_char(ci, ptf, gcol, fcol);
+ out->right(-offset);
+ }
+ if (cs)
+ out->right(-x);
+}
+
+void break_char_node::tprint(troff_output_file *t)
+{
+ ch->tprint(t);
+}
+
+void break_char_node::zero_width_tprint(troff_output_file *t)
+{
+ ch->zero_width_tprint(t);
+}
+
+void hline_node::tprint(troff_output_file *out)
+{
+ if (x < H0) {
+ out->right(x);
+ x = -x;
+ }
+ if (n == 0) {
+ out->right(x);
+ return;
+ }
+ hunits w = n->width();
+ if (w <= H0) {
+ error("horizontal line drawing character must have positive width");
+ out->right(x);
+ return;
+ }
+ int i = int(x/w);
+ if (i == 0) {
+ hunits xx = x - w;
+ hunits xx2 = xx/2;
+ out->right(xx2);
+ if (out->is_on())
+ n->tprint(out);
+ out->right(xx - xx2);
+ }
+ else {
+ hunits rem = x - w*i;
+ if (rem > H0) {
+ if (n->overlaps_horizontally()) {
+ if (out->is_on())
+ n->tprint(out);
+ out->right(rem - w);
+ }
+ else
+ out->right(rem);
+ }
+ while (--i >= 0)
+ if (out->is_on())
+ n->tprint(out);
+ }
+}
+
+void vline_node::tprint(troff_output_file *out)
+{
+ if (n == 0) {
+ out->down(x);
+ return;
+ }
+ vunits h = n->size();
+ int overlaps = n->overlaps_vertically();
+ vunits y = x;
+ if (y < V0) {
+ y = -y;
+ int i = y / h;
+ vunits rem = y - i*h;
+ if (i == 0) {
+ out->right(n->width());
+ out->down(-rem);
+ }
+ else {
+ while (--i > 0) {
+ n->zero_width_tprint(out);
+ out->down(-h);
+ }
+ if (overlaps) {
+ n->zero_width_tprint(out);
+ out->down(-rem);
+ if (out->is_on())
+ n->tprint(out);
+ out->down(-h);
+ }
+ else {
+ if (out->is_on())
+ n->tprint(out);
+ out->down(-h - rem);
+ }
+ }
+ }
+ else {
+ int i = y / h;
+ vunits rem = y - i*h;
+ if (i == 0) {
+ out->down(rem);
+ out->right(n->width());
+ }
+ else {
+ out->down(h);
+ if (overlaps)
+ n->zero_width_tprint(out);
+ out->down(rem);
+ while (--i > 0) {
+ n->zero_width_tprint(out);
+ out->down(h);
+ }
+ if (out->is_on())
+ n->tprint(out);
+ }
+ }
+}
+
+void zero_width_node::tprint(troff_output_file *out)
+{
+ if (!n)
+ return;
+ if (!n->next) {
+ n->zero_width_tprint(out);
+ return;
+ }
+ int hpos = out->get_hpos();
+ int vpos = out->get_vpos();
+ node *tem = n;
+ while (tem) {
+ tem->tprint(out);
+ tem = tem->next;
+ }
+ out->moveto(hpos, vpos);
+}
+
+void overstrike_node::tprint(troff_output_file *out)
+{
+ hunits pos = H0;
+ for (node *tem = list; tem; tem = tem->next) {
+ hunits x = (max_width - tem->width())/2;
+ out->right(x - pos);
+ pos = x;
+ tem->zero_width_tprint(out);
+ }
+ out->right(max_width - pos);
+}
+
+void bracket_node::tprint(troff_output_file *out)
+{
+ if (list == 0)
+ return;
+ int npieces = 0;
+ node *tem;
+ for (tem = list; tem; tem = tem->next)
+ ++npieces;
+ vunits h = list->size();
+ vunits totalh = h*npieces;
+ vunits y = (totalh - h)/2;
+ out->down(y);
+ for (tem = list; tem; tem = tem->next) {
+ tem->zero_width_tprint(out);
+ out->down(-h);
+ }
+ out->right(max_width);
+ out->down(totalh - y);
+}
+
+void node::tprint(troff_output_file *)
+{
+}
+
+void node::zero_width_tprint(troff_output_file *out)
+{
+ int hpos = out->get_hpos();
+ int vpos = out->get_vpos();
+ tprint(out);
+ out->moveto(hpos, vpos);
+}
+
+void space_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->right(n);
+}
+
+void hmotion_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->right(n);
+}
+
+void space_char_hmotion_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ if (is_html) {
+ // we emit the space width as a negative glyph index
+ out->flush_tbuf();
+ out->do_motion();
+ out->put('N');
+ out->put(-n.to_units());
+ out->put('\n');
+ }
+ out->right(n);
+}
+
+void vmotion_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ out->down(n);
+}
+
+void kern_pair_node::tprint(troff_output_file *out)
+{
+ n1->tprint(out);
+ out->right(amount);
+ n2->tprint(out);
+}
+
+static void tprint_reverse_node_list(troff_output_file *out, node *n)
+{
+ if (n == 0)
+ return;
+ tprint_reverse_node_list(out, n->next);
+ n->tprint(out);
+}
+
+void dbreak_node::tprint(troff_output_file *out)
+{
+ tprint_reverse_node_list(out, none);
+}
+
+void composite_node::tprint(troff_output_file *out)
+{
+ hunits bold_offset;
+ int is_bold = tf->get_bold(&bold_offset);
+ hunits track_kern = tf->get_track_kern();
+ hunits constant_space;
+ int is_constant_spaced = tf->get_constant_space(&constant_space);
+ hunits x = H0;
+ if (is_constant_spaced) {
+ x = constant_space;
+ for (node *tem = n; tem; tem = tem->next)
+ x -= tem->width();
+ if (is_bold)
+ x -= bold_offset;
+ hunits x2 = x/2;
+ out->right(x2);
+ x -= x2;
+ }
+ if (is_bold) {
+ int hpos = out->get_hpos();
+ int vpos = out->get_vpos();
+ tprint_reverse_node_list(out, n);
+ out->moveto(hpos, vpos);
+ out->right(bold_offset);
+ }
+ tprint_reverse_node_list(out, n);
+ if (is_constant_spaced)
+ out->right(x);
+ else
+ out->right(track_kern);
+}
+
+static node *make_composite_node(charinfo *s, environment *env)
+{
+ int fontno = env_definite_font(env);
+ if (fontno < 0) {
+ error("cannot format composite glyph: no current font");
+ return 0;
+ }
+ assert(fontno < font_table_size && font_table[fontno] != 0);
+ node *n = charinfo_to_node_list(s, env);
+ font_size fs = env->get_font_size();
+ int char_height = env->get_char_height();
+ int char_slant = env->get_char_slant();
+ tfont *tf = font_table[fontno]->get_tfont(fs, char_height, char_slant,
+ fontno);
+ if (env->is_composite())
+ tf = tf->get_plain();
+ return new composite_node(n, s, tf, 0, 0, 0);
+}
+
+static node *make_glyph_node(charinfo *s, environment *env,
+ bool want_warnings = true)
+{
+ int fontno = env_definite_font(env);
+ if (fontno < 0) {
+ error("cannot format glyph: no current font");
+ return 0;
+ }
+ assert(fontno < font_table_size && font_table[fontno] != 0);
+ int fn = fontno;
+ int found = font_table[fontno]->contains(s);
+ if (!found) {
+ macro *mac = s->get_macro();
+ if (mac && s->is_fallback())
+ return make_composite_node(s, env);
+ if (s->numbered()) {
+ if (want_warnings)
+ warning(WARN_CHAR, "character code %1 not defined in current"
+ " font", s->get_number());
+ return 0;
+ }
+ special_font_list *sf = font_table[fontno]->sf;
+ while (sf != 0 && !found) {
+ fn = sf->n;
+ if (font_table[fn])
+ found = font_table[fn]->contains(s);
+ sf = sf->next;
+ }
+ if (!found) {
+ symbol f = font_table[fontno]->get_name();
+ string gl(f.contents());
+ gl += ' ';
+ gl += s->nm.contents();
+ gl += '\0';
+ charinfo *ci = get_charinfo(symbol(gl.contents()));
+ if (ci && ci->get_macro())
+ return make_composite_node(ci, env);
+ }
+ if (!found) {
+ sf = global_special_fonts;
+ while (sf != 0 && !found) {
+ fn = sf->n;
+ if (font_table[fn])
+ found = font_table[fn]->contains(s);
+ sf = sf->next;
+ }
+ }
+ if (!found)
+ if (mac && s->is_special())
+ return make_composite_node(s, env);
+ if (!found) {
+ for (fn = 0; fn < font_table_size; fn++)
+ if (font_table[fn]
+ && font_table[fn]->is_special()
+ && font_table[fn]->contains(s)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ if (want_warnings && s->first_time_not_found()) {
+ unsigned char input_code = s->get_ascii_code();
+ if (input_code != 0) {
+ if (csgraph(input_code))
+ warning(WARN_CHAR, "character '%1' not defined",
+ input_code);
+ else
+ warning(WARN_CHAR, "character with input code %1 not"
+ " defined", int(input_code));
+ }
+ else if (s->nm.contents()) {
+ const char *nm = s->nm.contents();
+ const char *backslash = (nm[1] == 0) ? "\\" : "";
+ warning(WARN_CHAR, "special character '%1%2' not defined",
+ backslash, nm);
+ }
+ }
+ return 0;
+ }
+ }
+ font_size fs = env->get_font_size();
+ int char_height = env->get_char_height();
+ int char_slant = env->get_char_slant();
+ tfont *tf = font_table[fontno]->get_tfont(fs, char_height, char_slant, fn);
+ if (env->is_composite())
+ tf = tf->get_plain();
+ color *gcol = env->get_glyph_color();
+ color *fcol = env->get_fill_color();
+ return new glyph_node(s, tf, gcol, fcol, 0, 0);
+}
+
+node *make_node(charinfo *ci, environment *env)
+{
+ switch (ci->get_special_translation()) {
+ case charinfo::TRANSLATE_SPACE:
+ return new space_char_hmotion_node(env->get_space_width(),
+ env->get_fill_color());
+ case charinfo::TRANSLATE_STRETCHABLE_SPACE:
+ return new unbreakable_space_node(env->get_space_width(),
+ env->get_fill_color());
+ case charinfo::TRANSLATE_DUMMY:
+ return new dummy_node;
+ case charinfo::TRANSLATE_HYPHEN_INDICATOR:
+ error("translation to \\%% ignored in this context");
+ break;
+ }
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ macro *mac = ci->get_macro();
+ if (mac && ci->is_normal())
+ return make_composite_node(ci, env);
+ else
+ return make_glyph_node(ci, env);
+}
+
+bool character_exists(charinfo *ci, environment *env)
+{
+ if (ci->get_special_translation() != charinfo::TRANSLATE_NONE)
+ return true;
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ if (ci->get_macro())
+ return true;
+ node *nd = make_glyph_node(ci, env, false /* don't want warnings */);
+ if (nd) {
+ delete nd;
+ return true;
+ }
+ return false;
+}
+
+node *node::add_char(charinfo *ci, environment *env,
+ hunits *widthp, int *spacep, node **glyph_comp_np)
+{
+ node *res;
+ switch (ci->get_special_translation()) {
+ case charinfo::TRANSLATE_SPACE:
+ res = new space_char_hmotion_node(env->get_space_width(),
+ env->get_fill_color(), this);
+ *widthp += res->width();
+ return res;
+ case charinfo::TRANSLATE_STRETCHABLE_SPACE:
+ res = new unbreakable_space_node(env->get_space_width(),
+ env->get_fill_color(), this);
+ res->freeze_space();
+ *widthp += res->width();
+ *spacep += res->nspaces();
+ return res;
+ case charinfo::TRANSLATE_DUMMY:
+ return new dummy_node(this);
+ case charinfo::TRANSLATE_HYPHEN_INDICATOR:
+ return add_discretionary_hyphen();
+ }
+ charinfo *tem = ci->get_translation();
+ if (tem)
+ ci = tem;
+ macro *mac = ci->get_macro();
+ if (mac && ci->is_normal()) {
+ res = make_composite_node(ci, env);
+ if (res) {
+ res->next = this;
+ *widthp += res->width();
+ if (glyph_comp_np)
+ *glyph_comp_np = res;
+ }
+ else {
+ if (glyph_comp_np)
+ *glyph_comp_np = res;
+ return this;
+ }
+ }
+ else {
+ node *gn = make_glyph_node(ci, env);
+ if (gn == 0)
+ return this;
+ else {
+ hunits old_width = width();
+ node *p = gn->merge_self(this);
+ if (p == 0) {
+ *widthp += gn->width();
+ gn->next = this;
+ res = gn;
+ }
+ else {
+ *widthp += p->width() - old_width;
+ res = p;
+ }
+ if (glyph_comp_np)
+ *glyph_comp_np = res;
+ }
+ }
+ int break_code = 0;
+ if (ci->can_break_before())
+ break_code = CAN_BREAK_BEFORE;
+ if (ci->can_break_after())
+ break_code |= CAN_BREAK_AFTER;
+ if (ci->ignore_hcodes())
+ break_code |= IGNORE_HCODES;
+ if (ci->prohibit_break_before())
+ break_code = PROHIBIT_BREAK_BEFORE;
+ if (ci->prohibit_break_after())
+ break_code |= PROHIBIT_BREAK_AFTER;
+ if (ci->inter_char_space())
+ break_code |= INTER_CHAR_SPACE;
+ if (break_code) {
+ node *next1 = res->next;
+ res->next = 0;
+ res = new break_char_node(res, break_code, get_break_code(),
+ env->get_fill_color(), next1);
+ }
+ return res;
+}
+
+#ifdef __GNUG__
+inline
+#endif
+int same_node(node *n1, node *n2)
+{
+ if (n1 != 0) {
+ if (n2 != 0)
+ return n1->type() == n2->type() && n1->same(n2);
+ else
+ return 0;
+ }
+ else
+ return n2 == 0;
+}
+
+int same_node_list(node *n1, node *n2)
+{
+ while (n1 && n2) {
+ if (n1->type() != n2->type() || !n1->same(n2))
+ return 0;
+ n1 = n1->next;
+ n2 = n2->next;
+ }
+ return !n1 && !n2;
+}
+
+int extra_size_node::same(node *nd)
+{
+ return n == ((extra_size_node *)nd)->n;
+}
+
+const char *extra_size_node::type()
+{
+ return "extra_size_node";
+}
+
+int extra_size_node::force_tprint()
+{
+ return 0;
+}
+
+int extra_size_node::is_tag()
+{
+ return 0;
+}
+
+int vertical_size_node::same(node *nd)
+{
+ return n == ((vertical_size_node *)nd)->n;
+}
+
+const char *vertical_size_node::type()
+{
+ return "vertical_size_node";
+}
+
+int vertical_size_node::set_unformat_flag()
+{
+ return 0;
+}
+
+int vertical_size_node::force_tprint()
+{
+ return 0;
+}
+
+int vertical_size_node::is_tag()
+{
+ return 0;
+}
+
+int hmotion_node::same(node *nd)
+{
+ return n == ((hmotion_node *)nd)->n
+ && col == ((hmotion_node *)nd)->col;
+}
+
+const char *hmotion_node::type()
+{
+ return "hmotion_node";
+}
+
+int hmotion_node::set_unformat_flag()
+{
+ unformat = 1;
+ return 1;
+}
+
+int hmotion_node::force_tprint()
+{
+ return 0;
+}
+
+int hmotion_node::is_tag()
+{
+ return 0;
+}
+
+node *hmotion_node::add_self(node *nd, hyphen_list **p)
+{
+ next = nd;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *hmotion_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int space_char_hmotion_node::same(node *nd)
+{
+ return n == ((space_char_hmotion_node *)nd)->n
+ && col == ((space_char_hmotion_node *)nd)->col;
+}
+
+const char *space_char_hmotion_node::type()
+{
+ return "space_char_hmotion_node";
+}
+
+int space_char_hmotion_node::force_tprint()
+{
+ return 0;
+}
+
+int space_char_hmotion_node::is_tag()
+{
+ return 0;
+}
+
+node *space_char_hmotion_node::add_self(node *nd, hyphen_list **p)
+{
+ next = nd;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *space_char_hmotion_node::get_hyphen_list(hyphen_list *tail,
+ int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int vmotion_node::same(node *nd)
+{
+ return n == ((vmotion_node *)nd)->n
+ && col == ((vmotion_node *)nd)->col;
+}
+
+const char *vmotion_node::type()
+{
+ return "vmotion_node";
+}
+
+int vmotion_node::force_tprint()
+{
+ return 0;
+}
+
+int vmotion_node::is_tag()
+{
+ return 0;
+}
+
+int hline_node::same(node *nd)
+{
+ return x == ((hline_node *)nd)->x && same_node(n, ((hline_node *)nd)->n);
+}
+
+const char *hline_node::type()
+{
+ return "hline_node";
+}
+
+int hline_node::force_tprint()
+{
+ return 0;
+}
+
+int hline_node::is_tag()
+{
+ return 0;
+}
+
+int vline_node::same(node *nd)
+{
+ return x == ((vline_node *)nd)->x && same_node(n, ((vline_node *)nd)->n);
+}
+
+const char *vline_node::type()
+{
+ return "vline_node";
+}
+
+int vline_node::force_tprint()
+{
+ return 0;
+}
+
+int vline_node::is_tag()
+{
+ return 0;
+}
+
+int dummy_node::same(node * /*nd*/)
+{
+ return 1;
+}
+
+const char *dummy_node::type()
+{
+ return "dummy_node";
+}
+
+int dummy_node::force_tprint()
+{
+ return 0;
+}
+
+int dummy_node::is_tag()
+{
+ return 0;
+}
+
+int transparent_dummy_node::same(node * /*nd*/)
+{
+ return 1;
+}
+
+const char *transparent_dummy_node::type()
+{
+ return "transparent_dummy_node";
+}
+
+int transparent_dummy_node::force_tprint()
+{
+ return 0;
+}
+
+int transparent_dummy_node::is_tag()
+{
+ return 0;
+}
+
+int transparent_dummy_node::ends_sentence()
+{
+ return 2;
+}
+
+int zero_width_node::same(node *nd)
+{
+ return same_node_list(n, ((zero_width_node *)nd)->n);
+}
+
+const char *zero_width_node::type()
+{
+ return "zero_width_node";
+}
+
+int zero_width_node::force_tprint()
+{
+ return 0;
+}
+
+int zero_width_node::is_tag()
+{
+ return 0;
+}
+
+int italic_corrected_node::same(node *nd)
+{
+ return (x == ((italic_corrected_node *)nd)->x
+ && same_node(n, ((italic_corrected_node *)nd)->n));
+}
+
+const char *italic_corrected_node::type()
+{
+ return "italic_corrected_node";
+}
+
+int italic_corrected_node::force_tprint()
+{
+ return 0;
+}
+
+int italic_corrected_node::is_tag()
+{
+ return 0;
+}
+
+left_italic_corrected_node::left_italic_corrected_node(node *xx)
+: node(xx), n(0)
+{
+}
+
+left_italic_corrected_node::left_italic_corrected_node(statem *s, int pop,
+ node *xx)
+: node(xx, s, pop), n(0)
+{
+}
+
+left_italic_corrected_node::~left_italic_corrected_node()
+{
+ delete n;
+}
+
+node *left_italic_corrected_node::merge_glyph_node(glyph_node *gn)
+{
+ if (n == 0) {
+ hunits lic = gn->left_italic_correction();
+ if (!lic.is_zero()) {
+ x = lic;
+ n = gn;
+ return this;
+ }
+ }
+ else {
+ node *nd = n->merge_glyph_node(gn);
+ if (nd) {
+ n = nd;
+ x = n->left_italic_correction();
+ return this;
+ }
+ }
+ return 0;
+}
+
+node *left_italic_corrected_node::copy()
+{
+ left_italic_corrected_node *nd =
+ new left_italic_corrected_node(state, div_nest_level);
+ if (n) {
+ nd->n = n->copy();
+ nd->x = x;
+ }
+ return nd;
+}
+
+void left_italic_corrected_node::tprint(troff_output_file *out)
+{
+ if (n) {
+ out->right(x);
+ n->tprint(out);
+ }
+}
+
+const char *left_italic_corrected_node::type()
+{
+ return "left_italic_corrected_node";
+}
+
+int left_italic_corrected_node::force_tprint()
+{
+ return 0;
+}
+
+int left_italic_corrected_node::is_tag()
+{
+ return 0;
+}
+
+int left_italic_corrected_node::same(node *nd)
+{
+ return (x == ((left_italic_corrected_node *)nd)->x
+ && same_node(n, ((left_italic_corrected_node *)nd)->n));
+}
+
+void left_italic_corrected_node::ascii_print(ascii_output_file *out)
+{
+ if (n)
+ n->ascii_print(out);
+}
+
+hunits left_italic_corrected_node::width()
+{
+ return n ? n->width() + x : H0;
+}
+
+void left_italic_corrected_node::vertical_extent(vunits *minimum,
+ vunits *maximum)
+{
+ if (n)
+ n->vertical_extent(minimum, maximum);
+ else
+ node::vertical_extent(minimum, maximum);
+}
+
+hunits left_italic_corrected_node::skew()
+{
+ return n ? n->skew() + x/2 : H0;
+}
+
+hunits left_italic_corrected_node::subscript_correction()
+{
+ return n ? n->subscript_correction() : H0;
+}
+
+hunits left_italic_corrected_node::italic_correction()
+{
+ return n ? n->italic_correction() : H0;
+}
+
+int left_italic_corrected_node::ends_sentence()
+{
+ return n ? n->ends_sentence() : 0;
+}
+
+int left_italic_corrected_node::overlaps_horizontally()
+{
+ return n ? n->overlaps_horizontally() : 0;
+}
+
+int left_italic_corrected_node::overlaps_vertically()
+{
+ return n ? n->overlaps_vertically() : 0;
+}
+
+node *left_italic_corrected_node::last_char_node()
+{
+ return n ? n->last_char_node() : 0;
+}
+
+tfont *left_italic_corrected_node::get_tfont()
+{
+ return n ? n->get_tfont() : 0;
+}
+
+hyphenation_type left_italic_corrected_node::get_hyphenation_type()
+{
+ if (n)
+ return n->get_hyphenation_type();
+ else
+ return HYPHEN_MIDDLE;
+}
+
+hyphen_list *left_italic_corrected_node::get_hyphen_list(hyphen_list *tail,
+ int *count)
+{
+ return n ? n->get_hyphen_list(tail, count) : tail;
+}
+
+node *left_italic_corrected_node::add_self(node *nd, hyphen_list **p)
+{
+ if (n) {
+ nd = new left_italic_corrected_node(state, div_nest_level, nd);
+ nd = n->add_self(nd, p);
+ n = 0;
+ delete this;
+ return nd;
+ }
+ else {
+ next = nd;
+ return this;
+ }
+}
+
+int left_italic_corrected_node::character_type()
+{
+ return n ? n->character_type() : 0;
+}
+
+int overstrike_node::same(node *nd)
+{
+ return same_node_list(list, ((overstrike_node *)nd)->list);
+}
+
+const char *overstrike_node::type()
+{
+ return "overstrike_node";
+}
+
+int overstrike_node::force_tprint()
+{
+ return 0;
+}
+
+int overstrike_node::is_tag()
+{
+ return 0;
+}
+
+node *overstrike_node::add_self(node *n, hyphen_list **p)
+{
+ next = n;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *overstrike_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int bracket_node::same(node *nd)
+{
+ return same_node_list(list, ((bracket_node *)nd)->list);
+}
+
+const char *bracket_node::type()
+{
+ return "bracket_node";
+}
+
+int bracket_node::force_tprint()
+{
+ return 0;
+}
+
+int bracket_node::is_tag()
+{
+ return 0;
+}
+
+int composite_node::same(node *nd)
+{
+ return ci == ((composite_node *)nd)->ci
+ && same_node_list(n, ((composite_node *)nd)->n);
+}
+
+const char *composite_node::type()
+{
+ return "composite_node";
+}
+
+int composite_node::force_tprint()
+{
+ return 0;
+}
+
+int composite_node::is_tag()
+{
+ return 0;
+}
+
+int glyph_node::same(node *nd)
+{
+ return ci == ((glyph_node *)nd)->ci
+ && tf == ((glyph_node *)nd)->tf
+ && gcol == ((glyph_node *)nd)->gcol
+ && fcol == ((glyph_node *)nd)->fcol;
+}
+
+const char *glyph_node::type()
+{
+ return "glyph_node";
+}
+
+int glyph_node::force_tprint()
+{
+ return 0;
+}
+
+int glyph_node::is_tag()
+{
+ return 0;
+}
+
+int ligature_node::same(node *nd)
+{
+ return (same_node(n1, ((ligature_node *)nd)->n1)
+ && same_node(n2, ((ligature_node *)nd)->n2)
+ && glyph_node::same(nd));
+}
+
+const char *ligature_node::type()
+{
+ return "ligature_node";
+}
+
+int ligature_node::force_tprint()
+{
+ return 0;
+}
+
+int ligature_node::is_tag()
+{
+ return 0;
+}
+
+int kern_pair_node::same(node *nd)
+{
+ return (amount == ((kern_pair_node *)nd)->amount
+ && same_node(n1, ((kern_pair_node *)nd)->n1)
+ && same_node(n2, ((kern_pair_node *)nd)->n2));
+}
+
+const char *kern_pair_node::type()
+{
+ return "kern_pair_node";
+}
+
+int kern_pair_node::force_tprint()
+{
+ return 0;
+}
+
+int kern_pair_node::is_tag()
+{
+ return 0;
+}
+
+int dbreak_node::same(node *nd)
+{
+ return (same_node_list(none, ((dbreak_node *)nd)->none)
+ && same_node_list(pre, ((dbreak_node *)nd)->pre)
+ && same_node_list(post, ((dbreak_node *)nd)->post));
+}
+
+const char *dbreak_node::type()
+{
+ return "dbreak_node";
+}
+
+int dbreak_node::force_tprint()
+{
+ return 0;
+}
+
+int dbreak_node::is_tag()
+{
+ return 0;
+}
+
+int break_char_node::same(node *nd)
+{
+ return break_code == ((break_char_node *)nd)->break_code
+ && col == ((break_char_node *)nd)->col
+ && same_node(ch, ((break_char_node *)nd)->ch);
+}
+
+const char *break_char_node::type()
+{
+ return "break_char_node";
+}
+
+int break_char_node::force_tprint()
+{
+ return 0;
+}
+
+int break_char_node::is_tag()
+{
+ return 0;
+}
+
+int break_char_node::get_break_code()
+{
+ return break_code;
+}
+
+int line_start_node::same(node * /*nd*/)
+{
+ return 1;
+}
+
+const char *line_start_node::type()
+{
+ return "line_start_node";
+}
+
+int line_start_node::force_tprint()
+{
+ return 0;
+}
+
+int line_start_node::is_tag()
+{
+ return 0;
+}
+
+int space_node::same(node *nd)
+{
+ return n == ((space_node *)nd)->n
+ && set == ((space_node *)nd)->set
+ && col == ((space_node *)nd)->col;
+}
+
+const char *space_node::type()
+{
+ return "space_node";
+}
+
+int word_space_node::same(node *nd)
+{
+ return n == ((word_space_node *)nd)->n
+ && set == ((word_space_node *)nd)->set
+ && col == ((word_space_node *)nd)->col;
+}
+
+const char *word_space_node::type()
+{
+ return "word_space_node";
+}
+
+int word_space_node::force_tprint()
+{
+ return 0;
+}
+
+int word_space_node::is_tag()
+{
+ return 0;
+}
+
+void unbreakable_space_node::tprint(troff_output_file *out)
+{
+ out->fill_color(col);
+ if (is_html) {
+ // we emit the space width as a negative glyph index
+ out->flush_tbuf();
+ out->do_motion();
+ out->put('N');
+ out->put(-n.to_units());
+ out->put('\n');
+ }
+ out->right(n);
+}
+
+int unbreakable_space_node::same(node *nd)
+{
+ return n == ((unbreakable_space_node *)nd)->n
+ && set == ((unbreakable_space_node *)nd)->set
+ && col == ((unbreakable_space_node *)nd)->col;
+}
+
+const char *unbreakable_space_node::type()
+{
+ return "unbreakable_space_node";
+}
+
+node *unbreakable_space_node::add_self(node *nd, hyphen_list **p)
+{
+ next = nd;
+ hyphen_list *pp = *p;
+ *p = (*p)->next;
+ delete pp;
+ return this;
+}
+
+hyphen_list *unbreakable_space_node::get_hyphen_list(hyphen_list *tail, int *)
+{
+ return new hyphen_list(0, tail);
+}
+
+int diverted_space_node::same(node *nd)
+{
+ return n == ((diverted_space_node *)nd)->n;
+}
+
+const char *diverted_space_node::type()
+{
+ return "diverted_space_node";
+}
+
+int diverted_space_node::force_tprint()
+{
+ return 0;
+}
+
+int diverted_space_node::is_tag()
+{
+ return 0;
+}
+
+int diverted_copy_file_node::same(node *nd)
+{
+ return filename == ((diverted_copy_file_node *)nd)->filename;
+}
+
+const char *diverted_copy_file_node::type()
+{
+ return "diverted_copy_file_node";
+}
+
+int diverted_copy_file_node::force_tprint()
+{
+ return 0;
+}
+
+int diverted_copy_file_node::is_tag()
+{
+ return 0;
+}
+
+// Grow the font_table so that its size is > n.
+
+static void grow_font_table(int n)
+{
+ assert(n >= font_table_size);
+ font_info **old_font_table = font_table;
+ int old_font_table_size = font_table_size;
+ font_table_size = font_table_size ? (font_table_size*3)/2 : 10;
+ if (font_table_size <= n)
+ font_table_size = n + 10;
+ font_table = new font_info *[font_table_size];
+ if (old_font_table_size)
+ memcpy(font_table, old_font_table,
+ old_font_table_size*sizeof(font_info *));
+ delete[] old_font_table;
+ for (int i = old_font_table_size; i < font_table_size; i++)
+ font_table[i] = 0;
+}
+
+dictionary font_translation_dictionary(17);
+
+static symbol get_font_translation(symbol nm)
+{
+ void *p = font_translation_dictionary.lookup(nm);
+ return p ? symbol((char *)p) : nm;
+}
+
+dictionary font_dictionary(50);
+
+static bool mount_font_no_translate(int n, symbol name,
+ symbol external_name,
+ bool check_only = false)
+{
+ assert(n >= 0);
+ // We store the address of this char in `font_dictionary` to indicate
+ // that we've previously tried to mount the font and failed.
+ static char a_char;
+ font *fm = 0 /* nullptr */;
+ void *p = font_dictionary.lookup(external_name);
+ if (0 /* nullptr */ == p) {
+ fm = font::load_font(external_name.contents(), check_only);
+ if (check_only)
+ return fm != 0 /* nullptr */;
+ if (0 /* nullptr */ == fm) {
+ (void)font_dictionary.lookup(external_name, &a_char);
+ return false;
+ }
+ (void)font_dictionary.lookup(name, fm);
+ }
+ else if (p == &a_char) {
+ return false;
+ }
+ else
+ fm = (font*)p;
+ if (check_only)
+ return true;
+ if (n >= font_table_size) {
+ if (n - font_table_size > 1000) {
+ error("font position too much larger than first unused position");
+ return false;
+ }
+ grow_font_table(n);
+ }
+ else if (font_table[n] != 0 /* nullptr */)
+ delete font_table[n];
+ font_table[n] = new font_info(name, n, external_name, fm);
+ font_family::invalidate_fontno(n);
+ return true;
+}
+
+bool mount_font(int n, symbol name, symbol external_name)
+{
+ assert(n >= 0);
+ name = get_font_translation(name);
+ if (external_name.is_null())
+ external_name = name;
+ else
+ external_name = get_font_translation(external_name);
+ return mount_font_no_translate(n, name, external_name);
+}
+
+int check_font(symbol fam, symbol name)
+{
+ if (check_style(name))
+ name = concat(fam, name);
+ return mount_font_no_translate(0, name, name, true /* check only */);
+}
+
+int check_style(symbol s)
+{
+ int i = symbol_fontno(s);
+ return i < 0 ? 0 : font_table[i]->is_style();
+}
+
+bool mount_style(int n, symbol name)
+{
+ assert(n >= 0);
+ if (n >= font_table_size) {
+ if (n - font_table_size > 1000) {
+ error("font position too much larger than first unused position");
+ return false;
+ }
+ grow_font_table(n);
+ }
+ else if (font_table[n] != 0)
+ delete font_table[n];
+ font_table[n] = new font_info(get_font_translation(name), n,
+ NULL_SYMBOL, 0);
+ font_family::invalidate_fontno(n);
+ return true;
+}
+
+/* global functions */
+
+void font_translate()
+{
+ symbol from = get_name(true /* required */);
+ if (!from.is_null()) {
+ symbol to = get_name();
+ if (to.is_null() || from == to)
+ font_translation_dictionary.remove(from);
+ else
+ (void)font_translation_dictionary.lookup(from, (void *)to.contents());
+ }
+ skip_line();
+}
+
+void font_position()
+{
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ error("negative font position");
+ else {
+ symbol internal_name = get_name(true /* required */);
+ if (!internal_name.is_null()) {
+ symbol external_name = get_long_name();
+ if (!mount_font(n, internal_name, external_name)) {
+ string msg;
+ if (external_name != 0 /* nullptr */)
+ msg += string(" from file '") + external_name.contents()
+ + string("'");
+ msg += '\0';
+ error("cannot load font '%1'%2 for mounting",
+ internal_name.contents(), msg.contents());
+ }
+ }
+ }
+ }
+ skip_line();
+}
+
+font_family::font_family(symbol s)
+: map_size(10), nm(s)
+{
+ map = new int[map_size];
+ for (int i = 0; i < map_size; i++)
+ map[i] = -1;
+}
+
+font_family::~font_family()
+{
+ delete[] map;
+}
+
+// Resolve a requested font mounting position to a mounting position
+// usable by the output driver. (Positions 1 through 4 are typically
+// allocated to styles, and are not usable thus.) A return value of
+// `-1` indicates failure.
+int font_family::make_definite(int mounting_position)
+{
+ assert(mounting_position >= 0);
+ int pos = mounting_position;
+ if (pos < 0)
+ return -1;
+ if (pos < map_size && map[pos] >= 0)
+ return map[pos];
+ if (!(pos < font_table_size && font_table[pos] != 0))
+ return -1;
+ if (pos >= map_size) {
+ int old_map_size = map_size;
+ int *old_map = map;
+ map_size *= 3;
+ map_size /= 2;
+ if (pos >= map_size)
+ map_size = pos + 10;
+ map = new int[map_size];
+ memcpy(map, old_map, old_map_size * sizeof (int));
+ delete[] old_map;
+ for (int j = old_map_size; j < map_size; j++)
+ map[j] = -1;
+ }
+ if (!(font_table[pos]->is_style()))
+ return map[pos] = pos;
+ symbol sty = font_table[pos]->get_name();
+ symbol f = concat(nm, sty);
+ int n;
+ // Don't use symbol_fontno, because that might return a style and
+ // because we don't want to translate the name.
+ for (n = 0; n < font_table_size; n++)
+ if (font_table[n] != 0 && font_table[n]->is_named(f)
+ && !font_table[n]->is_style())
+ break;
+ if (n >= font_table_size) {
+ n = next_available_font_position();
+ if (!mount_font_no_translate(n, f, f))
+ return -1;
+ }
+ return map[pos] = n;
+}
+
+dictionary family_dictionary(5);
+
+font_family *lookup_family(symbol nm)
+{
+ font_family *f = (font_family *)family_dictionary.lookup(nm);
+ if (!f) {
+ f = new font_family(nm);
+ (void)family_dictionary.lookup(nm, f);
+ }
+ return f;
+}
+
+void font_family::invalidate_fontno(int n)
+{
+ assert(n >= 0 && n < font_table_size);
+ dictionary_iterator iter(family_dictionary);
+ symbol nam;
+ font_family *fam;
+ while (iter.get(&nam, (void **)&fam)) {
+ int mapsize = fam->map_size;
+ if (n < mapsize)
+ fam->map[n] = -1;
+ for (int i = 0; i < mapsize; i++)
+ if (fam->map[i] == n)
+ fam->map[i] = -1;
+ }
+}
+
+void style()
+{
+ int n;
+ if (get_integer(&n)) {
+ if (n < 0)
+ error("negative font position");
+ else {
+ symbol internal_name = get_name(true /* required */);
+ if (!internal_name.is_null())
+ (void) mount_style(n, internal_name);
+ }
+ }
+ skip_line();
+}
+
+static void font_lookup_error(font_lookup_info& finfo,
+ const char *msg)
+{
+ if (finfo.requested_name)
+ error("cannot load font '%1' %2", finfo.requested_name, msg);
+ else
+ error("cannot load font at position %1 %2",
+ finfo.requested_position, msg);
+}
+
+// Read the next token and look it up as a font name or position number.
+// Return lookup success. Store, in the supplied struct argument, the
+// requested name or position, and the position actually resolved; -1
+// means not found (see `font_lookup_info` constructor).
+static bool has_font(font_lookup_info *finfo)
+{
+ int n;
+ tok.skip();
+ if (tok.usable_as_delimiter()) {
+ symbol s = get_name(true /* required */);
+ finfo->requested_name = (char *)s.contents();
+ if (!s.is_null()) {
+ n = symbol_fontno(s);
+ if (n < 0) {
+ n = next_available_font_position();
+ if (mount_font(n, s))
+ finfo->position = n;
+ }
+ finfo->position = curenv->get_family()->make_definite(n);
+ }
+ }
+ else if (get_integer(&n)) {
+ finfo->requested_position = n;
+ if (!(n < 0 || n >= font_table_size || font_table[n] == 0))
+ finfo->position = curenv->get_family()->make_definite(n);
+ }
+ return (finfo->position != -1);
+}
+
+static int underline_fontno = 2;
+
+void underline_font()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to make it the underline font");
+ else
+ underline_fontno = finfo.position;
+ skip_line();
+}
+
+int get_underline_fontno()
+{
+ return underline_fontno;
+}
+
+void define_font_special_character()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo)) {
+ font_lookup_error(finfo, "to define font-specific fallback glyph");
+ // Normally we skip the remainder of the line unconditionally at the
+ // end of a request-implementing function, but do_define_character()
+ // will eat the rest of it for us.
+ skip_line();
+ }
+ else {
+ symbol f = font_table[finfo.position]->get_name();
+ do_define_character(CHAR_FONT_SPECIAL, f.contents());
+ }
+}
+
+void remove_font_special_character()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to remove font-specific fallback glyph");
+ else {
+ symbol f = font_table[finfo.position]->get_name();
+ while (!tok.is_newline() && !tok.is_eof()) {
+ if (!tok.is_space() && !tok.is_tab()) {
+ charinfo *s = tok.get_char(true /* required */);
+ string gl(f.contents());
+ gl += ' ';
+ gl += s->nm.contents();
+ gl += '\0';
+ charinfo *ci = get_charinfo(symbol(gl.contents()));
+ if (!ci)
+ break;
+ macro *m = ci->set_macro(0);
+ if (m)
+ delete m;
+ }
+ tok.next();
+ }
+ }
+ skip_line();
+}
+
+static void read_special_fonts(special_font_list **sp)
+{
+ special_font_list *s = *sp;
+ *sp = 0;
+ while (s != 0) {
+ special_font_list *tem = s;
+ s = s->next;
+ delete tem;
+ }
+ special_font_list **p = sp;
+ while (has_arg()) {
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to mark it as special");
+ else {
+ special_font_list *tem = new special_font_list;
+ tem->n = finfo.position;
+ tem->next = 0;
+ *p = tem;
+ p = &(tem->next);
+ }
+ }
+}
+
+void font_special_request()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to mark other fonts as special"
+ " contingently upon it"); // a mouthful :-/
+ else
+ read_special_fonts(&font_table[finfo.position]->sf);
+ skip_line();
+}
+
+void special_request()
+{
+ read_special_fonts(&global_special_fonts);
+ skip_line();
+}
+
+void font_zoom_request()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "to set a zoom factor for it");
+ else {
+ int n = finfo.position;
+ if (font_table[n]->is_style())
+ warning(WARN_FONT, "can't set zoom factor for a style");
+ else {
+ int zoom;
+ if (has_arg() && get_integer(&zoom)) {
+ if (zoom < 0)
+ warning(WARN_FONT, "can't use negative zoom factor");
+ else
+ font_table[n]->set_zoom(zoom);
+ }
+ else
+ font_table[n]->set_zoom(0);
+ }
+ }
+ skip_line();
+}
+
+int next_available_font_position()
+{
+ int i;
+ for (i = 1; i < font_table_size && font_table[i] != 0; i++)
+ ;
+ return i;
+}
+
+int symbol_fontno(symbol s)
+{
+ s = get_font_translation(s);
+ for (int i = 0; i < font_table_size; i++)
+ if (font_table[i] != 0 && font_table[i]->is_named(s))
+ return i;
+ return -1;
+}
+
+int is_good_fontno(int n)
+{
+ return n >= 0 && n < font_table_size && font_table[n] != 0;
+}
+
+int get_bold_fontno(int n)
+{
+ if (n >= 0 && n < font_table_size && font_table[n] != 0) {
+ hunits offset;
+ if (font_table[n]->get_bold(&offset))
+ return offset.to_units() + 1;
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
+hunits env_digit_width(environment *env)
+{
+ node *n = make_glyph_node(charset_table['0'], env);
+ if (n) {
+ hunits x = n->width();
+ delete n;
+ return x;
+ }
+ else
+ return H0;
+}
+
+hunits env_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return scale(fs.to_units()/3, env->get_space_size(), 12);
+ else
+ return font_table[fn]->get_space_width(fs, env->get_space_size());
+}
+
+hunits env_sentence_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return scale(fs.to_units()/3, env->get_sentence_space_size(), 12);
+ else
+ return font_table[fn]->get_space_width(fs, env->get_sentence_space_size());
+}
+
+hunits env_half_narrow_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return 0;
+ else
+ return font_table[fn]->get_half_narrow_space_width(fs);
+}
+
+hunits env_narrow_space_width(environment *env)
+{
+ int fn = env_definite_font(env);
+ font_size fs = env->get_font_size();
+ if (fn < 0 || fn >= font_table_size || font_table[fn] == 0)
+ return 0;
+ else
+ return font_table[fn]->get_narrow_space_width(fs);
+}
+
+void bold_font()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "for emboldening");
+ else {
+ int n = finfo.position;
+ if (has_arg()) {
+ // This is a bit non-orthogonal, but faithful to CSTR #54. We can
+ // only conditionally embolden a font specified by name, not
+ // position, so ".bd S B 4" works but ".bd 5 3 4" does not. The
+ // latter bolds the font at position 5 unconditionally, and
+ // ignores the third argument.
+ if (tok.usable_as_delimiter()) {
+ font_lookup_info finfo2;
+ if (!has_font(&finfo2))
+ font_lookup_error(finfo2, "for conditional emboldening");
+ else {
+ int f = finfo2.position;
+ units offset;
+ if (has_arg() && get_number(&offset, 'u') && offset >= 1)
+ font_table[f]->set_conditional_bold(n, hunits(offset - 1));
+ else
+ font_table[f]->conditional_unbold(n);
+ }
+ }
+ else {
+ font_lookup_info finfo2;
+ if (!has_font(&finfo2))
+ font_lookup_error(finfo2, "for conditional emboldening");
+ units offset;
+ if (get_number(&offset, 'u') && offset >= 1)
+ font_table[n]->set_bold(hunits(offset - 1));
+ else
+ font_table[n]->unbold();
+ }
+ }
+ else
+ font_table[n]->unbold();
+ }
+ skip_line();
+}
+
+track_kerning_function::track_kerning_function() : non_zero(0)
+{
+}
+
+track_kerning_function::track_kerning_function(int min_s, hunits min_a,
+ int max_s, hunits max_a)
+: non_zero(1), min_size(min_s), min_amount(min_a), max_size(max_s),
+ max_amount(max_a)
+{
+}
+
+int track_kerning_function::operator==(const track_kerning_function &tk)
+{
+ if (non_zero)
+ return (tk.non_zero
+ && min_size == tk.min_size
+ && min_amount == tk.min_amount
+ && max_size == tk.max_size
+ && max_amount == tk.max_amount);
+ else
+ return !tk.non_zero;
+}
+
+int track_kerning_function::operator!=(const track_kerning_function &tk)
+{
+ if (non_zero)
+ return (!tk.non_zero
+ || min_size != tk.min_size
+ || min_amount != tk.min_amount
+ || max_size != tk.max_size
+ || max_amount != tk.max_amount);
+ else
+ return tk.non_zero;
+}
+
+hunits track_kerning_function::compute(int size)
+{
+ if (non_zero) {
+ if (max_size <= min_size)
+ return min_amount;
+ else if (size <= min_size)
+ return min_amount;
+ else if (size >= max_size)
+ return max_amount;
+ else
+ return (scale(max_amount, size - min_size, max_size - min_size)
+ + scale(min_amount, max_size - size, max_size - min_size));
+ }
+ else
+ return H0;
+}
+
+void track_kern()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "for track kerning");
+ else {
+ int n = finfo.position, min_s, max_s;
+ hunits min_a, max_a;
+ if (has_arg()
+ && get_number(&min_s, 'z')
+ && get_hunits(&min_a, 'p')
+ && get_number(&max_s, 'z')
+ && get_hunits(&max_a, 'p')) {
+ track_kerning_function tk(min_s, min_a, max_s, max_a);
+ font_table[n]->set_track_kern(tk);
+ }
+ else {
+ track_kerning_function tk;
+ font_table[n]->set_track_kern(tk);
+ }
+ }
+ skip_line();
+}
+
+void constant_space()
+{
+ font_lookup_info finfo;
+ if (!has_font(&finfo))
+ font_lookup_error(finfo, "for constant spacing");
+ else {
+ int n = finfo.position, x, y;
+ if (!has_arg() || !get_integer(&x))
+ font_table[n]->set_constant_space(CONSTANT_SPACE_NONE);
+ else {
+ if (!has_arg() || !get_number(&y, 'z'))
+ font_table[n]->set_constant_space(CONSTANT_SPACE_RELATIVE, x);
+ else
+ font_table[n]->set_constant_space(CONSTANT_SPACE_ABSOLUTE,
+ scale(y*x,
+ units_per_inch,
+ 36*72*sizescale));
+ }
+ }
+ skip_line();
+}
+
+void ligature()
+{
+ int lig;
+ if (has_arg() && get_integer(&lig) && lig >= 0 && lig <= 2)
+ global_ligature_mode = lig;
+ else
+ global_ligature_mode = 1;
+ skip_line();
+}
+
+void kern_request()
+{
+ int k;
+ if (has_arg() && get_integer(&k))
+ global_kern_mode = k != 0;
+ else
+ global_kern_mode = 1;
+ skip_line();
+}
+
+void set_soft_hyphen_char()
+{
+ soft_hyphen_char = get_optional_char();
+ if (!soft_hyphen_char)
+ soft_hyphen_char = get_charinfo(HYPHEN_SYMBOL);
+ skip_line();
+}
+
+void init_output()
+{
+ if (suppress_output_flag)
+ the_output = new suppress_output_file;
+ else if (ascii_output_flag)
+ the_output = new ascii_output_file;
+ else
+ the_output = new troff_output_file;
+}
+
+class next_available_font_position_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *next_available_font_position_reg::get_string()
+{
+ return i_to_a(next_available_font_position());
+}
+
+class printing_reg : public reg {
+public:
+ const char *get_string();
+};
+
+const char *printing_reg::get_string()
+{
+ if (the_output)
+ return the_output->is_printing() ? "1" : "0";
+ else
+ return "0";
+}
+
+void init_node_requests()
+{
+ init_request("bd", bold_font);
+ init_request("cs", constant_space);
+ init_request("fp", font_position);
+ init_request("fschar", define_font_special_character);
+ init_request("fspecial", font_special_request);
+ init_request("fzoom", font_zoom_request);
+ init_request("ftr", font_translate);
+ init_request("kern", kern_request);
+ init_request("lg", ligature);
+ init_request("rfschar", remove_font_special_character);
+ init_request("shc", set_soft_hyphen_char);
+ init_request("special", special_request);
+ init_request("sty", style);
+ init_request("tkf", track_kern);
+ init_request("uf", underline_font);
+ register_dictionary.define(".fp", new next_available_font_position_reg);
+ register_dictionary.define(".kern",
+ new readonly_register(&global_kern_mode));
+ register_dictionary.define(".lg",
+ new readonly_register(&global_ligature_mode));
+ register_dictionary.define(".P", new printing_reg);
+ soft_hyphen_char = get_charinfo(HYPHEN_SYMBOL);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/node.h b/src/roff/troff/node.h
new file mode 100644
index 0000000..de82753
--- /dev/null
+++ b/src/roff/troff/node.h
@@ -0,0 +1,670 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+struct hyphen_list {
+ unsigned char hyphen;
+ unsigned char breakable;
+ unsigned char hyphenation_code;
+ hyphen_list *next;
+ hyphen_list(unsigned char code, hyphen_list *p = 0);
+};
+
+void hyphenate(hyphen_list *, unsigned);
+
+enum hyphenation_type { HYPHEN_MIDDLE, HYPHEN_BOUNDARY, HYPHEN_INHIBIT };
+
+class ascii_output_file;
+
+struct breakpoint;
+struct vertical_size;
+class charinfo;
+
+class macro;
+
+class troff_output_file;
+class tfont;
+class environment;
+
+class glyph_node;
+class diverted_space_node;
+class token_node;
+
+struct node {
+ node *next;
+ node *last;
+ statem *state;
+ statem *push_state;
+ int div_nest_level;
+ int is_special;
+ node();
+ node(node *);
+ node(node *, statem *, int);
+ node *add_char(charinfo *, environment *, hunits *, int *, node ** = 0);
+
+ virtual ~node();
+ virtual node *copy() = 0;
+ virtual int set_unformat_flag();
+ virtual int force_tprint() = 0;
+ virtual int is_tag() = 0;
+ virtual int get_break_code();
+ virtual hunits width();
+ virtual hunits subscript_correction();
+ virtual hunits italic_correction();
+ virtual hunits left_italic_correction();
+ virtual hunits skew();
+ virtual int nspaces();
+ virtual int merge_space(hunits, hunits, hunits);
+ virtual vunits vertical_width();
+ virtual node *last_char_node();
+ virtual void vertical_extent(vunits *, vunits *);
+ virtual int character_type();
+ virtual void set_vertical_size(vertical_size *);
+ virtual int ends_sentence();
+ virtual node *merge_self(node *);
+ virtual node *add_discretionary_hyphen();
+ virtual node *add_self(node *, hyphen_list **);
+ virtual hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ virtual void ascii_print(ascii_output_file *);
+ virtual void asciify(macro *);
+ virtual int discardable();
+ virtual void spread_space(int *, hunits *);
+ virtual void freeze_space();
+ virtual void is_escape_colon();
+ virtual breakpoint *get_breakpoints(hunits, int, breakpoint * = 0, int = 0);
+ virtual int nbreaks();
+ virtual void split(int, node **, node **);
+ virtual hyphenation_type get_hyphenation_type();
+ virtual int reread(int *);
+ virtual token_node *get_token_node();
+ virtual int overlaps_vertically();
+ virtual int overlaps_horizontally();
+ virtual units size();
+ virtual int interpret(macro *);
+
+ virtual node *merge_glyph_node(glyph_node *);
+ virtual tfont *get_tfont();
+ virtual color *get_glyph_color();
+ virtual color *get_fill_color();
+ virtual void tprint(troff_output_file *);
+ virtual void zero_width_tprint(troff_output_file *);
+
+ node *add_italic_correction(hunits *);
+
+ virtual int same(node *) = 0;
+ virtual const char *type() = 0;
+ virtual void debug_node();
+ virtual void debug_node_list();
+};
+
+inline node::node()
+: next(0), last(0), state(0), push_state(0), div_nest_level(0), is_special(0)
+{
+}
+
+inline node::node(node *n)
+: next(n), last(0), state(0), push_state(0), div_nest_level(0), is_special(0)
+{
+}
+
+inline node::node(node *n, statem *s, int divlevel)
+: next(n), last(0), push_state(0), div_nest_level(divlevel), is_special(0)
+{
+ if (s)
+ state = new statem(s);
+ else
+ state = 0;
+}
+
+inline node::~node()
+{
+ if (state != 0)
+ delete state;
+ if (push_state != 0)
+ delete push_state;
+}
+
+// 0 means it doesn't, 1 means it does, 2 means it's transparent
+
+int node_list_ends_sentence(node *);
+
+struct breakpoint {
+ breakpoint *next;
+ hunits width;
+ int nspaces;
+ node *nd;
+ int index;
+ char hyphenated;
+};
+
+class line_start_node : public node {
+public:
+ line_start_node() {}
+ node *copy() { return new line_start_node; }
+ int same(node *);
+ int force_tprint();
+ int is_tag();
+ const char *type();
+ void asciify(macro *);
+};
+
+class space_node : public node {
+private:
+protected:
+ hunits n;
+ char set;
+ char was_escape_colon;
+ color *col; /* for grotty */
+ space_node(hunits, int, int, color *, statem *, int, node * = 0);
+public:
+ space_node(hunits, color *, node * = 0);
+ node *copy();
+ int nspaces();
+ hunits width();
+ int discardable();
+ int merge_space(hunits, hunits, hunits);
+ void freeze_space();
+ void is_escape_colon();
+ void spread_space(int *, hunits *);
+ void tprint(troff_output_file *);
+ breakpoint *get_breakpoints(hunits, int, breakpoint * = 0, int = 0);
+ int nbreaks();
+ void split(int, node **, node **);
+ void ascii_print(ascii_output_file *);
+ int same(node *);
+ void asciify(macro *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hyphenation_type get_hyphenation_type();
+};
+
+struct width_list {
+ hunits width;
+ hunits sentence_width;
+ width_list *next;
+ width_list(hunits, hunits);
+ width_list(width_list *);
+};
+
+class word_space_node : public space_node {
+protected:
+ width_list *orig_width;
+ unsigned char unformat;
+ word_space_node(hunits, int, color *, width_list *, int, statem *, int,
+ node * = 0);
+public:
+ word_space_node(hunits, color *, width_list *, node * = 0);
+ ~word_space_node();
+ node *copy();
+ int reread(int *);
+ int set_unformat_flag();
+ void tprint(troff_output_file *);
+ int same(node *);
+ void asciify(macro *);
+ const char *type();
+ int merge_space(hunits, hunits, hunits);
+ int force_tprint();
+ int is_tag();
+};
+
+class unbreakable_space_node : public word_space_node {
+ unbreakable_space_node(hunits, int, color *, statem *, int, node * = 0);
+public:
+ unbreakable_space_node(hunits, color *, node * = 0);
+ node *copy();
+ int reread(int *);
+ void tprint(troff_output_file *);
+ int same(node *);
+ void asciify(macro *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ breakpoint *get_breakpoints(hunits, int, breakpoint * = 0, int = 0);
+ int nbreaks();
+ void split(int, node **, node **);
+ int merge_space(hunits, hunits, hunits);
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class diverted_space_node : public node {
+public:
+ vunits n;
+ diverted_space_node(vunits, node * = 0);
+ diverted_space_node(vunits, statem *, int, node * = 0);
+ node *copy();
+ int reread(int *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class diverted_copy_file_node : public node {
+ symbol filename;
+public:
+ vunits n;
+ diverted_copy_file_node(symbol, node * = 0);
+ diverted_copy_file_node(symbol, statem *, int, node * = 0);
+ node *copy();
+ int reread(int *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class extra_size_node : public node {
+ vunits n;
+public:
+ extra_size_node(vunits);
+ extra_size_node(vunits, statem *, int);
+ void set_vertical_size(vertical_size *);
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class vertical_size_node : public node {
+ vunits n;
+public:
+ vertical_size_node(vunits, statem *, int);
+ vertical_size_node(vunits);
+ void set_vertical_size(vertical_size *);
+ void asciify(macro *);
+ node *copy();
+ int set_unformat_flag();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class hmotion_node : public node {
+protected:
+ hunits n;
+ unsigned char was_tab;
+ unsigned char unformat;
+ color *col; /* for grotty */
+public:
+ hmotion_node(hunits i, color *c, node *nxt = 0)
+ : node(nxt), n(i), was_tab(0), unformat(0), col(c) {}
+ hmotion_node(hunits i, color *c, statem *s, int divlevel, node *nxt = 0)
+ : node(nxt, s, divlevel), n(i), was_tab(0), unformat(0), col(c) {}
+ hmotion_node(hunits i, int flag1, int flag2, color *c, statem *s,
+ int divlevel, node *nxt = 0)
+ : node(nxt, s, divlevel), n(i), was_tab(flag1), unformat(flag2),
+ col(c) {}
+ hmotion_node(hunits i, int flag1, int flag2, color *c, node *nxt = 0)
+ : node(nxt), n(i), was_tab(flag1), unformat(flag2), col(c) {}
+ node *copy();
+ int reread(int *);
+ int set_unformat_flag();
+ void asciify(macro *);
+ void tprint(troff_output_file *);
+ hunits width();
+ void ascii_print(ascii_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class space_char_hmotion_node : public hmotion_node {
+public:
+ space_char_hmotion_node(hunits, color *, node * = 0);
+ space_char_hmotion_node(hunits, color *, statem *, int, node * = 0);
+ node *copy();
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class vmotion_node : public node {
+ vunits n;
+ color *col; /* for grotty */
+public:
+ vmotion_node(vunits, color *);
+ vmotion_node(vunits, color *, statem *, int);
+ void tprint(troff_output_file *);
+ node *copy();
+ vunits vertical_width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class hline_node : public node {
+ hunits x;
+ node *n;
+public:
+ hline_node(hunits, node *, node * = 0);
+ hline_node(hunits, node *, statem *, int, node * = 0);
+ ~hline_node();
+ node *copy();
+ hunits width();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class vline_node : public node {
+ vunits x;
+ node *n;
+public:
+ vline_node(vunits, node *, node * = 0);
+ vline_node(vunits, node *, statem *, int, node * = 0);
+ ~vline_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ hunits width();
+ vunits vertical_width();
+ void vertical_extent(vunits *, vunits *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class dummy_node : public node {
+public:
+ dummy_node(node *nd = 0) : node(nd) {}
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hyphenation_type get_hyphenation_type();
+};
+
+class transparent_dummy_node : public node {
+public:
+ transparent_dummy_node(node *nd = 0) : node(nd) {}
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int ends_sentence();
+ hyphenation_type get_hyphenation_type();
+};
+
+class zero_width_node : public node {
+ node *n;
+public:
+ zero_width_node(node *);
+ zero_width_node(node *, statem *, int);
+ ~zero_width_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ void append(node *);
+ int character_type();
+ void vertical_extent(vunits *, vunits *);
+};
+
+class left_italic_corrected_node : public node {
+ node *n;
+ hunits x;
+public:
+ left_italic_corrected_node(node * = 0);
+ left_italic_corrected_node(statem *, int, node * = 0);
+ ~left_italic_corrected_node();
+ void tprint(troff_output_file *);
+ void ascii_print(ascii_output_file *);
+ void asciify(macro *);
+ node *copy();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ hunits width();
+ node *last_char_node();
+ void vertical_extent(vunits *, vunits *);
+ int ends_sentence();
+ int overlaps_horizontally();
+ int overlaps_vertically();
+ hyphenation_type get_hyphenation_type();
+ tfont *get_tfont();
+ int character_type();
+ hunits skew();
+ hunits italic_correction();
+ hunits subscript_correction();
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ node *add_self(node *, hyphen_list **);
+ node *merge_glyph_node(glyph_node *);
+};
+
+class overstrike_node : public node {
+ node *list;
+ hunits max_width;
+public:
+ overstrike_node();
+ overstrike_node(statem *, int);
+ ~overstrike_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ void overstrike(node *); // add another node to be overstruck
+ hunits width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ node *add_self(node *, hyphen_list **);
+ hyphen_list *get_hyphen_list(hyphen_list *, int *);
+ hyphenation_type get_hyphenation_type();
+};
+
+class bracket_node : public node {
+ node *list;
+ hunits max_width;
+public:
+ bracket_node();
+ bracket_node(statem *, int);
+ ~bracket_node();
+ node *copy();
+ void tprint(troff_output_file *);
+ void bracket(node *); // add another node to be overstruck
+ hunits width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class special_node : public node {
+ macro mac;
+ tfont *tf;
+ color *gcol;
+ color *fcol;
+ int no_init_string;
+ void tprint_start(troff_output_file *);
+ void tprint_char(troff_output_file *, unsigned char);
+ void tprint_end(troff_output_file *);
+public:
+ special_node(const macro &, int = 0);
+ special_node(const macro &, tfont *, color *, color *, statem *, int,
+ int = 0);
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int ends_sentence();
+ tfont *get_tfont();
+};
+
+class suppress_node : public node {
+ int is_on;
+ int emit_limits; // must we issue the extent of the area written out?
+ symbol filename;
+ char position;
+ int image_id;
+public:
+ suppress_node(int, int);
+ suppress_node(symbol, char, int);
+ suppress_node(int, int, symbol, char, int, statem *, int);
+ suppress_node(int, int, symbol, char, int);
+ node *copy();
+ void tprint(troff_output_file *);
+ hunits width();
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+private:
+ void put(troff_output_file *, const char *);
+};
+
+class tag_node : public node {
+public:
+ string tag_string;
+ int delayed;
+ tag_node();
+ tag_node(string, int);
+ tag_node(string, statem *, int, int);
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+ int ends_sentence();
+};
+
+struct hvpair {
+ hunits h;
+ vunits v;
+ hvpair();
+};
+
+class draw_node : public node {
+ int npoints;
+ font_size sz;
+ color *gcol;
+ color *fcol;
+ char code;
+ hvpair *point;
+public:
+ draw_node(char, hvpair *, int, font_size, color *, color *);
+ draw_node(char, hvpair *, int, font_size, color *, color *, statem *, int);
+ ~draw_node();
+ hunits width();
+ vunits vertical_width();
+ node *copy();
+ void tprint(troff_output_file *);
+ int same(node *);
+ const char *type();
+ int force_tprint();
+ int is_tag();
+};
+
+class charinfo;
+node *make_node(charinfo *, environment *);
+bool character_exists(charinfo *, environment *);
+
+int same_node_list(node *, node *);
+node *reverse_node_list(node *);
+void delete_node_list(node *);
+node *copy_node_list(node *);
+
+int get_bold_fontno(int);
+
+inline hyphen_list::hyphen_list(unsigned char code, hyphen_list *p)
+: hyphen(0), breakable(0), hyphenation_code(code), next(p)
+{
+}
+
+extern void read_desc();
+extern bool mount_font(int, symbol, symbol = NULL_SYMBOL);
+extern int check_font(symbol, symbol);
+extern int check_style(symbol);
+extern bool mount_style(int, symbol);
+extern int is_good_fontno(int);
+extern int symbol_fontno(symbol);
+extern int next_available_font_position();
+extern void init_size_table(int *);
+extern int get_underline_fontno();
+
+class output_file {
+ char make_g_plus_plus_shut_up;
+public:
+ output_file();
+ bool is_dying;
+ virtual ~output_file();
+ virtual void trailer(vunits);
+ virtual void flush() = 0;
+ virtual void transparent_char(unsigned char) = 0;
+ virtual void print_line(hunits x, vunits y, node *n,
+ vunits before, vunits after, hunits width) = 0;
+ virtual void begin_page(int pageno, vunits page_length) = 0;
+ virtual void copy_file(hunits x, vunits y, const char *filename) = 0;
+ virtual int is_printing() = 0;
+ virtual void put_filename(const char *, int);
+ virtual void on();
+ virtual void off();
+#ifdef COLUMN
+ virtual void vjustify(vunits, symbol);
+#endif /* COLUMN */
+ mtsm state;
+};
+
+#ifndef POPEN_MISSING
+extern char *pipe_command;
+#endif
+
+extern output_file *the_output;
+extern void init_output();
+int in_output_page_list(int);
+
+class font_family {
+ int *map;
+ int map_size;
+public:
+ const symbol nm;
+ font_family(symbol);
+ ~font_family();
+ int make_definite(int);
+ static void invalidate_fontno(int);
+};
+
+font_family *lookup_family(symbol);
+symbol get_font_name(int, environment *);
+symbol get_style_name(int);
+extern search_path include_search_path;
diff --git a/src/roff/troff/number.cpp b/src/roff/troff/number.cpp
new file mode 100644
index 0000000..348b557
--- /dev/null
+++ b/src/roff/troff/number.cpp
@@ -0,0 +1,712 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include "troff.h"
+#include "hvunits.h"
+#include "stringclass.h"
+#include "mtsm.h"
+#include "env.h"
+#include "token.h"
+#include "div.h"
+
+const vunits V0; // zero in vertical units
+const hunits H0; // zero in horizontal units
+
+int hresolution = 1;
+int vresolution = 1;
+int units_per_inch;
+int sizescale;
+
+static bool is_valid_expression(units *v, int scaling_unit,
+ bool is_parenthesized,
+ bool is_mandatory = false);
+static bool is_valid_expression_start();
+
+int get_vunits(vunits *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */)) {
+ *res = vunits(x);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int get_hunits(hunits *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */)) {
+ *res = hunits(x);
+ return 1;
+ }
+ else
+ return 0;
+}
+
+// for \B
+
+int get_number_rigidly(units *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */,
+ true /* is_mandatory */)) {
+ *res = x;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int get_number(units *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, si, false /* is_parenthesized */)) {
+ *res = x;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+int get_integer(int *res)
+{
+ if (!is_valid_expression_start())
+ return 0;
+ units x;
+ if (is_valid_expression(&x, 0, false /* is_parenthesized */)) {
+ *res = x;
+ return 1;
+ }
+ else
+ return 0;
+}
+
+enum incr_number_result { BAD, ABSOLUTE, INCREMENT, DECREMENT };
+
+static incr_number_result get_incr_number(units *res, unsigned char);
+
+int get_vunits(vunits *res, unsigned char si, vunits prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, si)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + v;
+ break;
+ case DECREMENT:
+ *res = prev_value - v;
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+int get_hunits(hunits *res, unsigned char si, hunits prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, si)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + v;
+ break;
+ case DECREMENT:
+ *res = prev_value - v;
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+int get_number(units *res, unsigned char si, units prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, si)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + v;
+ break;
+ case DECREMENT:
+ *res = prev_value - v;
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+int get_integer(int *res, int prev_value)
+{
+ units v;
+ switch (get_incr_number(&v, 0)) {
+ case BAD:
+ return 0;
+ case ABSOLUTE:
+ *res = v;
+ break;
+ case INCREMENT:
+ *res = prev_value + int(v);
+ break;
+ case DECREMENT:
+ *res = prev_value - int(v);
+ break;
+ default:
+ assert(0 == "unhandled switch case returned by get_incr_number()");
+ }
+ return 1;
+}
+
+
+static incr_number_result get_incr_number(units *res, unsigned char si)
+{
+ if (!is_valid_expression_start())
+ return BAD;
+ incr_number_result result = ABSOLUTE;
+ if (tok.ch() == '+') {
+ tok.next();
+ result = INCREMENT;
+ }
+ else if (tok.ch() == '-') {
+ tok.next();
+ result = DECREMENT;
+ }
+ if (is_valid_expression(res, si, false /* is_parenthesized */))
+ return result;
+ else
+ return BAD;
+}
+
+static bool is_valid_expression_start()
+{
+ while (tok.is_space())
+ tok.next();
+ if (tok.is_newline()) {
+ warning(WARN_MISSING, "numeric expression missing");
+ return false;
+ }
+ if (tok.is_tab()) {
+ warning(WARN_TAB, "expected numeric expression, got %1",
+ tok.description());
+ return false;
+ }
+ if (tok.is_right_brace()) {
+ warning(WARN_RIGHT_BRACE, "expected numeric expression, got right"
+ "brace escape sequence");
+ return false;
+ }
+ return true;
+}
+
+enum { OP_LEQ = 'L', OP_GEQ = 'G', OP_MAX = 'X', OP_MIN = 'N' };
+
+#define SCALING_UNITS "icfPmnpuvMsz"
+
+static bool is_valid_term(units *v, int scaling_unit,
+ bool is_parenthesized, bool is_mandatory);
+
+static bool is_valid_expression(units *v, int scaling_unit,
+ bool is_parenthesized,
+ bool is_mandatory)
+{
+ int result = is_valid_term(v, scaling_unit, is_parenthesized,
+ is_mandatory);
+ while (result) {
+ if (is_parenthesized)
+ tok.skip();
+ int op = tok.ch();
+ switch (op) {
+ case '+':
+ case '-':
+ case '/':
+ case '*':
+ case '%':
+ case ':':
+ case '&':
+ tok.next();
+ break;
+ case '>':
+ tok.next();
+ if (tok.ch() == '=') {
+ tok.next();
+ op = OP_GEQ;
+ }
+ else if (tok.ch() == '?') {
+ tok.next();
+ op = OP_MAX;
+ }
+ break;
+ case '<':
+ tok.next();
+ if (tok.ch() == '=') {
+ tok.next();
+ op = OP_LEQ;
+ }
+ else if (tok.ch() == '?') {
+ tok.next();
+ op = OP_MIN;
+ }
+ break;
+ case '=':
+ tok.next();
+ if (tok.ch() == '=')
+ tok.next();
+ break;
+ default:
+ return result;
+ }
+ units v2;
+ if (!is_valid_term(&v2, scaling_unit, is_parenthesized,
+ is_mandatory))
+ return false;
+ int overflow = 0;
+ switch (op) {
+ case '<':
+ *v = *v < v2;
+ break;
+ case '>':
+ *v = *v > v2;
+ break;
+ case OP_LEQ:
+ *v = *v <= v2;
+ break;
+ case OP_GEQ:
+ *v = *v >= v2;
+ break;
+ case OP_MIN:
+ if (*v > v2)
+ *v = v2;
+ break;
+ case OP_MAX:
+ if (*v < v2)
+ *v = v2;
+ break;
+ case '=':
+ *v = *v == v2;
+ break;
+ case '&':
+ *v = *v > 0 && v2 > 0;
+ break;
+ case ':':
+ *v = *v > 0 || v2 > 0;
+ break;
+ case '+':
+ if (v2 < 0) {
+ if (*v < INT_MIN - v2)
+ overflow = 1;
+ }
+ else if (v2 > 0) {
+ if (*v > INT_MAX - v2)
+ overflow = 1;
+ }
+ if (overflow) {
+ error("addition overflow");
+ return false;
+ }
+ *v += v2;
+ break;
+ case '-':
+ if (v2 < 0) {
+ if (*v > INT_MAX + v2)
+ overflow = 1;
+ }
+ else if (v2 > 0) {
+ if (*v < INT_MIN + v2)
+ overflow = 1;
+ }
+ if (overflow) {
+ error("subtraction overflow");
+ return false;
+ }
+ *v -= v2;
+ break;
+ case '*':
+ if (v2 < 0) {
+ if (*v > 0) {
+ if ((unsigned)*v > -(unsigned)INT_MIN / -(unsigned)v2)
+ overflow = 1;
+ }
+ else if (-(unsigned)*v > INT_MAX / -(unsigned)v2)
+ overflow = 1;
+ }
+ else if (v2 > 0) {
+ if (*v > 0) {
+ if (*v > INT_MAX / v2)
+ overflow = 1;
+ }
+ else if (-(unsigned)*v > -(unsigned)INT_MIN / v2)
+ overflow = 1;
+ }
+ if (overflow) {
+ error("multiplication overflow");
+ return false;
+ }
+ *v *= v2;
+ break;
+ case '/':
+ if (v2 == 0) {
+ error("division by zero");
+ return false;
+ }
+ *v /= v2;
+ break;
+ case '%':
+ if (v2 == 0) {
+ error("modulus by zero");
+ return false;
+ }
+ *v %= v2;
+ break;
+ default:
+ assert(0 == "unhandled switch case while processing operator");
+ }
+ }
+ return result;
+}
+
+static bool is_valid_term(units *v, int scaling_unit,
+ bool is_parenthesized, bool is_mandatory)
+{
+ int negative = 0;
+ for (;;)
+ if (is_parenthesized && tok.is_space())
+ tok.next();
+ else if (tok.ch() == '+')
+ tok.next();
+ else if (tok.ch() == '-') {
+ tok.next();
+ negative = !negative;
+ }
+ else
+ break;
+ unsigned char c = tok.ch();
+ switch (c) {
+ case '|':
+ // | is not restricted to the outermost level
+ // tbl uses this
+ tok.next();
+ if (!is_valid_term(v, scaling_unit, is_parenthesized, is_mandatory))
+ return false;
+ int tem;
+ tem = (scaling_unit == 'v'
+ ? curdiv->get_vertical_position().to_units()
+ : curenv->get_input_line_position().to_units());
+ if (tem >= 0) {
+ if (*v < INT_MIN + tem) {
+ error("numeric overflow");
+ return false;
+ }
+ }
+ else {
+ if (*v > INT_MAX + tem) {
+ error("numeric overflow");
+ return false;
+ }
+ }
+ *v -= tem;
+ if (negative) {
+ if (*v == INT_MIN) {
+ error("numeric overflow");
+ return false;
+ }
+ *v = -*v;
+ }
+ return true;
+ case '(':
+ tok.next();
+ c = tok.ch();
+ if (c == ')') {
+ if (is_mandatory)
+ return false;
+ warning(WARN_SYNTAX, "empty parentheses");
+ tok.next();
+ *v = 0;
+ return true;
+ }
+ else if (c != 0 && strchr(SCALING_UNITS, c) != 0) {
+ tok.next();
+ if (tok.ch() == ';') {
+ tok.next();
+ scaling_unit = c;
+ }
+ else {
+ error("expected ';' after scaling unit, got %1",
+ tok.description());
+ return false;
+ }
+ }
+ else if (c == ';') {
+ scaling_unit = 0;
+ tok.next();
+ }
+ if (!is_valid_expression(v, scaling_unit,
+ true /* is_parenthesized */, is_mandatory))
+ return false;
+ tok.skip();
+ if (tok.ch() != ')') {
+ if (is_mandatory)
+ return false;
+ warning(WARN_SYNTAX, "expected ')', got %1", tok.description());
+ }
+ else
+ tok.next();
+ if (negative) {
+ if (*v == INT_MIN) {
+ error("numeric overflow");
+ return false;
+ }
+ *v = -*v;
+ }
+ return true;
+ case '.':
+ *v = 0;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ *v = 0;
+ do {
+ if (*v > INT_MAX/10) {
+ error("numeric overflow");
+ return false;
+ }
+ *v *= 10;
+ if (*v > INT_MAX - (int(c) - '0')) {
+ error("numeric overflow");
+ return false;
+ }
+ *v += c - '0';
+ tok.next();
+ c = tok.ch();
+ } while (csdigit(c));
+ break;
+ case '/':
+ case '*':
+ case '%':
+ case ':':
+ case '&':
+ case '>':
+ case '<':
+ case '=':
+ warning(WARN_SYNTAX, "empty left operand to '%1' operator", c);
+ *v = 0;
+ return is_mandatory ? false : true;
+ default:
+ warning(WARN_NUMBER, "expected numeric expression, got %1",
+ tok.description());
+ return false;
+ }
+ int divisor = 1;
+ if (tok.ch() == '.') {
+ tok.next();
+ for (;;) {
+ c = tok.ch();
+ if (!csdigit(c))
+ break;
+ // we may multiply the divisor by 254 later on
+ if (divisor <= INT_MAX/2540 && *v <= (INT_MAX - 9)/10) {
+ *v *= 10;
+ *v += c - '0';
+ divisor *= 10;
+ }
+ tok.next();
+ }
+ }
+ int si = scaling_unit;
+ int do_next = 0;
+ if ((c = tok.ch()) != 0 && strchr(SCALING_UNITS, c) != 0) {
+ switch (scaling_unit) {
+ case 0:
+ warning(WARN_SCALE, "scaling unit invalid in context");
+ break;
+ case 'z':
+ if (c != 'u' && c != 'z') {
+ warning(WARN_SCALE, "'%1' scaling unit invalid in context;"
+ " convert to 'z' or 'u'", c);
+ break;
+ }
+ si = c;
+ break;
+ case 'u':
+ si = c;
+ break;
+ default:
+ if (c == 'z') {
+ warning(WARN_SCALE, "'z' scaling unit invalid in context");
+ break;
+ }
+ si = c;
+ break;
+ }
+ // Don't do tok.next() here because the next token might be \s,
+ // which would affect the interpretation of m.
+ do_next = 1;
+ }
+ switch (si) {
+ case 'i':
+ *v = scale(*v, units_per_inch, divisor);
+ break;
+ case 'c':
+ *v = scale(*v, units_per_inch*100, divisor*254);
+ break;
+ case 0:
+ case 'u':
+ if (divisor != 1)
+ *v /= divisor;
+ break;
+ case 'f':
+ *v = scale(*v, 65536, divisor);
+ break;
+ case 'p':
+ *v = scale(*v, units_per_inch, divisor*72);
+ break;
+ case 'P':
+ *v = scale(*v, units_per_inch, divisor*6);
+ break;
+ case 'm':
+ {
+ // Convert to hunits so that with -Tascii 'm' behaves as in nroff.
+ hunits em = curenv->get_size();
+ *v = scale(*v, em.is_zero() ? hresolution : em.to_units(),
+ divisor);
+ }
+ break;
+ case 'M':
+ {
+ hunits em = curenv->get_size();
+ *v = scale(*v, em.is_zero() ? hresolution : em.to_units(),
+ (divisor * 100));
+ }
+ break;
+ case 'n':
+ {
+ // Convert to hunits so that with -Tascii 'n' behaves as in nroff.
+ hunits en = curenv->get_size() / 2;
+ *v = scale(*v, en.is_zero() ? hresolution : en.to_units(),
+ divisor);
+ }
+ break;
+ case 'v':
+ *v = scale(*v, curenv->get_vertical_spacing().to_units(), divisor);
+ break;
+ case 's':
+ while (divisor > INT_MAX/(sizescale*72)) {
+ divisor /= 10;
+ *v /= 10;
+ }
+ *v = scale(*v, units_per_inch, divisor*sizescale*72);
+ break;
+ case 'z':
+ *v = scale(*v, sizescale, divisor);
+ break;
+ default:
+ assert(0 == "unhandled switch case when processing scaling unit");
+ }
+ if (do_next)
+ tok.next();
+ if (negative) {
+ if (*v == INT_MIN) {
+ error("numeric overflow");
+ return false;
+ }
+ *v = -*v;
+ }
+ return true;
+}
+
+units scale(units n, units x, units y)
+{
+ assert(x >= 0 && y > 0);
+ if (x == 0)
+ return 0;
+ if (n >= 0) {
+ if (n <= INT_MAX/x)
+ return (n*x)/y;
+ }
+ else {
+ if (-(unsigned)n <= -(unsigned)INT_MIN/x)
+ return (n*x)/y;
+ }
+ double res = n*double(x)/double(y);
+ if (res > INT_MAX) {
+ error("numeric overflow");
+ return INT_MAX;
+ }
+ else if (res < INT_MIN) {
+ error("numeric overflow");
+ return INT_MIN;
+ }
+ return int(res);
+}
+
+vunits::vunits(units x)
+{
+ // Don't depend on rounding direction when dividing negative integers.
+ if (vresolution == 1)
+ n = x;
+ else
+ n = (x < 0
+ ? -((-x + (vresolution / 2) - 1) / vresolution)
+ : (x + (vresolution / 2) - 1) / vresolution);
+}
+
+hunits::hunits(units x)
+{
+ // Don't depend on rounding direction when dividing negative integers.
+ if (hresolution == 1)
+ n = x;
+ else
+ n = (x < 0
+ ? -((-x + (hresolution / 2) - 1) / hresolution)
+ : (x + (hresolution / 2) - 1) / hresolution);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/reg.cpp b/src/roff/troff/reg.cpp
new file mode 100644
index 0000000..6b7689d
--- /dev/null
+++ b/src/roff/troff/reg.cpp
@@ -0,0 +1,479 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "troff.h"
+#include "dictionary.h"
+#include "token.h"
+#include "request.h"
+#include "reg.h"
+
+object_dictionary register_dictionary(101);
+
+bool reg::get_value(units * /*d*/)
+{
+ return false;
+}
+
+void reg::increment()
+{
+ error("can't increment read-only register");
+}
+
+void reg::decrement()
+{
+ error("can't decrement read-only register");
+}
+
+void reg::set_increment(units /*n*/)
+{
+ error("can't automatically increment read-only register");
+}
+
+void reg::alter_format(char /*f*/, int /*w*/)
+{
+ error("can't assign format of read-only register");
+}
+
+const char *reg::get_format()
+{
+ return "0";
+}
+
+void reg::set_value(units /*n*/)
+{
+ error("can't write read-only register");
+}
+
+general_reg::general_reg() : format('1'), width(0), inc(0)
+{
+}
+
+static char uppercase_array[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z',
+};
+
+static char lowercase_array[] = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z',
+};
+
+static const char *number_value_to_ascii(int value, char format, int width)
+{
+ static char buf[128]; // must be at least 21
+ switch(format) {
+ case '1':
+ if (width <= 0)
+ return i_to_a(value);
+ else if (width > int(sizeof(buf) - 2))
+ sprintf(buf, "%.*d", int(sizeof(buf) - 2), int(value));
+ else
+ sprintf(buf, "%.*d", width, int(value));
+ break;
+ case 'i':
+ case 'I':
+ {
+ char *p = buf;
+ // troff uses z and w to represent 10000 and 5000 in Roman
+ // numerals; I can find no historical basis for this usage
+ const char *s = format == 'i' ? "zwmdclxvi" : "ZWMDCLXVI";
+ int n = int(value);
+ if (n >= 40000 || n <= -40000) {
+ error("magnitude of '%1' too big for i or I format", n);
+ return i_to_a(n);
+ }
+ if (n == 0) {
+ *p++ = '0';
+ *p = 0;
+ break;
+ }
+ if (n < 0) {
+ *p++ = '-';
+ n = -n;
+ }
+ while (n >= 10000) {
+ *p++ = s[0];
+ n -= 10000;
+ }
+ for (int i = 1000; i > 0; i /= 10, s += 2) {
+ int m = n/i;
+ n -= m*i;
+ switch (m) {
+ case 3:
+ *p++ = s[2];
+ /* falls through */
+ case 2:
+ *p++ = s[2];
+ /* falls through */
+ case 1:
+ *p++ = s[2];
+ break;
+ case 4:
+ *p++ = s[2];
+ *p++ = s[1];
+ break;
+ case 8:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 7:
+ *p++ = s[1];
+ *p++ = s[2];
+ *p++ = s[2];
+ break;
+ case 6:
+ *p++ = s[1];
+ *p++ = s[2];
+ break;
+ case 5:
+ *p++ = s[1];
+ break;
+ case 9:
+ *p++ = s[2];
+ *p++ = s[0];
+ }
+ }
+ *p = 0;
+ break;
+ }
+ case 'a':
+ case 'A':
+ {
+ int n = value;
+ char *p = buf;
+ if (n == 0) {
+ *p++ = '0';
+ *p = 0;
+ }
+ else {
+ if (n < 0) {
+ n = -n;
+ *p++ = '-';
+ }
+ // this is a bit tricky
+ while (n > 0) {
+ int d = n % 26;
+ if (d == 0)
+ d = 26;
+ n -= d;
+ n /= 26;
+ *p++ = format == 'a' ? lowercase_array[d - 1] :
+ uppercase_array[d - 1];
+ }
+ *p-- = 0;
+ char *q = buf[0] == '-' ? buf + 1 : buf;
+ while (q < p) {
+ char temp = *q;
+ *q = *p;
+ *p = temp;
+ --p;
+ ++q;
+ }
+ }
+ break;
+ }
+ default:
+ assert(0);
+ break;
+ }
+ return buf;
+}
+
+const char *general_reg::get_string()
+{
+ units n;
+ if (!get_value(&n))
+ return "";
+ return number_value_to_ascii(n, format, width);
+}
+
+
+void general_reg::increment()
+{
+ int n;
+ if (get_value(&n))
+ set_value(n + inc);
+}
+
+void general_reg::decrement()
+{
+ int n;
+ if (get_value(&n))
+ set_value(n - inc);
+}
+
+void general_reg::set_increment(units n)
+{
+ inc = n;
+}
+
+void general_reg::alter_format(char f, int w)
+{
+ format = f;
+ width = w;
+}
+
+static const char *number_format_to_ascii(char format, int width)
+{
+ static char buf[24];
+ if (format == '1') {
+ if (width > 0) {
+ int n = width;
+ if (n > int(sizeof(buf)) - 1)
+ n = int(sizeof(buf)) - 1;
+ sprintf(buf, "%.*d", n, 0);
+ return buf;
+ }
+ else
+ return "0";
+ }
+ else {
+ buf[0] = format;
+ buf[1] = '\0';
+ return buf;
+ }
+}
+
+const char *general_reg::get_format()
+{
+ return number_format_to_ascii(format, width);
+}
+
+class number_reg : public general_reg {
+ units value;
+public:
+ number_reg();
+ bool get_value(units *);
+ void set_value(units);
+};
+
+number_reg::number_reg() : value(0)
+{
+}
+
+bool number_reg::get_value(units *res)
+{
+ *res = value;
+ return true;
+}
+
+void number_reg::set_value(units n)
+{
+ value = n;
+}
+
+variable_reg::variable_reg(units *p) : ptr(p)
+{
+}
+
+void variable_reg::set_value(units n)
+{
+ *ptr = n;
+}
+
+bool variable_reg::get_value(units *res)
+{
+ *res = *ptr;
+ return true;
+}
+
+void define_number_reg()
+{
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ units v;
+ units prev_value;
+ if (!r || !r->get_value(&prev_value))
+ prev_value = 0;
+ if (get_number(&v, 'u', prev_value)) {
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ r->set_value(v);
+ if (tok.is_space() && has_arg() && get_number(&v, 'u'))
+ r->set_increment(v);
+ }
+ skip_line();
+}
+
+#if 0
+void inline_define_reg()
+{
+ token start;
+ start.next();
+ if (!start.delimiter(true /* report error */))
+ return;
+ tok.next();
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null())
+ return;
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ units v;
+ units prev_value;
+ if (!r->get_value(&prev_value))
+ prev_value = 0;
+ if (get_number(&v, 'u', prev_value)) {
+ r->set_value(v);
+ if (start != tok) {
+ if (get_number(&v, 'u')) {
+ r->set_increment(v);
+ if (start != tok)
+ warning(WARN_DELIM, "closing delimiter does not match");
+ }
+ }
+ }
+}
+#endif
+
+void set_number_reg(symbol nm, units n)
+{
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ r->set_value(n);
+}
+
+reg *lookup_number_reg(symbol nm)
+{
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ warning(WARN_REG, "register '%1' not defined", nm.contents());
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ return r;
+}
+
+void alter_format()
+{
+ symbol nm = get_name(true /* required */);
+ if (nm.is_null()) {
+ skip_line();
+ return;
+ }
+ reg *r = (reg *)register_dictionary.lookup(nm);
+ if (r == 0) {
+ r = new number_reg;
+ register_dictionary.define(nm, r);
+ }
+ tok.skip();
+ char c = tok.ch();
+ if (csdigit(c)) {
+ int n = 0;
+ do {
+ ++n;
+ tok.next();
+ } while (csdigit(tok.ch()));
+ r->alter_format('1', n);
+ }
+ else if (c == 'i' || c == 'I' || c == 'a' || c == 'A')
+ r->alter_format(c);
+ else if (tok.is_newline() || tok.is_eof())
+ warning(WARN_MISSING, "missing register format");
+ else
+ if (!cscntrl(c))
+ error("invalid register format '%1'", c);
+ else
+ error("invalid register format (got %1)", tok.description());
+ skip_line();
+}
+
+void remove_reg()
+{
+ for (;;) {
+ symbol s = get_name();
+ if (s.is_null())
+ break;
+ register_dictionary.remove(s);
+ }
+ skip_line();
+}
+
+void alias_reg()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null()) {
+ if (!register_dictionary.alias(s1, s2))
+ warning(WARN_REG, "register '%1' not defined", s2.contents());
+ }
+ }
+ skip_line();
+}
+
+void rename_reg()
+{
+ symbol s1 = get_name(true /* required */);
+ if (!s1.is_null()) {
+ symbol s2 = get_name(true /* required */);
+ if (!s2.is_null())
+ register_dictionary.rename(s1, s2);
+ }
+ skip_line();
+}
+
+void print_number_regs()
+{
+ object_dictionary_iterator iter(register_dictionary);
+ reg *r;
+ symbol s;
+ while (iter.get(&s, (object **)&r)) {
+ assert(!s.is_null());
+ errprint("%1\t", s.contents());
+ const char *p = r->get_string();
+ if (p)
+ errprint(p);
+ errprint("\n");
+ }
+ fflush(stderr);
+ skip_line();
+}
+
+void init_reg_requests()
+{
+ init_request("rr", remove_reg);
+ init_request("nr", define_number_reg);
+ init_request("af", alter_format);
+ init_request("aln", alias_reg);
+ init_request("rnn", rename_reg);
+ init_request("pnr", print_number_regs);
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/reg.h b/src/roff/troff/reg.h
new file mode 100644
index 0000000..132bcf1
--- /dev/null
+++ b/src/roff/troff/reg.h
@@ -0,0 +1,74 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+class reg : public object {
+public:
+ virtual const char *get_string() = 0;
+ virtual bool get_value(units *);
+ virtual void increment();
+ virtual void decrement();
+ virtual void set_increment(units);
+ virtual void alter_format(char f, int w = 0);
+ virtual const char *get_format();
+ virtual void set_value(units);
+};
+
+class readonly_register : public reg {
+ int *p;
+public:
+ readonly_register(int *);
+ const char *get_string();
+};
+
+class general_reg : public reg {
+ char format;
+ int width;
+ int inc;
+public:
+ general_reg();
+ const char *get_string();
+ void increment();
+ void decrement();
+ void alter_format(char f, int w = 0);
+ void set_increment(units);
+ const char *get_format();
+ void add_value(units);
+
+ void set_value(units) = 0;
+ bool get_value(units *) = 0;
+};
+
+class variable_reg : public general_reg {
+ units *ptr;
+public:
+ variable_reg(int *);
+ void set_value(units);
+ bool get_value(units *);
+};
+
+extern object_dictionary register_dictionary;
+extern void set_number_reg(symbol nm, units n);
+extern void check_output_limits(int x, int y);
+extern void reset_output_registers();
+
+reg *lookup_number_reg(symbol);
+#if 0
+void inline_define_reg();
+#endif
diff --git a/src/roff/troff/request.h b/src/roff/troff/request.h
new file mode 100644
index 0000000..0991e49
--- /dev/null
+++ b/src/roff/troff/request.h
@@ -0,0 +1,97 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+typedef void (*REQUEST_FUNCP)();
+
+class macro;
+
+class request_or_macro : public object {
+public:
+ request_or_macro();
+ virtual void invoke(symbol, bool) = 0;
+ virtual macro *to_macro();
+};
+
+class request : public request_or_macro {
+ REQUEST_FUNCP p;
+public:
+ void invoke(symbol, bool);
+ request(REQUEST_FUNCP);
+};
+
+void delete_request_or_macro(request_or_macro *);
+
+extern object_dictionary request_dictionary;
+
+class macro_header;
+struct node;
+
+class macro : public request_or_macro {
+ const char *filename; // where was it defined?
+ int lineno;
+ int len;
+ int empty_macro;
+ int is_a_diversion;
+ int is_a_string; // if it contains no newline
+public:
+ macro_header *p;
+ macro();
+ ~macro();
+ macro(const macro &);
+ macro(int);
+ macro &operator=(const macro &);
+ void append(unsigned char);
+ void append(node *);
+ void append_unsigned(unsigned int);
+ void append_int(int);
+ void append_str(const char *);
+ void set(unsigned char, int);
+ unsigned char get(int);
+ int length();
+ void invoke(symbol, bool);
+ macro *to_macro();
+ void print_size();
+ int empty();
+ int is_diversion();
+ int is_string();
+ void clear_string_flag();
+ friend class string_iterator;
+ friend void chop_macro();
+ friend void substring_request();
+ friend int operator==(const macro &, const macro &);
+};
+
+extern void init_input_requests();
+extern void init_markup_requests();
+extern void init_div_requests();
+extern void init_node_requests();
+extern void init_reg_requests();
+extern void init_env_requests();
+extern void init_hyphen_requests();
+extern void init_request(const char *, REQUEST_FUNCP);
+
+class charinfo;
+class environment;
+
+node *charinfo_to_node_list(charinfo *, const environment *);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/token.h b/src/roff/troff/token.h
new file mode 100644
index 0000000..bd76055
--- /dev/null
+++ b/src/roff/troff/token.h
@@ -0,0 +1,249 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+class charinfo;
+struct node;
+class vunits;
+
+class token {
+ symbol nm;
+ node *nd;
+ unsigned char c;
+ int val;
+ units dim;
+ enum token_type {
+ TOKEN_BACKSPACE,
+ TOKEN_BEGIN_TRAP,
+ TOKEN_CHAR, // a normal printing character
+ TOKEN_DUMMY, // \&
+ TOKEN_EMPTY, // this is the initial value
+ TOKEN_END_TRAP,
+ TOKEN_ESCAPE, // \e
+ TOKEN_HYPHEN_INDICATOR,
+ TOKEN_INTERRUPT, // \c
+ TOKEN_ITALIC_CORRECTION, // \/
+ TOKEN_LEADER, // ^A
+ TOKEN_LEFT_BRACE,
+ TOKEN_MARK_INPUT, // \k -- 'nm' is the name of the register
+ TOKEN_NEWLINE, // newline
+ TOKEN_NODE,
+ TOKEN_NUMBERED_CHAR,
+ TOKEN_PAGE_EJECTOR,
+ TOKEN_REQUEST,
+ TOKEN_RIGHT_BRACE,
+ TOKEN_SPACE, // ' ' -- ordinary space
+ TOKEN_SPECIAL, // a special character -- \' \` \- \(xx \[xxx]
+ TOKEN_SPREAD, // \p -- break and spread output line
+ TOKEN_STRETCHABLE_SPACE, // \~
+ TOKEN_UNSTRETCHABLE_SPACE, // '\ '
+ TOKEN_HORIZONTAL_SPACE, // \|, \^, \0, \h
+ TOKEN_TAB, // tab
+ TOKEN_TRANSPARENT, // \!
+ TOKEN_TRANSPARENT_DUMMY, // \)
+ TOKEN_ZERO_WIDTH_BREAK, // \:
+ TOKEN_EOF // end of file
+ } type;
+public:
+ token();
+ ~token();
+ token(const token &);
+ void operator=(const token &);
+ void next();
+ void process();
+ void skip();
+ int nspaces(); // 1 if space, 0 otherwise
+ bool is_eof();
+ bool is_space();
+ bool is_stretchable_space();
+ bool is_unstretchable_space();
+ bool is_horizontal_space();
+ bool is_white_space();
+ bool is_special();
+ bool is_newline();
+ bool is_tab();
+ bool is_leader();
+ bool is_backspace();
+ bool usable_as_delimiter(bool = false);
+ bool is_dummy();
+ bool is_transparent_dummy();
+ bool is_transparent();
+ bool is_left_brace();
+ bool is_right_brace();
+ bool is_page_ejector();
+ bool is_hyphen_indicator();
+ bool is_zero_width_break();
+ int operator==(const token &); // need this for delimiters, and for conditions
+ int operator!=(const token &); // ditto
+ unsigned char ch();
+ charinfo *get_char(bool = false);
+ int add_to_zero_width_node_list(node **);
+ void make_space();
+ void make_newline();
+ const char *description();
+
+ friend void process_input_stack();
+ friend node *do_overstrike();
+};
+
+extern token tok; // the current token
+
+extern symbol get_name(bool = false);
+extern symbol get_long_name(bool = false);
+extern charinfo *get_optional_char();
+extern char *read_string();
+extern void check_missing_character();
+extern void skip_line();
+extern void handle_initial_title();
+
+enum char_mode {
+ CHAR_NORMAL,
+ CHAR_FALLBACK,
+ CHAR_FONT_SPECIAL,
+ CHAR_SPECIAL
+};
+
+extern void do_define_character(char_mode, const char * = 0);
+
+class hunits;
+extern void read_title_parts(node **part, hunits *part_width);
+
+extern int get_number_rigidly(units *result, unsigned char si);
+
+extern int get_number(units *result, unsigned char si);
+extern int get_integer(int *result);
+
+extern int get_number(units *result, unsigned char si, units prev_value);
+extern int get_integer(int *result, int prev_value);
+
+void interpolate_number_reg(symbol, int);
+
+const char *asciify(int c);
+
+inline bool token::is_newline()
+{
+ return type == TOKEN_NEWLINE;
+}
+
+inline bool token::is_space()
+{
+ return type == TOKEN_SPACE;
+}
+
+inline bool token::is_stretchable_space()
+{
+ return type == TOKEN_STRETCHABLE_SPACE;
+}
+
+inline bool token::is_unstretchable_space()
+{
+ return type == TOKEN_UNSTRETCHABLE_SPACE;
+}
+
+inline bool token::is_horizontal_space()
+{
+ return type == TOKEN_HORIZONTAL_SPACE;
+}
+
+inline bool token::is_special()
+{
+ return type == TOKEN_SPECIAL;
+}
+
+inline int token::nspaces()
+{
+ return (int)(type == TOKEN_SPACE);
+}
+
+inline bool token::is_white_space()
+{
+ return type == TOKEN_SPACE || type == TOKEN_TAB;
+}
+
+inline bool token::is_transparent()
+{
+ return type == TOKEN_TRANSPARENT;
+}
+
+inline bool token::is_page_ejector()
+{
+ return type == TOKEN_PAGE_EJECTOR;
+}
+
+inline unsigned char token::ch()
+{
+ return type == TOKEN_CHAR ? c : 0;
+}
+
+inline bool token::is_eof()
+{
+ return type == TOKEN_EOF;
+}
+
+inline bool token::is_dummy()
+{
+ return type == TOKEN_DUMMY;
+}
+
+inline bool token::is_transparent_dummy()
+{
+ return type == TOKEN_TRANSPARENT_DUMMY;
+}
+
+inline bool token::is_left_brace()
+{
+ return type == TOKEN_LEFT_BRACE;
+}
+
+inline bool token::is_right_brace()
+{
+ return type == TOKEN_RIGHT_BRACE;
+}
+
+inline bool token::is_tab()
+{
+ return type == TOKEN_TAB;
+}
+
+inline bool token::is_leader()
+{
+ return type == TOKEN_LEADER;
+}
+
+inline bool token::is_backspace()
+{
+ return type == TOKEN_BACKSPACE;
+}
+
+inline bool token::is_hyphen_indicator()
+{
+ return type == TOKEN_HYPHEN_INDICATOR;
+}
+
+inline bool token::is_zero_width_break()
+{
+ return type == TOKEN_ZERO_WIDTH_BREAK;
+}
+
+bool has_arg();
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/roff/troff/troff.1.man b/src/roff/troff/troff.1.man
new file mode 100644
index 0000000..d83c4a5
--- /dev/null
+++ b/src/roff/troff/troff.1.man
@@ -0,0 +1,1047 @@
+'\" t
+.TH @g@troff @MAN1EXT@ "@MDATE@" "groff @VERSION@"
+.SH Name
+@g@troff \- GNU
+.I roff
+typesetter and document formatter
+.
+.
+.\" ====================================================================
+.\" Legal Terms
+.\" ====================================================================
+.\"
+.\" Copyright (C) 1989-2021 Free Software Foundation, Inc.
+.\"
+.\" This file is part of groff, the GNU roff type-setting system.
+.\"
+.\" Permission is granted to copy, distribute and/or modify this
+.\" document under the terms of the GNU Free Documentation License,
+.\" Version 1.3 or any later version published by the Free Software
+.\" Foundation; with no Invariant Sections, with no Front-Cover Texts,
+.\" and with no Back-Cover Texts.
+.\"
+.\" A copy of the Free Documentation License is included as a file
+.\" called FDL in the main directory of the groff source package.
+.
+.
+.\" Save and disable compatibility mode (for, e.g., Solaris 10/11).
+.do nr *groff_troff_1_man_C \n[.cp]
+.cp 0
+.
+.\" Define fallback for groff 1.23's MR macro if the system lacks it.
+.nr do-fallback 0
+.if !\n(.f .nr do-fallback 1 \" mandoc
+.if \n(.g .if !d MR .nr do-fallback 1 \" older groff
+.if !\n(.g .nr do-fallback 1 \" non-groff *roff
+.if \n[do-fallback] \{\
+. de MR
+. ie \\n(.$=1 \
+. I \%\\$1
+. el \
+. IR \%\\$1 (\\$2)\\$3
+. .
+.\}
+.rr do-fallback
+.
+.
+.\" ====================================================================
+.SH Synopsis
+.\" ====================================================================
+.
+.SY @g@troff
+.RB [ \-abcCEiRUz ]
+.RB [ \-d\~\c
+.IR ctext ]
+.RB [ \-d\~\c
+.IB string =\c
+.IR text ]
+.RB [ \-f\~\c
+.IR font-family ]
+.RB [ \-F\~\c
+.IR font-directory ]
+.RB [ \-I\~\c
+.IR inclusion-directory ]
+.RB [ \-m\~\c
+.IR macro-package ]
+.RB [ \-M\~\c
+.IR macro-directory ]
+.RB [ \-n\~\c
+.IR page-number ]
+.RB [ \-o\~\c
+.IR page-list ]
+.RB [ \-r\~\c
+.IR cnumeric-expression ]
+.RB [ \-r\~\c
+.IB register =\c
+.IR numeric-expression ]
+.RB [ \-T\~\c
+.IR output-device ]
+.RB [ \-w\~\c
+.IR warning-category ]
+.RB [ \-W\~\c
+.IR warning-category ]
+.RI [ file\~ .\|.\|.]
+.YS
+.
+.
+.SY @g@troff
+.B \-\-help
+.YS
+.
+.
+.SY @g@troff
+.B \-v
+.
+.SY @g@troff
+.B \-\-version
+.YS
+.
+.
+.\" ====================================================================
+.SH Description
+.\" ====================================================================
+.
+GNU
+.I troff \" GNU
+transforms
+.MR groff @MAN7EXT@
+language input into the device-independent output format described in
+.MR groff_out @MAN5EXT@ ;
+.I @g@troff
+is thus the heart of the GNU
+.I roff
+document formatting system.
+.
+If no
+.I file
+operands are given on the command line,
+or if
+.I file
+is
+.RB \[lq] \- \[rq],
+the standard input stream is read.
+.
+.
+.P
+GNU
+.I troff \" GNU
+is functionally compatible with the AT&T
+.I troff \" AT&T
+typesetter and features numerous extensions.
+.
+Many people prefer to use the
+.MR groff @MAN1EXT@
+command,
+a front end which also runs preprocessors and output drivers in the
+appropriate order and with appropriate options.
+.
+.
+.\" ====================================================================
+.SH Options
+.\" ====================================================================
+.
+.B \-h
+and
+.B \-\-help
+display a usage message,
+while
+.B \-v
+and
+.B \-\-version
+show version information;
+all exit afterward.
+.
+.
+.TP
+.B \-a
+Generate a plain text approximation of the typeset output.
+.
+The read-only register
+.B .A
+is set to\~1.
+.
+This option produces a sort of abstract preview of the formatted output.
+.
+.
+.RS
+.IP \[bu] 2n
+Page breaks are marked by a phrase in angle brackets;
+for example,
+\[lq]<beginning of page>\[rq].
+.
+.
+.IP \[bu]
+Lines are broken where they would be in the formatted output.
+.
+.
+.IP \[bu]
+A horizontal motion of any size is represented as one space.
+.
+Adjacent horizontal motions are not combined.
+.
+Inter-sentence space nodes
+(those arising from the second argument to the
+.B .ss
+request)
+are not represented.
+.
+.
+.IP \[bu]
+Vertical motions are not represented.
+.
+.
+.IP \[bu]
+Special characters are rendered in angle brackets;
+for example,
+the default soft hyphen character appears as
+\[lq]<hy>\[rq].
+.RE
+.
+.
+.IP
+The above description should not be considered a specification;
+the details of
+.B \-a
+output are subject to change.
+.
+.
+.TP
+.B \-b
+Write a backtrace reporting the state of
+.IR @g@troff 's
+input parser to the standard error stream with each diagnostic message.
+.
+The line numbers given in the backtrace might not always be correct,
+because
+.IR @g@troff 's
+idea of line numbers can be confused by requests that append to
+.\" strings or (??? strings never contain newlines)
+macros.
+.
+.
+.TP
+.B \-c
+Start with color output disabled.
+.
+.
+.TP
+.B \-C
+Enable AT&T
+.I troff \" AT&T
+compatibility mode;
+implies
+.BR \-c .
+.
+See
+.MR groff_diff @MAN7EXT@ .
+.
+.
+.TP
+.BI \-d\~ ctext
+.TQ
+.BI \-d\~ string = text
+Define
+.I roff
+.RI string\~ c
+or
+.I string
+as
+.I text.
+.
+.IR c \~must
+be one character;
+.I string
+can be of arbitrary length.
+.
+Such string assignments happen before any macro file is loaded,
+including the startup file.
+.
+Due to
+.MR getopt_long 3
+limitations,
+.IR c\~ cannot
+be,
+and
+.I string
+cannot contain,
+an equals sign,
+even though that is a valid character in a
+.I roff
+identifier.
+.
+.
+.TP
+.B \-E
+Inhibit
+.I @g@troff
+error messages;
+implies
+.BR \-Ww .
+.
+This option does
+.I not
+suppress messages sent to the standard error stream by documents or
+macro packages using
+.B tm
+or related requests.
+.
+.
+.TP
+.BI \-f\~ fam
+Use
+.I fam
+as the default font family.
+.
+.
+.TP
+.BI \-F\~ dir
+Search in directory
+.I dir
+for the selected output device's directory of device and font
+description files.
+.
+See the description of
+.I GROFF_FONT_PATH
+in section \[lq]Environment\[rq] below for the default search locations
+and ordering.
+.
+.
+.TP
+.B \-i
+Read the standard input stream after all named input files have been
+processed.
+.
+.
+.TP
+.BI \-I\~ dir
+Search the directory
+.I dir
+for files
+(those named on the command line;
+in
+.BR psbb ,
+.BR so ,
+and
+.B soquiet
+requests;
+and in
+.RB \[lq] "\[rs]X\[aq]ps: import\[aq]" \[rq],
+.RB \[lq] "\[rs]X\[aq]ps: file\[aq]" \[rq],
+and
+.RB \[lq] "\[rs]X\[aq]pdf: pdfpic\[aq]" \[rq]
+device control escape sequences).
+.
+.B \-I
+may be specified more than once;
+each
+.I dir
+is searched in the given order.
+.
+To search the current working directory before others,
+add
+.RB \[lq] "\-I .\&" \[rq]
+at the desired place;
+it is otherwise searched last.
+.
+.B \-I
+works similarly to,
+and is named for,
+the \[lq]include\[rq]
+option of Unix C compilers.
+.
+.
+.TP
+.BI \-m\~ name
+Process the file
+.RI name .tmac
+prior to any input files.
+.
+If not found,
+.IR tmac. name
+is attempted.
+.
+.I name
+(in both arrangements)
+is presumed to be a macro file;
+see the description of
+.I GROFF_TMAC_PATH
+in section \[lq]Environment\[rq] below for the default search locations
+and ordering.
+.
+.
+.TP
+.BI \-M\~ dir
+Search directory
+.I dir
+for macro files.
+.
+See the description of
+.I GROFF_TMAC_PATH
+in section \[lq]Environment\[rq] below for the default search locations
+and ordering.
+.
+.
+.TP
+.BI \-n\~ num
+Begin numbering pages at
+.I num.
+.
+The default
+.RB is\~ 1 .
+.
+.
+.TP
+.BI \-o\~ list
+Output only pages in
+.I list,
+which is a comma-separated list of inclusive page ranges;
+.I n
+means page
+.I n,
+.IB m \- n
+means every page
+.RI between\~ m
+.RI and\~ n ,
+.BI \- n
+means every page up
+.RI to\~ n ,
+and
+.IB n \-
+means every page from
+.IR n \~on.
+.
+.I @g@troff
+stops processing and exits after formatting the last page enumerated in
+.I list.
+.
+.
+.TP
+.BI \-r\~ cnumeric-expression
+.TQ
+.BI \-r\~ register = numeric-expression
+Define
+.I roff
+.RI register\~ c
+or
+.I register
+as
+.I numeric-expression.
+.
+.IR c \~must
+be a one-character name;
+.I register
+can be of arbitrary length.
+.
+Such register assignments happen before any macro file is loaded,
+including the startup file.
+.
+Due to
+.MR getopt_long 3
+limitations,
+.IR c\~ cannot
+be,
+and
+.I register
+cannot contain,
+an equals sign,
+even though that is a valid character in a
+.I roff
+identifier.
+.
+.
+.TP
+.B \-R
+Don't load
+.I troffrc
+and
+.IR troffrc\-end .
+.
+.
+.TP
+.BI \-T\~ dev
+Prepare output for device
+.I dev.
+.
+The default is
+.BR @DEVICE@ ;
+see
+.MR groff @MAN1EXT@ .
+.
+.
+.TP
+.B \-U
+Operate in
+.I unsafe mode,
+enabling the
+.BR open ,
+.BR opena ,
+.BR pi ,
+.BR pso ,
+and
+.B sy
+requests,
+which are disabled by default because they allow an untrusted input
+document to write to arbitrary file names and run arbitrary commands.
+.
+This option also adds the current directory to the macro package search
+path;
+see the
+.B \-m
+and
+.B \-M
+options above.
+.
+.
+.TP
+.BI \-w\~ name
+.TQ
+.BI \-W\~ name
+Enable
+.RB ( \-w )
+or inhibit
+.RB ( \-W )
+warnings in category
+.I name.
+.
+See section \[lq]Warnings\[rq] below.
+.
+.
+.TP
+.B \-z
+Suppress formatted output.
+.
+.
+.\" ====================================================================
+.SH Warnings
+.\" ====================================================================
+.
+.\" BEGIN Keep parallel with groff.texi node "Warnings".
+.\" Caveat: the Texinfo manual sorts them by number, not name.
+Warning diagnostics emitted by
+.I @g@troff
+are divided into named,
+numbered categories.
+.
+The name associated with each warning category is used by the
+.B \-w
+and
+.B \-W
+options.
+.
+Each category is also assigned a power of two;
+the sum of enabled category codes is used by the
+.B warn
+request and the
+.B .warn
+register.
+.
+Warnings of each category are produced under the following
+circumstances.
+.
+.
+.P
+.TS
+tab(@), center, box;
+c c c | c c c
+r rI lB | r rI lB.
+Bit@Code@Category@Bit@Code@Category
+_
+0@1@char@10@1024@reg
+1@2@number@11@2048@tab
+2@4@break@12@4096@right\-brace
+3@8@delim@13@8192@missing
+4@16@el@14@16384@input
+5@32@scale@15@32768@escape
+6@64@range@16@65536@space
+7@128@syntax@17@131072@font
+8@256@di@18@262144@ig
+9@512@mac@19@524288@color
+@@@20@1048576@file
+.TE
+.
+.
+.P
+.nr x \w'\fBright\-brace'+1n+\w'00000'u
+.ta \nxuR
+.
+.
+.TP \nxu+3n
+.BR break "\t4"
+A filled output line could not be broken such that its length was less
+than the output line length
+.BR \[rs]n[.l] .
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR char "\t1"
+No mounted font defines a glyph for the requested character.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR color "\t524288"
+An undefined color name was selected,
+an attempt was made to define a color using an unrecognized color space,
+an invalid component in a color definition was encountered,
+or an attempt was made to redefine a default color.
+.
+.
+.TP
+.BR delim "\t8"
+The closing delimiter in an escape sequence was missing or mismatched.
+.
+.
+.TP
+.BR di "\t256"
+A
+.BR di ,
+.BR da ,
+.BR box ,
+or
+.B boxa
+request was invoked without an argument when there was no current
+diversion.
+.
+.
+.TP
+.BR el "\t16"
+The
+.B el
+request was encountered with no prior corresponding
+.B ie
+request.
+.
+.
+.TP
+.BR escape "\t32768"
+An unsupported escape sequence was encountered.
+.
+.
+.TP
+.BR file "\t1048576"
+An attempt was made to load a file that does not exist.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR font "\t131072"
+A non-existent font was selected,
+or the selection was ignored because a font selection escape sequence
+was used after the output line continuation escape sequence on an input
+line.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR ig "\t262144"
+An invalid escape sequence occurred in input ignored using the
+.B ig
+request.
+.
+This warning category diagnoses a condition that is an error when it
+occurs in non-ignored input.
+.
+.
+.TP
+.BR input "\t16384"
+An invalid character occurred on the input stream.
+.
+.
+.TP
+.BR mac "\t512"
+An undefined string,
+macro,
+or diversion was used.
+.
+When such an object is dereferenced,
+an empty one of that name is automatically created.
+.
+So,
+unless it is later deleted,
+at most one warning is given for each.
+.
+.
+.IP
+This warning is also emitted upon an attempt to move an unplanted trap
+macro.
+.
+In such cases,
+the unplanted macro is
+.I not
+dereferenced,
+so it is not created if it does not exist.
+.
+.
+.TP
+.BR missing "\t8192"
+A request was invoked with a mandatory argument absent.
+.
+.
+.TP
+.BR number "\t2"
+An invalid numeric expression was encountered.
+.
+This category is enabled by default.
+.
+.
+.TP
+.BR range "\t64"
+A numeric expression was out of range for its context.
+.
+.
+.TP
+.BR reg "\t1024"
+An undefined register was used.
+.
+When an undefined register is dereferenced,
+it is automatically defined with a value of\~0.
+.
+So,
+unless it is later deleted,
+at most one warning is given for each.
+.
+.
+.TP
+.BR right\-brace "\t4096"
+A right brace escape sequence
+.B \[rs]}
+was encountered where a number was expected.
+.
+.
+.TP
+.BR scale "\t32"
+A scaling unit inappropriate to its context was used in a numeric
+expression.
+.
+.
+.TP
+.BR space "\t65536"
+A space was missing between a request or macro and its argument.
+.
+This warning is produced when an undefined name longer than two
+characters is encountered and the first two characters of the name
+constitute a defined name.
+.
+No request is invoked,
+no macro called,
+and an empty macro is not defined.
+.
+This category is enabled by default.
+.
+It never occurs in compatibility mode.
+.
+.
+.TP
+.BR syntax "\t128"
+A self-contradictory hyphenation mode was requested;
+an empty or incomplete numeric expression was encountered;
+an operand to a numeric operator was missing;
+an attempt was made to define a recursive,
+empty,
+or nonsensical character class;
+or a
+.I groff
+extension conditional expression operator was used while in
+compatibility mode.
+.
+.
+.TP
+.BR tab "\t2048"
+A tab character was encountered where a number was expected,
+or appeared in an unquoted macro argument.
+.
+.
+.P
+Two warning names group other warning categories for convenience.
+.
+.
+.TP
+.B all
+All warning categories except
+.BR di ,
+.BR mac ,
+and
+.BR reg .
+.
+This shorthand is intended to produce all warnings that are useful with
+macro packages and documents written for AT&T
+.I troff \" AT&T
+and its descendants,
+which have less fastidious diagnostics than GNU
+.IR troff . \" GNU
+.
+.
+.TP
+.B w
+All warning categories.
+.
+Authors of documents and macro packages targeting
+.I groff
+are encouraged to use this setting.
+.\" END Keep parallel with groff.texi node "Warnings".
+.
+.
+.\" ====================================================================
+.SH Environment
+.\" ====================================================================
+.
+.I GROFF_FONT_PATH
+and
+.I GROFF_TMAC_PATH
+each accept a search path of directories;
+that is,
+a list of directory names separated by the system's path component
+separator character.
+.
+On Unix systems,
+this character is a colon (:);
+on Windows systems,
+it is a semicolon (;).
+.
+.
+.TP
+.I GROFF_FONT_PATH
+A list of directories in which to seek the selected output device's
+directory of device and font description files.
+.
+.I @g@troff
+will scan directories given as arguments to any specified
+.B \-F
+options before these,
+then in a site-specific directory
+.RI ( @LOCALFONTDIR@ ),
+a standard location
+.RI ( @FONTDIR@ ),
+and a compatibility directory
+.RI ( @LEGACYFONTDIR@ )
+after them.
+.
+.
+.TP
+.I GROFF_TMAC_PATH
+A list of directories in which to search for macro files.
+.
+.I @g@troff
+will scan directories given as arguments to any specified
+.B \-M
+options before these,
+then the current directory
+(only if in unsafe mode),
+the user's home directory,
+.if !'@COMPATIBILITY_WRAPPERS@'no' \{\
+a platform-specific directory
+.RI ( @SYSTEMMACRODIR@ ),
+.\}
+a site-specific directory
+.RI ( @LOCALMACRODIR@ ),
+and a standard location
+.RI ( @MACRODIR@ )
+after them.
+.
+.
+.TP
+.I GROFF_TYPESETTER
+Set the default output device.
+.
+If empty or not set,
+.B @DEVICE@
+is used.
+.
+The
+.B \-T
+option overrides
+.IR \%GROFF_TYPESETTER .
+.
+.
+.TP
+.I SOURCE_DATE_EPOCH
+A timestamp
+(expressed as seconds since the Unix epoch)
+to use as the output creation timestamp in place of the current time.
+.
+The time is converted to human-readable form using
+.MR localtime 3
+when the formatter starts up and stored in registers usable by documents
+and macro packages.
+.
+.
+.TP
+.I TZ
+The timezone to use when converting the current time
+(or value of
+.IR SOURCE_DATE_EPOCH )
+to human-readable form;
+see
+.MR tzset 3 .
+.
+.
+.\" ====================================================================
+.SH Files
+.\" ====================================================================
+.
+.TP
+.I @MACRODIR@/\:\%troffrc
+is an initialization macro file loaded before any macro packages
+specified with
+.B \-m
+options.
+.
+.
+.TP
+.I @MACRODIR@/\:\%troffrc\-end
+is an initialization macro file loaded after all macro packages
+specified with
+.B \-m
+options.
+.
+.
+.TP
+.IR @MACRODIR@/\: name \:.tmac
+are macro files distributed with
+.IR groff .
+.
+.
+.TP
+.IR @FONTDIR@/\:\%dev name /\:DESC
+describes the output device
+.IR name .
+.
+.
+.TP
+.IR @FONTDIR@/\:\%dev name / F
+describes the font
+.I F
+of device
+.I name.
+.
+.
+.P
+.I troffrc
+and
+.I troffrc\-end
+are sought neither in the current nor the home directory by default for
+security reasons,
+even if the
+.B \-U
+option is specified.
+.
+Use the
+.B \-M
+command-line option or the
+.I GROFF_TMAC_PATH
+environment variable to add these directories to the search path if
+necessary.
+.
+.
+.\" ====================================================================
+.SH Authors
+.\" ====================================================================
+.
+The GNU version of
+.I troff \" generic
+was originally written by James Clark;
+he also wrote the original version of this document,
+which was updated by
+.MT wl@\:gnu\:.org
+Werner Lemberg
+.ME ,
+.MT groff\-bernd\:.warken\-72@\:web\:.de
+Bernd Warken
+.ME ,
+and
+.MT g.branden\:.robinson@\:gmail\:.com
+G.\& Branden Robinson
+.ME .
+.
+.
+.\" ====================================================================
+.SH "See also"
+.\" ====================================================================
+.
+.IR "Groff: The GNU Implementation of troff" ,
+by Trent A.\& Fisher and Werner Lemberg,
+is the primary
+.I groff
+manual.
+.
+You can browse it interactively with \[lq]info groff\[rq].
+.
+.
+.TP
+.MR groff @MAN1EXT@
+offers an overview of the GNU
+.I roff
+system
+and describes its front end executable.
+.
+.
+.TP
+.MR groff @MAN7EXT@
+details the
+.I groff
+language,
+including a short but complete reference of all predefined requests,
+registers,
+and escape sequences.
+.
+.
+.TP
+.MR groff_char @MAN7EXT@
+explains the syntax of
+.I groff
+special character escape sequences,
+and lists all special characters predefined by the language.
+.
+.
+.TP
+.MR groff_diff @MAN7EXT@
+enumerates the differences between
+AT&T device-independent
+.I troff \" AT&T
+and
+.IR groff .
+.
+.
+.TP
+.MR groff_font @MAN5EXT@
+covers the format of
+.I groff
+device and font description files.
+.
+.
+.TP
+.MR groff_out @MAN5EXT@
+describes the format of
+.IR @g@troff 's
+output.
+.
+.
+.TP
+.MR groff_tmac @MAN5EXT@
+includes information about macro files that ship with
+.IR groff .
+.
+.
+.TP
+.MR roff @MAN7EXT@
+supplies background on
+.I roff
+systems in general,
+including pointers to further related documentation.
+.
+.
+.\" Restore compatibility mode (for, e.g., Solaris 10/11).
+.cp \n[*groff_troff_1_man_C]
+.do rr *groff_troff_1_man_C
+.
+.
+.\" Local Variables:
+.\" fill-column: 72
+.\" mode: nroff
+.\" End:
+.\" vim: set filetype=groff textwidth=72:
diff --git a/src/roff/troff/troff.am b/src/roff/troff/troff.am
new file mode 100644
index 0000000..42ac7fa
--- /dev/null
+++ b/src/roff/troff/troff.am
@@ -0,0 +1,66 @@
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# 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 3 of the License, or (at your
+# option) any later version.
+#
+# groff is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+prefixexecbin_PROGRAMS += troff
+PREFIXMAN1 += src/roff/troff/troff.1
+EXTRA_DIST += \
+ src/roff/troff/column.cpp \
+ src/roff/troff/troff.1.man \
+ src/roff/troff/TODO
+troff_LDADD = libgroff.a lib/libgnu.a $(LIBM)
+troff_SOURCES = \
+ src/roff/troff/dictionary.cpp \
+ src/roff/troff/div.cpp \
+ src/roff/troff/env.cpp \
+ src/roff/troff/input.cpp \
+ src/roff/troff/mtsm.cpp \
+ src/roff/troff/node.cpp \
+ src/roff/troff/number.cpp \
+ src/roff/troff/reg.cpp \
+ src/roff/troff/env.h \
+ src/roff/troff/node.h \
+ src/roff/troff/troff.h \
+ src/roff/troff/div.h \
+ src/roff/troff/reg.h \
+ src/roff/troff/dictionary.h \
+ src/roff/troff/input.h \
+ src/roff/troff/mtsm.h \
+ src/roff/troff/token.h \
+ src/roff/troff/charinfo.h \
+ src/roff/troff/request.h \
+ src/roff/troff/hvunits.h
+
+nodist_troff_SOURCES = src/roff/troff/majorminor.cpp
+
+src/roff/troff/input.$(OBJEXT): defs.h
+CLEANFILES += src/roff/troff/majorminor.cpp
+
+src/roff/troff/majorminor.cpp: $(top_srcdir)/.version
+ $(AM_V_at)printf 'const char *major_version = "%s";\n' \
+ $(MAJOR_VERSION) > $@.tmp
+ $(AM_V_at)printf 'const char *minor_version = "%s";\n' \
+ $(MINOR_VERSION) >> $@.tmp
+ $(AM_V_at)printf 'const char *revision = "%s";\n' \
+ $(REVISION) >> $@.tmp
+ $(AM_V_GEN)mv $@.tmp $@
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/roff/troff/troff.h b/src/roff/troff/troff.h
new file mode 100644
index 0000000..8cef744
--- /dev/null
+++ b/src/roff/troff/troff.h
@@ -0,0 +1,97 @@
+// -*- C++ -*-
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+ Written by James Clark (jjc@jclark.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 3 of the License, or
+(at your option) any later version.
+
+groff is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include "lib.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "color.h"
+#include "device.h"
+#include "searchpath.h"
+
+typedef int units;
+
+extern units scale(units n, units x, units y); // scale n by x/y
+
+extern units units_per_inch;
+
+extern int ascii_output_flag;
+extern int suppress_output_flag;
+extern int color_flag;
+extern int is_html;
+
+extern bool device_has_tcommand;
+extern int vresolution;
+extern int hresolution;
+extern int sizescale;
+
+extern search_path *mac_path;
+
+#include "cset.h"
+#include "cmap.h"
+#include "errarg.h"
+#include "error.h"
+
+enum warning_type {
+ WARN_CHAR = 01,
+ WARN_NUMBER = 02,
+ WARN_BREAK = 04,
+ WARN_DELIM = 010,
+ WARN_EL = 020,
+ WARN_SCALE = 040,
+ WARN_RANGE = 0100,
+ WARN_SYNTAX = 0200,
+ WARN_DI = 0400,
+ WARN_MAC = 01000,
+ WARN_REG = 02000,
+ WARN_TAB = 04000,
+ WARN_RIGHT_BRACE = 010000,
+ WARN_MISSING = 020000,
+ WARN_INPUT = 040000,
+ WARN_ESCAPE = 0100000,
+ WARN_SPACE = 0200000,
+ WARN_FONT = 0400000,
+ WARN_IG = 01000000,
+ WARN_COLOR = 02000000,
+ WARN_FILE = 04000000
+ // change WARN_TOTAL if you add more warning types
+};
+
+const int WARN_TOTAL = 07777777;
+
+int warning(warning_type, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+int output_warning(warning_type, const char *,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg,
+ const errarg & = empty_errarg);
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: