diff options
Diffstat (limited to 'src/roff')
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>á</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>á</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, + ¤t_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 = ⊤ *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 = ⊤ *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 ¯o::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, ¯os); + 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, ®ister_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 = ¯o_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(¬_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: |