summaryrefslogtreecommitdiffstats
path: root/src/libs/libdriver
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/libdriver')
-rw-r--r--src/libs/libdriver/input.cpp1841
-rw-r--r--src/libs/libdriver/libdriver.am31
-rw-r--r--src/libs/libdriver/printer.cpp268
3 files changed, 2140 insertions, 0 deletions
diff --git a/src/libs/libdriver/input.cpp b/src/libs/libdriver/input.cpp
new file mode 100644
index 0000000..821b526
--- /dev/null
+++ b/src/libs/libdriver/input.cpp
@@ -0,0 +1,1841 @@
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+
+ Written by James Clark (jjc@jclark.com)
+ Major rewrite 2001 by Bernd Warken <groff-bernd.warken-72@web.de>
+
+ This file is part of groff, the GNU roff text processing system.
+
+ 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/>.
+*/
+
+/* Description
+
+ This file implements the parser for the intermediate groff output,
+ see groff_out(5), and does the printout for the given device.
+
+ All parsed information is processed within the function do_file().
+ A device postprocessor just needs to fill in the methods for the class
+ 'printer' (or rather a derived class) without having to worry about
+ the syntax of the intermediate output format. Consequently, the
+ programming of groff postprocessors is similar to the development of
+ device drivers.
+
+ The prototyping for this file is done in driver.h (and error.h).
+*/
+
+/* Changes of the 2001 rewrite of this file.
+
+ The interface to the outside and the handling of the global
+ variables was not changed, but internally many necessary changes
+ were performed.
+
+ The main aim for this rewrite is to provide a first step towards
+ making groff fully compatible with classical troff without pain.
+
+ Bugs fixed
+ - Unknown subcommands of 'D' and 'x' are now ignored like in the
+ classical case, but a warning is issued. This was also
+ implemented for the other commands.
+ - A warning is emitted if 'x stop' is missing.
+ - 'DC' and 'DE' commands didn't position to the right end after
+ drawing (now they do), see discussion below.
+ - So far, 'x stop' was ignored. Now it terminates the processing
+ of the current intermediate output file like the classical troff.
+ - The command 'c' didn't check correctly on white-space.
+ - The environment stack wasn't suitable for the color extensions
+ (replaced by a class).
+ - The old groff parser could only handle a prologue with the first
+ 3 lines having a fixed structure, while classical troff specified
+ the sequence of the first 3 commands without further
+ restrictions. Now the parser is smart about additional
+ white space, comments, and empty lines in the prologue.
+ - The old parser allowed space characters only as syntactical
+ separators, while classical troff had tab characters as well.
+ Now any sequence of tabs and/or spaces is a syntactical
+ separator between commands and/or arguments.
+ - Range checks for numbers implemented.
+
+ New and improved features
+ - The color commands 'm' and 'DF' are added.
+ - The old color command 'Df' is now converted and delegated to 'DFg'.
+ - The command 'F' is implemented as 'use intended file name'. It
+ checks whether its argument agrees with the file name used so far,
+ otherwise a warning is issued. Then the new name is remembered
+ and used for the following error messages.
+ - For the positioning after drawing commands, an alternative, easier
+ scheme is provided, but not yet activated; it can be chosen by
+ undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
+ It extends the rule of the classical troff output language in a
+ logical way instead of the rather strange actual positioning.
+ For details, see the discussion below.
+ - For the 'D' commands that only set the environment, the calling of
+ pr->send_draw() was removed because this doesn't make sense for
+ the 'DF' commands; the (changed) environment is sent with the
+ next command anyway.
+ - Error handling was clearly separated into warnings and fatal.
+ - The error behavior on additional arguments for 'D' and 'x'
+ commands with a fixed number of arguments was changed from being
+ ignored (former groff) to issue a warning and ignore (now), see
+ skip_line_x(). No fatal was chosen because both string and
+ integer arguments can occur.
+ - The gtroff program issues a trailing dummy integer argument for
+ some drawing commands with an odd number of arguments to make the
+ number of arguments even, e.g. the DC and Dt commands; this is
+ honored now.
+ - All D commands with a variable number of args expect an even
+ number of trailing integer arguments, so fatal on error was
+ implemented.
+ - Disable environment stack and the commands '{' and '}' by making
+ them conditional on macro USE_ENV_STACK; actually, this is
+ undefined by default. There isn't any known application for these
+ features.
+
+ Cosmetics
+ - Nested 'switch' commands are avoided by using more functions.
+ Dangerous 'fall-through's avoided.
+ - Commands and functions are sorted alphabetically (where possible).
+ - Dynamic arrays/buffers are now implemented as container classes.
+ - Some functions had an ugly return structure; this has been
+ streamlined by using classes.
+ - Use standard C math functions for number handling, so getting rid
+ of differences to '0'.
+ - The macro 'IntArg' has been created for an easier transition
+ to guaranteed 32 bits integers ('int' is enough for GNU, while
+ ANSI only guarantees 'long int' to have a length of 32 bits).
+ - The many usages of type 'int' are differentiated by using 'Char',
+ 'bool', and 'IntArg' where appropriate.
+ - To ease the calls of the local utility functions, the parser
+ variables 'current_file', 'npages', and 'current_env'
+ (formerly env) were made global to the file (formerly they were
+ local to the do_file() function)
+ - Various comments were added.
+
+ TODO
+ - Get rid of the stupid drawing positioning.
+ - Can the 'Dt' command be completely handled by setting environment
+ within do_file() instead of sending to pr?
+ - Integer arguments must be >= 32 bits, use conditional #define.
+ - Add scaling facility for classical device independence and
+ non-groff devices. Classical troff output had a quasi device
+ independence by scaling the intermediate output to the resolution
+ of the postprocessor device if different from the one specified
+ with 'x T', groff have not. So implement full quasi device
+ independence, including the mapping of the strange classical
+ devices to the postprocessor device (seems to be reasonably
+ easy).
+ - The external, global pointer variables are not optimally handled.
+ - The global variables 'current_filename',
+ 'current_source_filename', and 'current_lineno' are only used for
+ error reporting. So implement a static class 'Error'
+ ('::' calls).
+ - The global 'device' is the name used during the formatting
+ process; there should be a new variable for the device name used
+ during the postprocessing.
+ - Implement the B-spline drawing 'D~' for all graphical devices.
+ - Make 'environment' a class with an overflow check for its members
+ and a delete method to get rid of delete_current_env().
+ - Implement the 'EnvStack' to use 'new' instead of 'malloc'.
+ - The class definitions of this document could go into a new file.
+ - The comments in this section should go to a 'Changelog' or some
+ 'README' file in this directory.
+*/
+
+/*
+ Discussion of the positioning by drawing commands
+
+ There was some confusion about the positioning of the graphical
+ pointer at the printout after having executed a 'D' command.
+ The classical troff manual of Ossanna & Kernighan specified,
+
+ 'The position after a graphical object has been drawn is
+ at its end; for circles and ellipses, the "end" is at the
+ right side.'
+
+ From this, it follows that
+ - all open figures (args, splines, and lines) should position at their
+ final point.
+ - all circles and ellipses should position at their right-most point
+ (as if 2 halves had been drawn).
+ - all closed figures apart from circles and ellipses shouldn't change
+ the position because they return to their origin.
+ - all setting commands should not change position because they do not
+ draw any graphical object.
+
+ In the case of the open figures, this means that the horizontal
+ displacement is the sum of all odd arguments and the vertical offset
+ the sum of all even arguments, called the alternate arguments sum
+ displacement in the following.
+
+ Unfortunately, groff did not implement this simple rule. The former
+ documentation in groff_out(5) differed from the source code, and
+ neither of them is compatible with the classical rule.
+
+ The former groff_out(5) specified to use the alternative arguments
+ sum displacement for calculating the drawing positioning of
+ non-classical commands, including the 'Dt' command (setting-only)
+ and closed polygons. Applying this to the new groff color commands
+ will lead to disaster. For their arguments can take large values (>
+ 65000). On low resolution devices, the displacement of such large
+ values will corrupt the display or kill the printer. So the
+ nonsense specification has come to a natural end anyway.
+
+ The groff source code, however, had no positioning for the
+ setting-only commands (esp. 'Dt'), the right-end positioning for
+ outlined circles and ellipses, and the alternative argument sum
+ displacement for all other commands (including filled circles and
+ ellipses).
+
+ The reason why no one seems to have suffered from this mayhem so
+ far is that the graphical objects are usually generated by
+ preprocessors like pic that do not depend on the automatic
+ positioning. When using the low level '\D' escape sequences or 'D'
+ output commands, the strange positionings can be circumvented by
+ absolute positionings or by tricks like '\Z'.
+
+ So doing an exorcism on the strange, incompatible displacements might
+ not harm any existing documents, but will make the usage of the
+ graphical escape sequences and commands natural.
+
+ That's why the rewrite of this file returned to the reasonable,
+ classical specification with its clear end-of-drawing rule that is
+ suitable for all cases. But a macro STUPID_DRAWING_POSITIONING is
+ provided for testing the funny former behavior.
+
+ The new rule implies the following behavior.
+ - Setting commands ('Dt', 'Df', 'DF') and polygons ('Dp' and 'DP')
+ do not change position now.
+ - Filled circles and ellipses ('DC' and 'DE') position at their
+ most right point (outlined ones 'Dc' and 'De' did this anyway).
+ - As before, all open graphical objects position to their final
+ drawing point (alternate sum of the command arguments).
+
+*/
+
+#ifndef STUPID_DRAWING_POSITIONING
+// uncomment next line if all non-classical D commands shall position
+// to the strange alternate sum of args displacement
+#define STUPID_DRAWING_POSITIONING
+#endif
+
+// Decide whether the commands '{' and '}' for different environments
+// should be used.
+#undef USE_ENV_STACK
+
+#include "driver.h"
+#include "device.h"
+
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <math.h>
+
+
+/**********************************************************************
+ local types
+ **********************************************************************/
+
+// integer type used in the fields of struct environment (see printer.h)
+typedef int EnvInt;
+
+// integer arguments of groff_out commands, must be >= 32 bits
+typedef int IntArg;
+
+// color components of groff_out color commands, must be >= 32 bits
+typedef unsigned int ColorArg;
+
+// Array for IntArg values.
+class IntArray {
+ size_t num_allocated;
+ size_t num_stored;
+ IntArg *data;
+public:
+ IntArray(void);
+ IntArray(const size_t);
+ ~IntArray(void);
+ IntArg operator[](const size_t i) const
+ {
+ if (i >= num_stored)
+ fatal("index out of range");
+ return (IntArg) data[i];
+ }
+ void append(IntArg);
+ IntArg *get_data(void) const { return (IntArg *)data; }
+ size_t len(void) const { return num_stored; }
+};
+
+// Characters read from the input queue.
+class Char {
+ int data;
+public:
+ Char(void) : data('\0') {}
+ Char(const int c) : data(c) {}
+ bool operator==(char c) const { return (data == c) ? true : false; }
+ bool operator==(int c) const { return (data == c) ? true : false; }
+ bool operator==(const Char c) const
+ { return (data == c.data) ? true : false; }
+ bool operator!=(char c) const { return !(*this == c); }
+ bool operator!=(int c) const { return !(*this == c); }
+ bool operator!=(const Char c) const { return !(*this == c); }
+ operator int() const { return (int) data; }
+ operator unsigned char() const { return (unsigned char) data; }
+ operator char() const { return (char) data; }
+};
+
+// Buffer for string arguments (Char, not char).
+class StringBuf {
+ size_t num_allocated;
+ size_t num_stored;
+ Char *data; // not terminated by '\0'
+public:
+ StringBuf(void); // allocate without storing
+ ~StringBuf(void);
+ void append(const Char); // append character to 'data'
+ char *make_string(void); // return new copy of 'data' with '\0'
+ bool is_empty(void) { // true if none stored
+ return (num_stored > 0) ? false : true;
+ }
+ void reset(void); // set 'num_stored' to 0
+};
+
+#ifdef USE_ENV_STACK
+class EnvStack {
+ environment **data;
+ size_t num_allocated;
+ size_t num_stored;
+public:
+ EnvStack(void);
+ ~EnvStack(void);
+ environment *pop(void);
+ void push(environment *e);
+};
+#endif // USE_ENV_STACK
+
+
+/**********************************************************************
+ external variables
+ **********************************************************************/
+
+// exported as extern by error.h (called from driver.h)
+// needed for error messages (see ../libgroff/error.cpp)
+const char *current_filename = 0; // printable name of the current file
+ // printable name of current source file
+const char *current_source_filename = 0;
+int current_lineno = 0; // current line number of printout
+
+// exported as extern by device.h;
+const char *device = 0; // cancel former init with literal
+
+printer *pr;
+
+// Note:
+//
+// We rely on an implementation of the 'new' operator which aborts
+// gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
+
+
+/**********************************************************************
+ static local variables
+ **********************************************************************/
+
+FILE *current_file = 0; // current input stream for parser
+
+// npages: number of pages processed so far (including current page),
+// _not_ the page number in the printout (can be set with 'p').
+int npages = 0;
+
+const ColorArg
+COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
+
+const IntArg
+INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
+
+// parser environment, created and deleted by each run of do_file()
+environment *current_env = 0;
+
+#ifdef USE_ENV_STACK
+const size_t
+envp_size = sizeof(environment *);
+#endif // USE_ENV_STACK
+
+
+/**********************************************************************
+ function declarations
+ **********************************************************************/
+
+// utility functions
+ColorArg color_from_Df_command(IntArg);
+ // transform old color into new
+void delete_current_env(void); // delete global var current_env
+void fatal_command(char); // abort for invalid command
+inline Char get_char(void); // read next character from input stream
+ColorArg get_color_arg(void); // read in argument for new color cmds
+IntArray *get_D_fixed_args(const size_t);
+ // read in fixed number of integer
+ // arguments
+IntArray *get_D_fixed_args_odd_dummy(const size_t);
+ // read in a fixed number of integer
+ // arguments plus optional dummy
+IntArray *get_D_variable_args(void);
+ // variable, even number of int args
+char *get_extended_arg(void); // argument for 'x X' (several lines)
+IntArg get_integer_arg(void); // read in next integer argument
+IntArray *get_possibly_integer_args();
+ // 0 or more integer arguments
+char *get_string_arg(void); // read in next string arg, ended by WS
+inline bool is_space_or_tab(const Char);
+ // test on space/tab char
+Char next_arg_begin(void); // skip white space on current line
+Char next_command(void); // go to next command, evt. diff. line
+inline bool odd(const int); // test if integer is odd
+void position_to_end_of_args(const IntArray * const);
+ // positioning after drawing
+void remember_filename(const char *);
+ // set global current_filename
+void remember_source_filename(const char *);
+ // set global current_source_filename
+void send_draw(const Char, const IntArray * const);
+ // call pr->draw
+void skip_line(void); // unconditionally skip to next line
+bool skip_line_checked(void); // skip line, false if args are left
+void skip_line_fatal(void); // skip line, fatal if args are left
+void skip_line_warn(void); // skip line, warn if args are left
+void skip_line_D(void); // skip line in D commands
+void skip_line_x(void); // skip line in x commands
+void skip_to_end_of_line(void); // skip to the end of the current line
+inline void unget_char(const Char);
+ // restore character onto input
+
+// parser subcommands
+void parse_color_command(color *);
+ // color sub(sub)commands m and DF
+void parse_D_command(void); // graphical subcommands
+bool parse_x_command(void); // device controller subcommands
+
+
+/**********************************************************************
+ class methods
+ **********************************************************************/
+
+#ifdef USE_ENV_STACK
+EnvStack::EnvStack(void)
+{
+ num_allocated = 4;
+ // allocate pointer to array of num_allocated pointers to environment
+ data = (environment **)malloc(envp_size * num_allocated);
+ if (data == 0)
+ fatal("could not allocate environment data");
+ num_stored = 0;
+}
+
+EnvStack::~EnvStack(void)
+{
+ for (size_t i = 0; i < num_stored; i++)
+ delete data[i];
+ free(data);
+}
+
+// return top element from stack and decrease stack pointer
+//
+// the calling function must take care of properly deleting the result
+environment *
+EnvStack::pop(void)
+{
+ num_stored--;
+ environment *result = data[num_stored];
+ data[num_stored] = 0;
+ return result;
+}
+
+// copy argument and push this onto the stack
+void
+EnvStack::push(environment *e)
+{
+ environment *e_copy = new environment;
+ if (num_stored >= num_allocated) {
+ environment **old_data = data;
+ num_allocated *= 2;
+ data = (environment **)malloc(envp_size * num_allocated);
+ if (data == 0)
+ fatal("could not allocate data");
+ for (size_t i = 0; i < num_stored; i++)
+ data[i] = old_data[i];
+ free(old_data);
+ }
+ e_copy->col = new color;
+ e_copy->fill = new color;
+ *e_copy->col = *e->col;
+ *e_copy->fill = *e->fill;
+ e_copy->fontno = e->fontno;
+ e_copy->height = e->height;
+ e_copy->hpos = e->hpos;
+ e_copy->size = e->size;
+ e_copy->slant = e->slant;
+ e_copy->vpos = e->vpos;
+ data[num_stored] = e_copy;
+ num_stored++;
+}
+#endif // USE_ENV_STACK
+
+IntArray::IntArray(void)
+{
+ num_allocated = 4;
+ data = new IntArg[num_allocated];
+ num_stored = 0;
+}
+
+IntArray::IntArray(const size_t n)
+{
+ if (n <= 0)
+ fatal("number of integers to be allocated must be > 0");
+ num_allocated = n;
+ data = new IntArg[num_allocated];
+ num_stored = 0;
+}
+
+IntArray::~IntArray(void)
+{
+ delete[] data;
+}
+
+void
+IntArray::append(IntArg x)
+{
+ if (num_stored >= num_allocated) {
+ IntArg *old_data = data;
+ num_allocated *= 2;
+ data = new IntArg[num_allocated];
+ for (size_t i = 0; i < num_stored; i++)
+ data[i] = old_data[i];
+ delete[] old_data;
+ }
+ data[num_stored] = x;
+ num_stored++;
+}
+
+StringBuf::StringBuf(void)
+{
+ num_stored = 0;
+ num_allocated = 128;
+ data = new Char[num_allocated];
+}
+
+StringBuf::~StringBuf(void)
+{
+ delete[] data;
+}
+
+void
+StringBuf::append(const Char c)
+{
+ if (num_stored >= num_allocated) {
+ Char *old_data = data;
+ num_allocated *= 2;
+ data = new Char[num_allocated];
+ for (size_t i = 0; i < num_stored; i++)
+ data[i] = old_data[i];
+ delete[] old_data;
+ }
+ data[num_stored] = c;
+ num_stored++;
+}
+
+char *
+StringBuf::make_string(void)
+{
+ char *result = new char[num_stored + 1];
+ for (size_t i = 0; i < num_stored; i++)
+ result[i] = (char) data[i];
+ result[num_stored] = '\0';
+ return result;
+}
+
+void
+StringBuf::reset(void)
+{
+ num_stored = 0;
+}
+
+/**********************************************************************
+ utility functions
+ **********************************************************************/
+
+//////////////////////////////////////////////////////////////////////
+/* color_from_Df_command:
+ Process the gray shade setting command Df.
+
+ Transform Df style color into DF style color.
+ Df color: 0-1000, 0 is white
+ DF color: 0-65536, 0 is black
+
+ The Df command is obsoleted by command DFg, but kept for
+ compatibility.
+*/
+ColorArg
+color_from_Df_command(IntArg Df_gray)
+{
+ return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
+}
+
+//////////////////////////////////////////////////////////////////////
+/* delete_current_env():
+ Delete global variable current_env and its pointer members.
+
+ This should be a class method of environment.
+*/
+void delete_current_env(void)
+{
+ delete current_env->col;
+ delete current_env->fill;
+ delete current_env;
+ current_env = 0;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* fatal_command():
+ Emit error message about invalid command and abort.
+*/
+void
+fatal_command(char command)
+{
+ fatal("'%1' command invalid before first 'p' command", command);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_char():
+ Retrieve the next character from the input queue.
+
+ Return: The retrieved character (incl. EOF), converted to Char.
+*/
+inline Char
+get_char(void)
+{
+ return (Char) getc(current_file);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_color_arg():
+ Retrieve an argument suitable for the color commands m and DF.
+
+ Return: The retrieved color argument.
+*/
+ColorArg
+get_color_arg(void)
+{
+ IntArg x = get_integer_arg();
+ if (x < 0 || x > (IntArg)COLORARG_MAX) {
+ error("color component argument out of range");
+ x = 0;
+ }
+ return (ColorArg) x;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_D_fixed_args():
+ Get a fixed number of integer arguments for D commands.
+
+ Fatal if wrong number of arguments.
+ Too many arguments on the line raise a warning.
+ A line skip is done.
+
+ number: In-parameter, the number of arguments to be retrieved.
+ ignore: In-parameter, ignore next argument -- GNU troff always emits
+ pairs of parameters for 'D' extensions added by groff.
+ Default is 'false'.
+
+ Return: New IntArray containing the arguments.
+*/
+IntArray *
+get_D_fixed_args(const size_t number)
+{
+ if (number <= 0)
+ fatal("requested number of arguments must be > 0");
+ IntArray *args = new IntArray(number);
+ for (size_t i = 0; i < number; i++)
+ args->append(get_integer_arg());
+ skip_line_D();
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_D_fixed_args_odd_dummy():
+ Get a fixed number of integer arguments for D commands and optionally
+ ignore a dummy integer argument if the requested number is odd.
+
+ The gtroff program adds a dummy argument to some commands to get
+ an even number of arguments.
+ Error if the number of arguments differs from the scheme above.
+ A line skip is done.
+
+ number: In-parameter, the number of arguments to be retrieved.
+
+ Return: New IntArray containing the arguments.
+*/
+IntArray *
+get_D_fixed_args_odd_dummy(const size_t number)
+{
+ if (number <= 0)
+ fatal("requested number of arguments must be > 0");
+ IntArray *args = new IntArray(number);
+ for (size_t i = 0; i < number; i++)
+ args->append(get_integer_arg());
+ if (odd(number)) {
+ IntArray *a = get_possibly_integer_args();
+ if (a->len() > 1)
+ error("too many arguments");
+ delete a;
+ }
+ skip_line_D();
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_D_variable_args():
+ Get a variable even number of integer arguments for D commands.
+
+ Get as many integer arguments as possible from the rest of the
+ current line.
+ - The arguments are separated by an arbitrary sequence of space or
+ tab characters.
+ - A comment, a newline, or EOF indicates the end of processing.
+ - Error on non-digit characters different from these.
+ - A final line skip is performed (except for EOF).
+
+ Return: New IntArray of the retrieved arguments.
+*/
+IntArray *
+get_D_variable_args()
+{
+ IntArray *args = get_possibly_integer_args();
+ size_t n = args->len();
+ if (n <= 0)
+ error("no arguments found");
+ if (odd(n))
+ error("even number of arguments expected");
+ skip_line_D();
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_extended_arg():
+ Retrieve extended arg for 'x X' command.
+
+ - Skip leading spaces and tabs, error on EOL or newline.
+ - Return everything before the next NL or EOF ('#' is not a comment);
+ as long as the following line starts with '+' this is returned
+ as well, with the '+' replaced by a newline.
+ - Final line skip is always performed.
+
+ Return: Allocated (new) string of retrieved text argument.
+*/
+char *
+get_extended_arg(void)
+{
+ StringBuf buf = StringBuf();
+ Char c = next_arg_begin();
+ while ((int) c != EOF) {
+ if ((int) c == '\n') {
+ current_lineno++;
+ c = get_char();
+ if ((int) c == '+')
+ buf.append((Char) '\n');
+ else {
+ unget_char(c); // first character of next line
+ break;
+ }
+ }
+ else
+ buf.append(c);
+ c = get_char();
+ }
+ return buf.make_string();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_integer_arg(): Retrieve integer argument.
+
+ Skip leading spaces and tabs, collect an optional '-' and all
+ following decimal digits (at least one) up to the next non-digit,
+ which is restored onto the input queue.
+
+ Fatal error on all other situations.
+
+ Return: Retrieved integer.
+*/
+IntArg
+get_integer_arg(void)
+{
+ StringBuf buf = StringBuf();
+ Char c = next_arg_begin();
+ if ((int) c == '-') {
+ buf.append(c);
+ c = get_char();
+ }
+ if (!isdigit((int) c))
+ fatal("integer argument expected");
+ while (isdigit((int) c)) {
+ buf.append(c);
+ c = get_char();
+ }
+ // c is not a digit
+ unget_char(c);
+ char *s = buf.make_string();
+ errno = 0;
+ long int number = strtol(s, 0, 10);
+ if (errno != 0
+ || number > INTARG_MAX || number < -INTARG_MAX) {
+ error("integer argument too large");
+ number = 0;
+ }
+ delete[] s;
+ return (IntArg) number;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_possibly_integer_args():
+ Parse the rest of the input line as a list of integer arguments.
+
+ Get as many integer arguments as possible from the rest of the
+ current line, even none.
+ - The arguments are separated by an arbitrary sequence of space or
+ tab characters.
+ - A comment, a newline, or EOF indicates the end of processing.
+ - Error on non-digit characters different from these.
+ - No line skip is performed.
+
+ Return: New IntArray of the retrieved arguments.
+*/
+IntArray *
+get_possibly_integer_args()
+{
+ bool done = false;
+ StringBuf buf = StringBuf();
+ Char c = get_char();
+ IntArray *args = new IntArray();
+ while (!done) {
+ buf.reset();
+ while (is_space_or_tab(c))
+ c = get_char();
+ if (c == '-') {
+ Char c1 = get_char();
+ if (isdigit((int) c1)) {
+ buf.append(c);
+ c = c1;
+ }
+ else
+ unget_char(c1);
+ }
+ while (isdigit((int) c)) {
+ buf.append(c);
+ c = get_char();
+ }
+ if (!buf.is_empty()) {
+ char *s = buf.make_string();
+ errno = 0;
+ long int x = strtol(s, 0, 10);
+ if (errno
+ || x > INTARG_MAX || x < -INTARG_MAX) {
+ error("invalid integer argument, set to 0");
+ x = 0;
+ }
+ args->append((IntArg) x);
+ delete[] s;
+ }
+ // Here, c is not a digit.
+ // Terminate on comment, end of line, or end of file, while
+ // space or tab indicate continuation; otherwise error.
+ switch((int) c) {
+ case '#':
+ skip_to_end_of_line();
+ done = true;
+ break;
+ case '\n':
+ done = true;
+ unget_char(c);
+ break;
+ case EOF:
+ done = true;
+ break;
+ case ' ':
+ case '\t':
+ break;
+ default:
+ error("integer argument expected");
+ done = true;
+ break;
+ }
+ }
+ return args;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* get_string_arg():
+ Retrieve string arg.
+
+ - Skip leading spaces and tabs; error on EOL or newline.
+ - Return all following characters before the next space, tab,
+ newline, or EOF character (in-word '#' is not a comment character).
+ - The terminating space, tab, newline, or EOF character is restored
+ onto the input queue, so no line skip.
+
+ Return: Retrieved string as char *, allocated by 'new'.
+*/
+char *
+get_string_arg(void)
+{
+ StringBuf buf = StringBuf();
+ Char c = next_arg_begin();
+ while (!is_space_or_tab(c)
+ && c != Char('\n') && c != Char(EOF)) {
+ buf.append(c);
+ c = get_char();
+ }
+ unget_char(c); // restore white space
+ return buf.make_string();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* is_space_or_tab():
+ Test a character if it is a space or tab.
+
+ c: In-parameter, character to be tested.
+
+ Return: True, if c is a space or tab character, false otherwise.
+*/
+inline bool
+is_space_or_tab(const Char c)
+{
+ return (c == Char(' ') || c == Char('\t')) ? true : false;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* next_arg_begin():
+ Return first character of next argument.
+
+ Skip space and tab characters; error on newline or EOF.
+
+ Return: The first character different from these (including '#').
+*/
+Char
+next_arg_begin(void)
+{
+ Char c;
+ while (1) {
+ c = get_char();
+ switch ((int) c) {
+ case ' ':
+ case '\t':
+ break;
+ case '\n':
+ case EOF:
+ error("missing argument");
+ return c;
+ default: // first essential character
+ return c;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* next_command():
+ Find the first character of the next command.
+
+ Skip spaces, tabs, comments (introduced by #), and newlines.
+
+ Return: The first character different from these (including EOF).
+*/
+Char
+next_command(void)
+{
+ Char c;
+ while (1) {
+ c = get_char();
+ switch ((int) c) {
+ case ' ':
+ case '\t':
+ break;
+ case '\n':
+ current_lineno++;
+ break;
+ case '#': // comment
+ skip_line();
+ break;
+ default: // EOF or first essential character
+ return c;
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* odd():
+ Test whether argument is an odd number.
+
+ n: In-parameter, the integer to be tested.
+
+ Return: True if odd, false otherwise.
+*/
+inline bool
+odd(const int n)
+{
+ return ((n & 1) == 1) ? true : false;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* position_to_end_of_args():
+ Move graphical pointer to end of drawn figure.
+
+ This is used by the D commands that draw open geometrical figures.
+ The algorithm simply sums up all horizontal displacements (arguments
+ with even number) for the horizontal component. Similarly, the
+ vertical component is the sum of the odd arguments.
+
+ args: In-parameter, the arguments of a former drawing command.
+*/
+void
+position_to_end_of_args(const IntArray * const args)
+{
+ size_t i;
+ const size_t n = args->len();
+ for (i = 0; i < n; i += 2)
+ current_env->hpos += (*args)[i];
+ for (i = 1; i < n; i += 2)
+ current_env->vpos += (*args)[i];
+}
+
+//////////////////////////////////////////////////////////////////////
+/* remember_filename():
+ Set global variable current_filename.
+
+ The actual filename is stored in current_filename. This is used by
+ the postprocessors, expecting the name "<standard input>" for stdin.
+
+ filename: In-out-parameter; is changed to the new value also.
+*/
+void
+remember_filename(const char *filename)
+{
+ char *fname;
+ if (strcmp(filename, "-") == 0)
+ fname = (char *)"<standard input>";
+ else
+ fname = (char *)filename;
+ size_t len = strlen(fname) + 1;
+ if (current_filename != 0)
+ free((char *)current_filename);
+ current_filename = (const char *)malloc(len);
+ if (current_filename == 0)
+ fatal("can't malloc space for filename");
+ strncpy((char *)current_filename, (char *)fname, len);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* remember_source_filename():
+ Set global variable current_source_filename.
+
+ The actual filename is stored in current_filename. This is used by
+ the postprocessors, expecting the name "<standard input>" for stdin.
+
+ filename: In-out-parameter; is changed to the new value also.
+*/
+void
+remember_source_filename(const char *filename)
+{
+ char *fname;
+ if (strcmp(filename, "-") == 0)
+ fname = (char *)"<standard input>";
+ else
+ fname = (char *)filename;
+ size_t len = strlen(fname) + 1;
+ if (current_source_filename != 0)
+ free((char *)current_source_filename);
+ current_source_filename = (const char *)malloc(len);
+ if (current_source_filename == 0)
+ fatal("can't malloc space for filename");
+ strncpy((char *)current_source_filename, (char *)fname, len);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* send_draw():
+ Call draw method of printer class.
+
+ subcmd: Letter of actual D subcommand.
+ args: Array of integer arguments of actual D subcommand.
+*/
+void
+send_draw(const Char subcmd, const IntArray * const args)
+{
+ EnvInt n = (EnvInt) args->len();
+ pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line():
+ Go to next line within the input queue.
+
+ Skip the rest of the current line, including the newline character.
+ The global variable current_lineno is adjusted.
+ No errors are raised.
+*/
+void
+skip_line(void)
+{
+ Char c = get_char();
+ while (1) {
+ if (c == '\n') {
+ current_lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ c = get_char();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_checked ():
+ Check that there aren't any arguments left on the rest of the line,
+ then skip line.
+
+ Spaces, tabs, and a comment are allowed before newline or EOF.
+ All other characters raise an error.
+*/
+bool
+skip_line_checked(void)
+{
+ bool ok = true;
+ Char c = get_char();
+ while (is_space_or_tab(c))
+ c = get_char();
+ switch((int) c) {
+ case '#': // comment
+ skip_line();
+ break;
+ case '\n':
+ current_lineno++;
+ break;
+ case EOF:
+ break;
+ default:
+ ok = false;
+ skip_line();
+ break;
+ }
+ return ok;
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_fatal ():
+ Fatal error if arguments left, otherwise skip line.
+
+ Spaces, tabs, and a comment are allowed before newline or EOF.
+ All other characters trigger the error.
+*/
+void
+skip_line_fatal(void)
+{
+ bool ok = skip_line_checked();
+ if (!ok) {
+ current_lineno--;
+ error("too many arguments");
+ current_lineno++;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_warn ():
+ Skip line, but warn if arguments are left on actual line.
+
+ Spaces, tabs, and a comment are allowed before newline or EOF.
+ All other characters raise a warning
+*/
+void
+skip_line_warn(void)
+{
+ bool ok = skip_line_checked();
+ if (!ok) {
+ current_lineno--;
+ warning("too many arguments on current line");
+ current_lineno++;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_D ():
+ Skip line in 'D' commands.
+
+ Decide whether in case of an additional argument a fatal error is
+ raised (the documented classical behavior), only a warning is
+ issued, or the line is just skipped (former groff behavior).
+ Actually decided for the warning.
+*/
+void
+skip_line_D(void)
+{
+ skip_line_warn();
+ // or: skip_line_fatal();
+ // or: skip_line();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_line_x ():
+ Skip line in 'x' commands.
+
+ Decide whether in case of an additional argument a fatal error is
+ raised (the documented classical behavior), only a warning is
+ issued, or the line is just skipped (former groff behavior).
+ Actually decided for the warning.
+*/
+void
+skip_line_x(void)
+{
+ skip_line_warn();
+ // or: skip_line_fatal();
+ // or: skip_line();
+}
+
+//////////////////////////////////////////////////////////////////////
+/* skip_to_end_of_line():
+ Go to the end of the current line.
+
+ Skip the rest of the current line, excluding the newline character.
+ The global variable current_lineno is not changed.
+ No errors are raised.
+*/
+void
+skip_to_end_of_line(void)
+{
+ Char c = get_char();
+ while (1) {
+ if (c == '\n') {
+ unget_char(c);
+ return;
+ }
+ if (c == EOF)
+ return;
+ c = get_char();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+/* unget_char(c):
+ Restore character c onto input queue.
+
+ Write a character back onto the input stream.
+ EOF is gracefully handled.
+
+ c: In-parameter; character to be pushed onto the input queue.
+*/
+inline void
+unget_char(const Char c)
+{
+ if (c != EOF) {
+ int ch = (int) c;
+ if (ungetc(ch, current_file) == EOF)
+ fatal("could not unget character");
+ }
+}
+
+
+/**********************************************************************
+ parser subcommands
+ **********************************************************************/
+
+//////////////////////////////////////////////////////////////////////
+/* parse_color_command:
+ Process the commands m and DF, but not Df.
+
+ col: In-out-parameter; the color object to be set, must have
+ been initialized before.
+*/
+void
+parse_color_command(color *col)
+{
+ ColorArg gray = 0;
+ ColorArg red = 0, green = 0, blue = 0;
+ ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
+ Char subcmd = next_arg_begin();
+ switch((int) subcmd) {
+ case 'c': // DFc or mc: CMY
+ cyan = get_color_arg();
+ magenta = get_color_arg();
+ yellow = get_color_arg();
+ col->set_cmy(cyan, magenta, yellow);
+ break;
+ case 'd': // DFd or md: set default color
+ col->set_default();
+ break;
+ case 'g': // DFg or mg: gray
+ gray = get_color_arg();
+ col->set_gray(gray);
+ break;
+ case 'k': // DFk or mk: CMYK
+ cyan = get_color_arg();
+ magenta = get_color_arg();
+ yellow = get_color_arg();
+ black = get_color_arg();
+ col->set_cmyk(cyan, magenta, yellow, black);
+ break;
+ case 'r': // DFr or mr: RGB
+ red = get_color_arg();
+ green = get_color_arg();
+ blue = get_color_arg();
+ col->set_rgb(red, green, blue);
+ break;
+ default:
+ error("invalid color scheme '%1'", (int) subcmd);
+ break;
+ } // end of color subcommands
+}
+
+//////////////////////////////////////////////////////////////////////
+/* parse_D_command():
+ Parse the subcommands of graphical command D.
+
+ This is the part of the do_file() parser that scans the graphical
+ subcommands.
+ - Error on lacking or wrong arguments.
+ - Warning on too many arguments.
+ - Line is always skipped.
+*/
+void
+parse_D_command()
+{
+ Char subcmd = next_arg_begin();
+ switch((int) subcmd) {
+ case '~': // D~: draw B-spline
+ // actually, this isn't available for some postprocessors
+ // fall through
+ default: // unknown options are passed to device
+ {
+ IntArray *args = get_D_variable_args();
+ send_draw(subcmd, args);
+ position_to_end_of_args(args);
+ delete args;
+ break;
+ }
+ case 'a': // Da: draw arc
+ {
+ IntArray *args = get_D_fixed_args(4);
+ send_draw(subcmd, args);
+ position_to_end_of_args(args);
+ delete args;
+ break;
+ }
+ case 'c': // Dc: draw circle line
+ {
+ IntArray *args = get_D_fixed_args(1);
+ send_draw(subcmd, args);
+ // move to right end
+ current_env->hpos += (*args)[0];
+ delete args;
+ break;
+ }
+ case 'C': // DC: draw solid circle
+ {
+ IntArray *args = get_D_fixed_args_odd_dummy(1);
+ send_draw(subcmd, args);
+ // move to right end
+ current_env->hpos += (*args)[0];
+ delete args;
+ break;
+ }
+ case 'e': // De: draw ellipse line
+ case 'E': // DE: draw solid ellipse
+ {
+ IntArray *args = get_D_fixed_args(2);
+ send_draw(subcmd, args);
+ // move to right end
+ current_env->hpos += (*args)[0];
+ delete args;
+ break;
+ }
+ case 'f': // Df: set fill gray; obsoleted by DFg
+ {
+ IntArg arg = get_integer_arg();
+ if ((arg >= 0) && (arg <= 1000)) {
+ // convert arg and treat it like DFg
+ ColorArg gray = color_from_Df_command(arg);
+ current_env->fill->set_gray(gray);
+ }
+ else {
+ // set fill color to the same value as the current outline color
+ delete current_env->fill;
+ current_env->fill = new color(current_env->col);
+ }
+ pr->change_fill_color(current_env);
+ // skip unused 'vertical' component (\D'...' always emits pairs)
+ (void) get_integer_arg();
+# ifdef STUPID_DRAWING_POSITIONING
+ current_env->hpos += arg;
+# endif
+ skip_line_x();
+ break;
+ }
+ case 'F': // DF: set fill color, several formats
+ parse_color_command(current_env->fill);
+ pr->change_fill_color(current_env);
+ // no positioning (setting-only command)
+ skip_line_x();
+ break;
+ case 'l': // Dl: draw line
+ {
+ IntArray *args = get_D_fixed_args(2);
+ send_draw(subcmd, args);
+ position_to_end_of_args(args);
+ delete args;
+ break;
+ }
+ case 'p': // Dp: draw closed polygon line
+ case 'P': // DP: draw solid closed polygon
+ {
+ IntArray *args = get_D_variable_args();
+ send_draw(subcmd, args);
+# ifdef STUPID_DRAWING_POSITIONING
+ // final args positioning
+ position_to_end_of_args(args);
+# endif
+ delete args;
+ break;
+ }
+ case 't': // Dt: set line thickness
+ {
+ IntArray *args = get_D_fixed_args_odd_dummy(1);
+ send_draw(subcmd, args);
+# ifdef STUPID_DRAWING_POSITIONING
+ // final args positioning
+ position_to_end_of_args(args);
+# endif
+ delete args;
+ break;
+ }
+ } // end of D subcommands
+}
+
+//////////////////////////////////////////////////////////////////////
+/* parse_x_command():
+ Parse subcommands of the device control command x.
+
+ This is the part of the do_file() parser that scans the device
+ controlling commands.
+ - Error on duplicate prologue commands.
+ - Error on wrong or lacking arguments.
+ - Warning on too many arguments.
+ - Line is always skipped.
+
+ Globals:
+ - current_env: is set by many subcommands.
+ - npages: page counting variable
+
+ Return: boolean in the meaning of 'stopped'
+ - true if parsing should be stopped ('x stop').
+ - false if parsing should continue.
+*/
+bool
+parse_x_command(void)
+{
+ bool stopped = false;
+ char *subcmd_str = get_string_arg();
+ char subcmd = subcmd_str[0];
+ switch (subcmd) {
+ case 'f': // x font: mount font
+ {
+ IntArg n = get_integer_arg();
+ char *name = get_string_arg();
+ pr->load_font(n, name);
+ delete[] name;
+ skip_line_x();
+ break;
+ }
+ case 'F': // x Filename: set filename for errors
+ {
+ char *str_arg = get_extended_arg();
+ if (str_arg == 0)
+ warning("empty argument for 'x F' command");
+ else {
+ remember_source_filename(str_arg);
+ delete[] str_arg;
+ }
+ break;
+ }
+ case 'H': // x Height: set character height
+ current_env->height = get_integer_arg();
+ if (current_env->height == current_env->size)
+ current_env->height = 0;
+ skip_line_x();
+ break;
+ case 'i': // x init: initialize device
+ error("duplicate 'x init' command");
+ skip_line_x();
+ break;
+ case 'p': // x pause: pause device
+ skip_line_x();
+ break;
+ case 'r': // x res: set resolution
+ error("duplicate 'x res' command");
+ skip_line_x();
+ break;
+ case 's': // x stop: stop device
+ stopped = true;
+ skip_line_x();
+ break;
+ case 'S': // x Slant: set slant
+ current_env->slant = get_integer_arg();
+ skip_line_x();
+ break;
+ case 't': // x trailer: generate trailer info
+ skip_line_x();
+ break;
+ case 'T': // x Typesetter: set typesetter
+ error("duplicate 'x T' command");
+ skip_line();
+ break;
+ case 'u': // x underline: from .cu
+ {
+ char *str_arg = get_string_arg();
+ pr->special(str_arg, current_env, 'u');
+ delete[] str_arg;
+ skip_line_x();
+ break;
+ }
+ case 'X': // x X: send uninterpretedly to device
+ {
+ char *str_arg = get_extended_arg(); // includes line skip
+ if (npages <= 0)
+ error("'x X' command invalid before first 'p' command");
+ else if (str_arg && (strncmp(str_arg, "devtag:",
+ strlen("devtag:")) == 0))
+ pr->devtag(str_arg, current_env);
+ else
+ pr->special(str_arg, current_env);
+ delete[] str_arg;
+ break;
+ }
+ default: // ignore unknown x commands, but warn
+ warning("unknown command 'x %1'", subcmd);
+ skip_line();
+ }
+ delete[] subcmd_str;
+ return stopped;
+}
+
+
+/**********************************************************************
+ exported part (by driver.h)
+ **********************************************************************/
+
+////////////////////////////////////////////////////////////////////////
+/* do_file():
+ Parse and postprocess groff intermediate output.
+
+ filename: "-" for standard input, normal file name otherwise
+*/
+void
+do_file(const char *filename)
+{
+ Char command;
+ bool stopped = false; // terminating condition
+
+#ifdef USE_ENV_STACK
+ EnvStack env_stack = EnvStack();
+#endif // USE_ENV_STACK
+
+ // setup of global variables
+ npages = 0;
+ current_lineno = 1;
+ // 'pr' is initialized after the prologue.
+ // 'device' is set by the 1st prologue command.
+
+ if (filename[0] == '-' && filename[1] == '\0')
+ current_file = stdin;
+ else {
+ errno = 0;
+ current_file = fopen(filename, "r");
+ if (errno != 0 || current_file == 0) {
+ error("can't open file '%1'", filename);
+ return;
+ }
+ }
+ remember_filename(filename);
+
+ if (current_env != 0)
+ delete_current_env();
+ current_env = new environment;
+ current_env->col = new color;
+ current_env->fill = new color;
+ current_env->fontno = -1;
+ current_env->height = 0;
+ current_env->hpos = -1;
+ current_env->slant = 0;
+ current_env->size = 0;
+ current_env->vpos = -1;
+
+ // parsing of prologue (first 3 commands)
+ {
+ char *str_arg;
+ IntArg int_arg;
+
+ // 1st command 'x T'
+ command = next_command();
+ if ((int) command == EOF)
+ return;
+ if ((int) command != 'x')
+ fatal("the first command must be 'x T'");
+ str_arg = get_string_arg();
+ if (str_arg[0] != 'T')
+ fatal("the first command must be 'x T'");
+ delete[] str_arg;
+ char *tmp_dev = get_string_arg();
+ if (pr == 0) { // note: 'pr' initialized after prologue
+ device = tmp_dev;
+ if (0 /* nullptr */ == font::load_desc())
+ fatal("cannot load description of '%1' device", tmp_dev);
+ }
+ else {
+ if (device == 0 || strcmp(device, tmp_dev) != 0)
+ fatal("all files must use the same device");
+ delete[] tmp_dev;
+ }
+ skip_line_x(); // ignore further arguments
+ current_env->size = 10 * font::sizescale;
+
+ // 2nd command 'x res'
+ command = next_command();
+ if ((int) command != 'x')
+ fatal("the second command must be 'x res'");
+ str_arg = get_string_arg();
+ if (str_arg[0] != 'r')
+ fatal("the second command must be 'x res'");
+ delete[] str_arg;
+ int_arg = get_integer_arg();
+ EnvInt font_res = font::res;
+ if (int_arg != font_res)
+ fatal("resolution does not match");
+ int_arg = get_integer_arg();
+ if (int_arg != font::hor)
+ fatal("minimum horizontal motion does not match");
+ int_arg = get_integer_arg();
+ if (int_arg != font::vert)
+ fatal("minimum vertical motion does not match");
+ skip_line_x(); // ignore further arguments
+
+ // 3rd command 'x init'
+ command = next_command();
+ if (command != 'x')
+ fatal("the third command must be 'x init'");
+ str_arg = get_string_arg();
+ if (str_arg[0] != 'i')
+ fatal("the third command must be 'x init'");
+ delete[] str_arg;
+ skip_line_x();
+ }
+
+ // parsing of body
+ if (pr == 0)
+ pr = make_printer();
+ while (!stopped) {
+ command = next_command();
+ if (command == EOF)
+ break;
+ // spaces, tabs, comments, and newlines are skipped here
+ switch ((int) command) {
+ case '#': // #: comment, ignore up to end of line
+ skip_line();
+ break;
+#ifdef USE_ENV_STACK
+ case '{': // {: start a new environment (a copy)
+ env_stack.push(current_env);
+ break;
+ case '}': // }: pop previous env from stack
+ delete_current_env();
+ current_env = env_stack.pop();
+ break;
+#endif // USE_ENV_STACK
+ case '0': // ddc: obsolete jump and print command
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ { // expect 2 digits and a character
+ char s[3];
+ Char c = next_arg_begin();
+ if (npages <= 0)
+ fatal_command(command);
+ if (!isdigit((int) c)) {
+ error("digit expected");
+ c = 0;
+ }
+ s[0] = (char) command;
+ s[1] = (char) c;
+ s[2] = '\0';
+ errno = 0;
+ long int x = strtol(s, 0, 10);
+ if (errno != 0)
+ error("couldn't convert 2 digits");
+ EnvInt hor_pos = (EnvInt) x;
+ current_env->hpos += hor_pos;
+ c = next_arg_begin();
+ if ((int) c == '\n' || (int) c == EOF)
+ error("character argument expected");
+ else
+ pr->set_ascii_char((unsigned char) c, current_env);
+ break;
+ }
+ case 'c': // c: print ascii char without moving
+ {
+ if (npages <= 0)
+ fatal_command(command);
+ Char c = next_arg_begin();
+ if (c == '\n' || c == EOF)
+ error("missing argument to 'c' command");
+ else
+ pr->set_ascii_char((unsigned char) c, current_env);
+ break;
+ }
+ case 'C': // C: print named special character
+ {
+ if (npages <= 0)
+ fatal_command(command);
+ char *str_arg = get_string_arg();
+ pr->set_special_char(str_arg, current_env);
+ delete[] str_arg;
+ break;
+ }
+ case 'D': // drawing commands
+ if (npages <= 0)
+ fatal_command(command);
+ parse_D_command();
+ break;
+ case 'f': // f: set font to number
+ current_env->fontno = get_integer_arg();
+ break;
+ case 'F': // F: obsolete, replaced by 'x F'
+ {
+ char *str_arg = get_extended_arg();
+ remember_source_filename(str_arg);
+ delete[] str_arg;
+ break;
+ }
+ case 'h': // h: relative horizontal move
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->hpos += (EnvInt) get_integer_arg();
+ break;
+ case 'H': // H: absolute horizontal positioning
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->hpos = (EnvInt) get_integer_arg();
+ break;
+ case 'm': // m: glyph color
+ parse_color_command(current_env->col);
+ pr->change_color(current_env);
+ break;
+ case 'n': // n: print end of line
+ // ignore two arguments (historically)
+ if (npages <= 0)
+ fatal_command(command);
+ pr->end_of_line();
+ (void) get_integer_arg();
+ (void) get_integer_arg();
+ break;
+ case 'N': // N: print char with given int code
+ if (npages <= 0)
+ fatal_command(command);
+ pr->set_numbered_char(get_integer_arg(), current_env);
+ break;
+ case 'p': // p: start new page with given number
+ if (npages > 0)
+ pr->end_page(current_env->vpos);
+ npages++; // increment # of processed pages
+ pr->begin_page(get_integer_arg());
+ current_env->vpos = 0;
+ break;
+ case 's': // s: set point size
+ current_env->size = get_integer_arg();
+ if (current_env->height == current_env->size)
+ current_env->height = 0;
+ break;
+ case 't': // t: print a text word
+ {
+ char c;
+ if (npages <= 0)
+ fatal_command(command);
+ char *str_arg = get_string_arg();
+ size_t i = 0;
+ while ((c = str_arg[i++]) != '\0') {
+ EnvInt w;
+ pr->set_ascii_char((unsigned char) c, current_env, &w);
+ current_env->hpos += w;
+ }
+ delete[] str_arg;
+ break;
+ }
+ case 'u': // u: print spaced word
+ {
+ char c;
+ if (npages <= 0)
+ fatal_command(command);
+ EnvInt kern = (EnvInt) get_integer_arg();
+ char *str_arg = get_string_arg();
+ size_t i = 0;
+ while ((c = str_arg[i++]) != '\0') {
+ EnvInt w;
+ pr->set_ascii_char((unsigned char) c, current_env, &w);
+ current_env->hpos += w + kern;
+ }
+ delete[] str_arg;
+ break;
+ }
+ case 'v': // v: relative vertical move
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->vpos += (EnvInt) get_integer_arg();
+ break;
+ case 'V': // V: absolute vertical positioning
+ if (npages <= 0)
+ fatal_command(command);
+ current_env->vpos = (EnvInt) get_integer_arg();
+ break;
+ case 'w': // w: inform about paddable space
+ break;
+ case 'x': // device controlling commands
+ stopped = parse_x_command();
+ break;
+ default:
+ warning("unrecognized command '%1'", (unsigned char) command);
+ skip_line();
+ break;
+ } // end of switch
+ } // end of while
+
+ // end of file reached
+ if (npages > 0)
+ pr->end_page(current_env->vpos);
+ delete pr;
+ pr = 0;
+ fclose(current_file);
+ // If 'stopped' is not 'true' here then there wasn't any 'x stop'.
+ if (!stopped)
+ warning("no final 'x stop' command");
+ delete_current_env();
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
diff --git a/src/libs/libdriver/libdriver.am b/src/libs/libdriver/libdriver.am
new file mode 100644
index 0000000..51475d8
--- /dev/null
+++ b/src/libs/libdriver/libdriver.am
@@ -0,0 +1,31 @@
+# Automake rules for 'libdriver'
+#
+# Copyright (C) 2014-2020 Free Software Foundation, Inc.
+#
+# '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 2 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/gpl-2.0.html>.
+#
+########################################################################
+
+noinst_LIBRARIES += libdriver.a
+libdriver_a_SOURCES = \
+ src/libs/libdriver/input.cpp \
+ src/libs/libdriver/printer.cpp
+
+
+# Local Variables:
+# mode: makefile-automake
+# fill-column: 72
+# End:
+# vim: set autoindent filetype=automake textwidth=72:
diff --git a/src/libs/libdriver/printer.cpp b/src/libs/libdriver/printer.cpp
new file mode 100644
index 0000000..f89b2e9
--- /dev/null
+++ b/src/libs/libdriver/printer.cpp
@@ -0,0 +1,268 @@
+/* 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 <assert.h>
+
+#include "driver.h"
+
+/* If we are sending output to an onscreen pager (as is the normal case
+ when reading man pages), then we may get an error state on the output
+ stream, if the user does not read all the way to the end.
+
+ We normally expect to catch this, and clean up the error context,
+ when the pager exits, because we should get, and handle, a SIGPIPE.
+
+ However ...
+*/
+
+#if (defined(_MSC_VER) || defined(_WIN32)) \
+ && !defined(__CYGWIN__) && !defined(_UWIN)
+
+ /* Native MS-Windows doesn't know about SIGPIPE, so we cannot detect
+ the early exit from the pager, and therefore, cannot clean up the
+ error context; thus we use the following static function to
+ identify this particular error context, and so suppress unwanted
+ diagnostics.
+ */
+
+ static int
+ check_for_output_error (FILE* stream)
+ {
+ /* First, clean up any prior error context on the output stream */
+ if (ferror (stream))
+ clearerr (stream);
+ /* Clear errno, in case clearerr() and fflush() don't */
+ errno = 0;
+ /* Flush the output stream, so we can capture any error context,
+ other than the specific case we wish to suppress.
+
+ Microsoft doesn't document it, but the error code for the
+ specific context we are trying to suppress seems to be EINVAL --
+ a strange choice, since it is not normally associated with
+ fflush(); of course, it *should* be EPIPE, but this *definitely*
+ is not used, and *is* so documented.
+ */
+ return ((fflush(stream) < 0) && (errno != EINVAL));
+ }
+
+#else
+
+ /* For other systems, we simply assume that *any* output error context
+ is to be reported.
+ */
+# define check_for_output_error(stream) ferror(stream) || fflush(stream) < 0
+
+#endif
+
+
+font_pointer_list::font_pointer_list(font *f, font_pointer_list *fp)
+: p(f), next(fp)
+{
+}
+
+printer::printer()
+: font_list(0), font_table(0), nfonts(0)
+{
+}
+
+printer::~printer()
+{
+ delete[] font_table;
+ while (font_list) {
+ font_pointer_list *tem = font_list;
+ font_list = font_list->next;
+ delete tem->p;
+ delete tem;
+ }
+ if (check_for_output_error(stdout))
+ fatal("output error");
+}
+
+void printer::load_font(int n, const char *nm)
+{
+ assert(n >= 0);
+ if (n >= nfonts) {
+ if (nfonts == 0) {
+ nfonts = 10;
+ if (nfonts <= n)
+ nfonts = n + 1;
+ font_table = new font *[nfonts];
+ for (int i = 0; i < nfonts; i++)
+ font_table[i] = 0;
+ }
+ else {
+ font **old_font_table = font_table;
+ int old_nfonts = nfonts;
+ nfonts *= 2;
+ if (n >= nfonts)
+ nfonts = n + 1;
+ font_table = new font *[nfonts];
+ int i;
+ for (i = 0; i < old_nfonts; i++)
+ font_table[i] = old_font_table[i];
+ for (i = old_nfonts; i < nfonts; i++)
+ font_table[i] = 0;
+ delete[] old_font_table;
+ }
+ }
+ font *f = find_font(nm);
+ font_table[n] = f;
+}
+
+font *printer::find_font(const char *nm)
+{
+ for (font_pointer_list *p = font_list; p; p = p->next)
+ if (strcmp(p->p->get_name(), nm) == 0)
+ return p->p;
+ font *f = make_font(nm);
+ if (0 /* nullptr */ == f)
+ fatal("cannot find font '%1'", nm);
+ font_list = new font_pointer_list(f, font_list);
+ return f;
+}
+
+font *printer::make_font(const char *nm)
+{
+ return font::load_font(nm);
+}
+
+void printer::end_of_line()
+{
+}
+
+void printer::special(char *, const environment *, char)
+{
+}
+
+void printer::devtag(char *, const environment *, char)
+{
+}
+
+void printer::draw(int, int *, int, const environment *)
+{
+}
+
+void printer::change_color(const environment * const)
+{
+}
+
+void printer::change_fill_color(const environment * const)
+{
+}
+
+void printer::set_ascii_char(unsigned char c, const environment *env,
+ int *widthp)
+{
+ char buf[2];
+ int w;
+ font *f;
+
+ buf[0] = c;
+ buf[1] = '\0';
+
+ glyph *g = set_char_and_width(buf, env, &w, &f);
+
+ if (g != UNDEFINED_GLYPH) {
+ set_char(g, f, env, w, 0);
+ if (widthp)
+ *widthp = w;
+ }
+}
+
+void printer::set_special_char(const char *nm, const environment *env,
+ int *widthp)
+{
+ font *f;
+ int w;
+ glyph *g = set_char_and_width(nm, env, &w, &f);
+ if (g != UNDEFINED_GLYPH) {
+ set_char(g, f, env, w, nm);
+ if (widthp)
+ *widthp = w;
+ }
+}
+
+glyph *printer::set_char_and_width(const char *nm,
+ const environment *env, int *widthp,
+ font **f)
+{
+ glyph *g = name_to_glyph(nm);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return UNDEFINED_GLYPH;
+ }
+ *f = font_table[fn];
+ if (*f == 0) {
+ error("no font mounted at position %1", fn);
+ return UNDEFINED_GLYPH;
+ }
+ if (!(*f)->contains(g)) {
+ if (nm[0] != '\0' && nm[1] == '\0')
+ error("font '%1' does not contain ordinary character '%2'",
+ (*f)->get_name(), nm[0]);
+ else
+ error("font '%1' does not contain special character '%2'",
+ (*f)->get_name(), nm);
+ return UNDEFINED_GLYPH;
+ }
+ int w = (*f)->get_width(g, env->size);
+ if (widthp)
+ *widthp = w;
+ return g;
+}
+
+void printer::set_numbered_char(int num, const environment *env, int
+ *widthp)
+{
+ glyph *g = number_to_glyph(num);
+ int fn = env->fontno;
+ if (fn < 0 || fn >= nfonts) {
+ error("invalid font position '%1'", fn);
+ return;
+ }
+ font *f = font_table[fn];
+ if (f == 0) {
+ error("no font mounted at position %1", fn);
+ return;
+ }
+ if (!f->contains(g)) {
+ error("font '%1' does not contain numbered character %2",
+ f->get_name(), num);
+ return;
+ }
+ int w = f->get_width(g, env->size);
+ if (widthp)
+ *widthp = w;
+ set_char(g, f, env, w, 0);
+}
+
+font *printer::get_font_from_index(int fontno)
+{
+ if ((fontno >= 0) && (fontno < nfonts))
+ return font_table[fontno];
+ else
+ return 0;
+}
+
+// Local Variables:
+// fill-column: 72
+// mode: C++
+// End:
+// vim: set cindent noexpandtab shiftwidth=2 textwidth=72: