1/*	$NetBSD: input.cpp,v 1.2 2016/01/13 19:01:58 christos Exp $	*/
2
3// -*- C++ -*-
4
5// <groff_src_dir>/src/libs/libdriver/input.cpp
6
7/* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005
8   Free Software Foundation, Inc.
9
10   Written by James Clark (jjc@jclark.com)
11   Major rewrite 2001 by Bernd Warken (bwarken@mayn.de)
12
13   Last update: 15 Jun 2005
14
15   This file is part of groff, the GNU roff text processing system.
16
17   groff is free software; you can redistribute it and/or modify it
18   under the terms of the GNU General Public License as published by
19   the Free Software Foundation; either version 2, or (at your option)
20   any later version.
21
22   groff is distributed in the hope that it will be useful, but
23   WITHOUT ANY WARRANTY; without even the implied warranty of
24   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25   General Public License for more details.
26
27   You should have received a copy of the GNU General Public License
28   along with groff; see the file COPYING.  If not, write to the Free
29   Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA
30   02110-1301, USA.
31*/
32
33/* Description
34
35   This file implements the parser for the intermediate groff output,
36   see groff_out(5), and does the printout for the given device.
37
38   All parsed information is processed within the function do_file().
39   A device postprocessor just needs to fill in the methods for the class
40   `printer' (or rather a derived class) without having to worry about
41   the syntax of the intermediate output format.  Consequently, the
42   programming of groff postprocessors is similar to the development of
43   device drivers.
44
45   The prototyping for this file is done in driver.h (and error.h).
46*/
47
48/* Changes of the 2001 rewrite of this file.
49
50   The interface to the outside and the handling of the global
51   variables was not changed, but internally many necessary changes
52   were performed.
53
54   The main aim for this rewrite is to provide a first step towards
55   making groff fully compatible with classical troff without pain.
56
57   Bugs fixed
58   - Unknown subcommands of `D' and `x' are now ignored like in the
59     classical case, but a warning is issued.  This was also
60     implemented for the other commands.
61   - A warning is emitted if `x stop' is missing.
62   - `DC' and `DE' commands didn't position to the right end after
63     drawing (now they do), see discussion below.
64   - So far, `x stop' was ignored.  Now it terminates the processing
65     of the current intermediate output file like the classical troff.
66   - The command `c' didn't check correctly on white-space.
67   - The environment stack wasn't suitable for the color extensions
68     (replaced by a class).
69   - The old groff parser could only handle a prologue with the first
70     3 lines having a fixed structure, while classical troff specified
71     the sequence of the first 3 commands without further
72     restrictions.  Now the parser is smart about additional
73     white space, comments, and empty lines in the prologue.
74   - The old parser allowed space characters only as syntactical
75     separators, while classical troff had tab characters as well.
76     Now any sequence of tabs and/or spaces is a syntactical
77     separator between commands and/or arguments.
78   - Range checks for numbers implemented.
79
80   New and improved features
81   - The color commands `m' and `DF' are added.
82   - The old color command `Df' is now converted and delegated to `DFg'.
83   - The command `F' is implemented as `use intended file name'.  It
84     checks whether its argument agrees with the file name used so far,
85     otherwise a warning is issued.  Then the new name is remembered
86     and used for the following error messages.
87   - For the positioning after drawing commands, an alternative, easier
88     scheme is provided, but not yet activated; it can be chosen by
89     undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
90     It extends the rule of the classical troff output language in a
91     logical way instead of the rather strange actual positioning.
92     For details, see the discussion below.
93   - For the `D' commands that only set the environment, the calling of
94     pr->send_draw() was removed because this doesn't make sense for
95     the `DF' commands; the (changed) environment is sent with the
96     next command anyway.
97   - Error handling was clearly separated into warnings and fatal.
98   - The error behavior on additional arguments for `D' and `x'
99     commands with a fixed number of arguments was changed from being
100     ignored (former groff) to issue a warning and ignore (now), see
101     skip_line_x().  No fatal was chosen because both string and
102     integer arguments can occur.
103   - The gtroff program issues a trailing dummy integer argument for
104     some drawing commands with an odd number of arguments to make the
105     number of arguments even, e.g. the DC and Dt commands; this is
106     honored now.
107   - All D commands with a variable number of args expect an even
108     number of trailing integer arguments, so fatal on error was
109     implemented.
110   - Disable environment stack and the commands `{' and `}' by making
111     them conditional on macro USE_ENV_STACK; actually, this is
112     undefined by default.  There isn't any known application for these
113     features.
114
115   Cosmetics
116   - Nested `switch' commands are avoided by using more functions.
117     Dangerous 'fall-through's avoided.
118   - Commands and functions are sorted alphabetically (where possible).
119   - Dynamic arrays/buffers are now implemented as container classes.
120   - Some functions had an ugly return structure; this has been
121     streamlined by using classes.
122   - Use standard C math functions for number handling, so getting rid
123     of differences to '0'.
124   - The macro `IntArg' has been created for an easier transition
125     to guaranteed 32 bits integers (`int' is enough for GNU, while
126     ANSI only guarantees `long int' to have a length of 32 bits).
127   - The many usages of type `int' are differentiated by using `Char',
128     `bool', and `IntArg' where appropriate.
129   - To ease the calls of the local utility functions, the parser
130     variables `current_file', `npages', and `current_env'
131     (formerly env) were made global to the file (formerly they were
132     local to the do_file() function)
133   - Various comments were added.
134
135   TODO
136   - Get rid of the stupid drawing positioning.
137   - Can the `Dt' command be completely handled by setting environment
138     within do_file() instead of sending to pr?
139   - Integer arguments must be >= 32 bits, use conditional #define.
140   - Add scaling facility for classical device independence and
141     non-groff devices.  Classical troff output had a quasi device
142     independence by scaling the intermediate output to the resolution
143     of the postprocessor device if different from the one specified
144     with `x T', groff have not.  So implement full quasi device
145     indepedence, including the mapping of the strange classical
146     devices to the postprocessor device (seems to be reasonably
147     easy).
148   - The external, global pointer variables are not optimally handled.
149     - The global variables `current_filename',
150       `current_source_filename', and `current_lineno' are only used for
151       error reporting.  So implement a static class `Error'
152       (`::' calls).
153     - The global `device' is the name used during the formatting
154       process; there should be a new variable for the device name used
155       during the postprocessing.
156  - Implement the B-spline drawing `D~' for all graphical devices.
157  - Make `environment' a class with an overflow check for its members
158    and a delete method to get rid of delete_current_env().
159  - Implement the `EnvStack' to use `new' instead of `malloc'.
160  - The class definitions of this document could go into a new file.
161  - The comments in this section should go to a `Changelog' or some
162    `README' file in this directory.
163*/
164
165/*
166  Discussion of the positioning by drawing commands
167
168  There was some confusion about the positioning of the graphical
169  pointer at the printout after having executed a `D' command.
170  The classical troff manual of Osanna & Kernighan specified,
171
172    `The position after a graphical object has been drawn is
173     at its end; for circles and ellipses, the "end" is at the
174     right side.'
175
176  From this, it follows that
177  - all open figures (args, splines, and lines) should position at their
178    final point.
179  - all circles and ellipses should position at their right-most point
180    (as if 2 halves had been drawn).
181  - all closed figures apart from circles and ellipses shouldn't change
182    the position because they return to their origin.
183  - all setting commands should not change position because they do not
184    draw any graphical object.
185
186  In the case of the open figures, this means that the horizontal
187  displacement is the sum of all odd arguments and the vertical offset
188  the sum of all even arguments, called the alternate arguments sum
189  displacement in the following.
190
191  Unfortunately, groff did not implement this simple rule.  The former
192  documentation in groff_out(5) differed from the source code, and
193  neither of them is compatible with the classical rule.
194
195  The former groff_out(5) specified to use the alternative arguments
196  sum displacement for calculating the drawing positioning of
197  non-classical commands, including the `Dt' command (setting-only)
198  and closed polygons.  Applying this to the new groff color commands
199  will lead to disaster.  For their arguments can take large values (>
200  65000).  On low resolution devices, the displacement of such large
201  values will corrupt the display or kill the printer.  So the
202  nonsense specification has come to a natural end anyway.
203
204  The groff source code, however, had no positioning for the
205  setting-only commands (esp. `Dt'), the right-end positioning for
206  outlined circles and ellipses, and the alternative argument sum
207  displacement for all other commands (including filled circles and
208  ellipses).
209
210  The reason why no one seems to have suffered from this mayhem so
211  far is that the graphical objects are usually generated by
212  preprocessors like pic that do not depend on the automatic
213  positioning.  When using the low level `\D' escape sequences or `D'
214  output commands, the strange positionings can be circumvented by
215  absolute positionings or by tricks like `\Z'.
216
217  So doing an exorcism on the strange, incompatible displacements might
218  not harm any existing documents, but will make the usage of the
219  graphical escape sequences and commands natural.
220
221  That's why the rewrite of this file returned to the reasonable,
222  classical specification with its clear end-of-drawing rule that is
223  suitable for all cases.  But a macro STUPID_DRAWING_POSITIONING is
224  provided for testing the funny former behavior.
225
226  The new rule implies the following behavior.
227  - Setting commands (`Dt', `Df', `DF') and polygons (`Dp' and `DP')
228    do not change position now.
229  - Filled circles and ellipses (`DC' and `DE') position at their
230    most right point (outlined ones `Dc' and `De' did this anyway).
231  - As before, all open graphical objects position to their final
232    drawing point (alternate sum of the command arguments).
233
234*/
235
236#ifndef STUPID_DRAWING_POSITIONING
237// uncomment next line if all non-classical D commands shall position
238// to the strange alternate sum of args displacement
239#define STUPID_DRAWING_POSITIONING
240#endif
241
242// Decide whether the commands `{' and `}' for different environments
243// should be used.
244#undef USE_ENV_STACK
245
246#include "driver.h"
247#include "device.h"
248
249#include <stdlib.h>
250#include <errno.h>
251#include <ctype.h>
252#include <math.h>
253
254
255/**********************************************************************
256                           local types
257 **********************************************************************/
258
259// integer type used in the fields of struct environment (see printer.h)
260typedef int EnvInt;
261
262// integer arguments of groff_out commands, must be >= 32 bits
263typedef int IntArg;
264
265// color components of groff_out color commands, must be >= 32 bits
266typedef unsigned int ColorArg;
267
268// Array for IntArg values.
269class IntArray {
270  size_t num_allocated;
271  size_t num_stored;
272  IntArg *data;
273public:
274  IntArray(void);
275  IntArray(const size_t);
276  ~IntArray(void);
277  IntArg operator[](const size_t i) const
278  {
279    if (i >= num_stored)
280      fatal("index out of range");
281    return (IntArg) data[i];
282  }
283  void append(IntArg);
284  IntArg *get_data(void) const { return (IntArg *)data; }
285  size_t len(void) const { return num_stored; }
286};
287
288// Characters read from the input queue.
289class Char {
290  int data;
291public:
292  Char(void) : data('\0') {}
293  Char(const int c) : data(c) {}
294  bool operator==(char c) const { return (data == c) ? true : false; }
295  bool operator==(int c) const { return (data == c) ? true : false; }
296  bool operator==(const Char c) const
297		  { return (data == c.data) ? true : false; }
298  bool operator!=(char c) const { return !(*this == c); }
299  bool operator!=(int c) const { return !(*this == c); }
300  bool operator!=(const Char c) const { return !(*this == c); }
301  operator int() const { return (int) data; }
302  operator unsigned char() const { return (unsigned char) data; }
303  operator char() const { return (char) data; }
304};
305
306// Buffer for string arguments (Char, not char).
307class StringBuf {
308  size_t num_allocated;
309  size_t num_stored;
310  Char *data;			// not terminated by '\0'
311public:
312  StringBuf(void);		// allocate without storing
313  ~StringBuf(void);
314  void append(const Char);	// append character to `data'
315  char *make_string(void);	// return new copy of `data' with '\0'
316  bool is_empty(void) {		// true if none stored
317    return (num_stored > 0) ? false : true;
318  }
319  void reset(void);		// set `num_stored' to 0
320};
321
322#ifdef USE_ENV_STACK
323class EnvStack {
324  environment **data;
325  size_t num_allocated;
326  size_t num_stored;
327public:
328  EnvStack(void);
329  ~EnvStack(void);
330  environment *pop(void);
331  void push(environment *e);
332};
333#endif // USE_ENV_STACK
334
335
336/**********************************************************************
337                          external variables
338 **********************************************************************/
339
340// exported as extern by error.h (called from driver.h)
341// needed for error messages (see ../libgroff/error.cpp)
342const char *current_filename = 0; // printable name of the current file
343				  // printable name of current source file
344const char *current_source_filename = 0;
345int current_lineno = 0;		  // current line number of printout
346
347// exported as extern by device.h;
348const char *device = 0;		  // cancel former init with literal
349
350printer *pr;
351
352// Note:
353//
354//   We rely on an implementation of the `new' operator which aborts
355//   gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
356
357
358/**********************************************************************
359                        static local variables
360 **********************************************************************/
361
362FILE *current_file = 0;		// current input stream for parser
363
364// npages: number of pages processed so far (including current page),
365//         _not_ the page number in the printout (can be set with `p').
366int npages = 0;
367
368const ColorArg
369COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
370
371const IntArg
372INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
373
374// parser environment, created and deleted by each run of do_file()
375environment *current_env = 0;
376
377#ifdef USE_ENV_STACK
378const size_t
379envp_size = sizeof(environment *);
380#endif // USE_ENV_STACK
381
382
383/**********************************************************************
384                        function declarations
385 **********************************************************************/
386
387// utility functions
388ColorArg color_from_Df_command(IntArg);
389				// transform old color into new
390void delete_current_env(void);	// delete global var current_env
391void fatal_command(char);	// abort for invalid command
392inline Char get_char(void);	// read next character from input stream
393ColorArg get_color_arg(void);	// read in argument for new color cmds
394IntArray *get_D_fixed_args(const size_t);
395				// read in fixed number of integer
396				// arguments
397IntArray *get_D_fixed_args_odd_dummy(const size_t);
398				// read in a fixed number of integer
399				// arguments plus optional dummy
400IntArray *get_D_variable_args(void);
401                                // variable, even number of int args
402char *get_extended_arg(void);	// argument for `x X' (several lines)
403IntArg get_integer_arg(void);	// read in next integer argument
404IntArray *get_possibly_integer_args();
405				// 0 or more integer arguments
406char *get_string_arg(void);	// read in next string arg, ended by WS
407inline bool is_space_or_tab(const Char);
408				// test on space/tab char
409Char next_arg_begin(void);	// skip white space on current line
410Char next_command(void);	// go to next command, evt. diff. line
411inline bool odd(const int);	// test if integer is odd
412void position_to_end_of_args(const IntArray * const);
413				// positioning after drawing
414void remember_filename(const char *);
415				// set global current_filename
416void remember_source_filename(const char *);
417				// set global current_source_filename
418void send_draw(const Char, const IntArray * const);
419				// call pr->draw
420void skip_line(void);		// unconditionally skip to next line
421bool skip_line_checked(void);	// skip line, false if args are left
422void skip_line_fatal(void);	// skip line, fatal if args are left
423void skip_line_warn(void);	// skip line, warn if args are left
424void skip_line_D(void);		// skip line in D commands
425void skip_line_x(void);		// skip line in x commands
426void skip_to_end_of_line(void);	// skip to the end of the current line
427inline void unget_char(const Char);
428				// restore character onto input
429
430// parser subcommands
431void parse_color_command(color *);
432				// color sub(sub)commands m and DF
433void parse_D_command(void);	// graphical subcommands
434bool parse_x_command(void);	// device controller subcommands
435
436
437/**********************************************************************
438                         class methods
439 **********************************************************************/
440
441#ifdef USE_ENV_STACK
442EnvStack::EnvStack(void)
443{
444  num_allocated = 4;
445  // allocate pointer to array of num_allocated pointers to environment
446  data = (environment **)malloc(envp_size * num_allocated);
447  if (data == 0)
448    fatal("could not allocate environment data");
449  num_stored = 0;
450}
451
452EnvStack::~EnvStack(void)
453{
454  for (size_t i = 0; i < num_stored; i++)
455    delete data[i];
456  free(data);
457}
458
459// return top element from stack and decrease stack pointer
460//
461// the calling function must take care of properly deleting the result
462environment *
463EnvStack::pop(void)
464{
465  num_stored--;
466  environment *result = data[num_stored];
467  data[num_stored] = 0;
468  return result;
469}
470
471// copy argument and push this onto the stack
472void
473EnvStack::push(environment *e)
474{
475  environment *e_copy = new environment;
476  if (num_stored >= num_allocated) {
477    environment **old_data = data;
478    num_allocated *= 2;
479    data = (environment **)malloc(envp_size * num_allocated);
480    if (data == 0)
481      fatal("could not allocate data");
482    for (size_t i = 0; i < num_stored; i++)
483      data[i] = old_data[i];
484    free(old_data);
485  }
486  e_copy->col = new color;
487  e_copy->fill = new color;
488  *e_copy->col = *e->col;
489  *e_copy->fill = *e->fill;
490  e_copy->fontno = e->fontno;
491  e_copy->height = e->height;
492  e_copy->hpos = e->hpos;
493  e_copy->size = e->size;
494  e_copy->slant = e->slant;
495  e_copy->vpos = e->vpos;
496  data[num_stored] = e_copy;
497  num_stored++;
498}
499#endif // USE_ENV_STACK
500
501IntArray::IntArray(void)
502{
503  num_allocated = 4;
504  data = new IntArg[num_allocated];
505  num_stored = 0;
506}
507
508IntArray::IntArray(const size_t n)
509{
510  if (n <= 0)
511    fatal("number of integers to be allocated must be > 0");
512  num_allocated = n;
513  data = new IntArg[num_allocated];
514  num_stored = 0;
515}
516
517IntArray::~IntArray(void)
518{
519  a_delete data;
520}
521
522void
523IntArray::append(IntArg x)
524{
525  if (num_stored >= num_allocated) {
526    IntArg *old_data = data;
527    num_allocated *= 2;
528    data = new IntArg[num_allocated];
529    for (size_t i = 0; i < num_stored; i++)
530      data[i] = old_data[i];
531    a_delete old_data;
532  }
533  data[num_stored] = x;
534  num_stored++;
535}
536
537StringBuf::StringBuf(void)
538{
539  num_stored = 0;
540  num_allocated = 128;
541  data = new Char[num_allocated];
542}
543
544StringBuf::~StringBuf(void)
545{
546  a_delete data;
547}
548
549void
550StringBuf::append(const Char c)
551{
552  if (num_stored >= num_allocated) {
553    Char *old_data = data;
554    num_allocated *= 2;
555    data = new Char[num_allocated];
556    for (size_t i = 0; i < num_stored; i++)
557      data[i] = old_data[i];
558    a_delete old_data;
559  }
560  data[num_stored] = c;
561  num_stored++;
562}
563
564char *
565StringBuf::make_string(void)
566{
567  char *result = new char[num_stored + 1];
568  for (size_t i = 0; i < num_stored; i++)
569    result[i] = (char) data[i];
570  result[num_stored] = '\0';
571  return result;
572}
573
574void
575StringBuf::reset(void)
576{
577  num_stored = 0;
578}
579
580/**********************************************************************
581                        utility functions
582 **********************************************************************/
583
584//////////////////////////////////////////////////////////////////////
585/* color_from_Df_command:
586   Process the gray shade setting command Df.
587
588   Transform Df style color into DF style color.
589   Df color: 0-1000, 0 is white
590   DF color: 0-65536, 0 is black
591
592   The Df command is obsoleted by command DFg, but kept for
593   compatibility.
594*/
595ColorArg
596color_from_Df_command(IntArg Df_gray)
597{
598  return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
599}
600
601//////////////////////////////////////////////////////////////////////
602/* delete_current_env():
603   Delete global variable current_env and its pointer members.
604
605   This should be a class method of environment.
606*/
607void delete_current_env(void)
608{
609  delete current_env->col;
610  delete current_env->fill;
611  delete current_env;
612  current_env = 0;
613}
614
615//////////////////////////////////////////////////////////////////////
616/* fatal_command():
617   Emit error message about invalid command and abort.
618*/
619void
620fatal_command(char command)
621{
622  fatal("`%1' command invalid before first `p' command", command);
623}
624
625//////////////////////////////////////////////////////////////////////
626/* get_char():
627   Retrieve the next character from the input queue.
628
629   Return: The retrieved character (incl. EOF), converted to Char.
630*/
631inline Char
632get_char(void)
633{
634  return (Char) getc(current_file);
635}
636
637//////////////////////////////////////////////////////////////////////
638/* get_color_arg():
639   Retrieve an argument suitable for the color commands m and DF.
640
641   Return: The retrieved color argument.
642*/
643ColorArg
644get_color_arg(void)
645{
646  IntArg x = get_integer_arg();
647  if (x < 0 || x > (IntArg)COLORARG_MAX) {
648    error("color component argument out of range");
649    x = 0;
650  }
651  return (ColorArg) x;
652}
653
654//////////////////////////////////////////////////////////////////////
655/* get_D_fixed_args():
656   Get a fixed number of integer arguments for D commands.
657
658   Fatal if wrong number of arguments.
659   Too many arguments on the line raise a warning.
660   A line skip is done.
661
662   number: In-parameter, the number of arguments to be retrieved.
663   ignore: In-parameter, ignore next argument -- GNU troff always emits
664           pairs of parameters for `D' extensions added by groff.
665           Default is `false'.
666
667   Return: New IntArray containing the arguments.
668*/
669IntArray *
670get_D_fixed_args(const size_t number)
671{
672  if (number <= 0)
673    fatal("requested number of arguments must be > 0");
674  IntArray *args = new IntArray(number);
675  for (size_t i = 0; i < number; i++)
676    args->append(get_integer_arg());
677  skip_line_D();
678  return args;
679}
680
681//////////////////////////////////////////////////////////////////////
682/* get_D_fixed_args_odd_dummy():
683   Get a fixed number of integer arguments for D commands and optionally
684   ignore a dummy integer argument if the requested number is odd.
685
686   The gtroff program adds a dummy argument to some commands to get
687   an even number of arguments.
688   Error if the number of arguments differs from the scheme above.
689   A line skip is done.
690
691   number: In-parameter, the number of arguments to be retrieved.
692
693   Return: New IntArray containing the arguments.
694*/
695IntArray *
696get_D_fixed_args_odd_dummy(const size_t number)
697{
698  if (number <= 0)
699    fatal("requested number of arguments must be > 0");
700  IntArray *args = new IntArray(number);
701  for (size_t i = 0; i < number; i++)
702    args->append(get_integer_arg());
703  if (odd(number)) {
704    IntArray *a = get_possibly_integer_args();
705    if (a->len() > 1)
706      error("too many arguments");
707    delete a;
708  }
709  skip_line_D();
710  return args;
711}
712
713//////////////////////////////////////////////////////////////////////
714/* get_D_variable_args():
715   Get a variable even number of integer arguments for D commands.
716
717   Get as many integer arguments as possible from the rest of the
718   current line.
719   - The arguments are separated by an arbitrary sequence of space or
720     tab characters.
721   - A comment, a newline, or EOF indicates the end of processing.
722   - Error on non-digit characters different from these.
723   - A final line skip is performed (except for EOF).
724
725   Return: New IntArray of the retrieved arguments.
726*/
727IntArray *
728get_D_variable_args()
729{
730  IntArray *args = get_possibly_integer_args();
731  size_t n = args->len();
732  if (n <= 0)
733    error("no arguments found");
734  if (odd(n))
735    error("even number of arguments expected");
736  skip_line_D();
737  return args;
738}
739
740//////////////////////////////////////////////////////////////////////
741/* get_extended_arg():
742   Retrieve extended arg for `x X' command.
743
744   - Skip leading spaces and tabs, error on EOL or newline.
745   - Return everything before the next NL or EOF ('#' is not a comment);
746     as long as the following line starts with '+' this is returned
747     as well, with the '+' replaced by a newline.
748   - Final line skip is always performed.
749
750   Return: Allocated (new) string of retrieved text argument.
751*/
752char *
753get_extended_arg(void)
754{
755  StringBuf buf = StringBuf();
756  Char c = next_arg_begin();
757  while ((int) c != EOF) {
758    if ((int) c == '\n') {
759      current_lineno++;
760      c = get_char();
761      if ((int) c == '+')
762	buf.append((Char) '\n');
763      else {
764	unget_char(c);		// first character of next line
765	break;
766      }
767    }
768    else
769      buf.append(c);
770    c = get_char();
771  }
772  return buf.make_string();
773}
774
775//////////////////////////////////////////////////////////////////////
776/* get_integer_arg(): Retrieve integer argument.
777
778   Skip leading spaces and tabs, collect an optional '-' and all
779   following decimal digits (at least one) up to the next non-digit,
780   which is restored onto the input queue.
781
782   Fatal error on all other situations.
783
784   Return: Retrieved integer.
785*/
786IntArg
787get_integer_arg(void)
788{
789  StringBuf buf = StringBuf();
790  Char c = next_arg_begin();
791  if ((int) c == '-') {
792    buf.append(c);
793    c = get_char();
794  }
795  if (!isdigit((int) c))
796    error("integer argument expected");
797  while (isdigit((int) c)) {
798    buf.append(c);
799    c = get_char();
800  }
801  // c is not a digit
802  unget_char(c);
803  char *s = buf.make_string();
804  errno = 0;
805  long int number = strtol(s, 0, 10);
806  if (errno != 0
807      || number > INTARG_MAX || number < -INTARG_MAX) {
808    error("integer argument too large");
809    number = 0;
810  }
811  a_delete s;
812  return (IntArg) number;
813}
814
815//////////////////////////////////////////////////////////////////////
816/* get_possibly_integer_args():
817   Parse the rest of the input line as a list of integer arguments.
818
819   Get as many integer arguments as possible from the rest of the
820   current line, even none.
821   - The arguments are separated by an arbitrary sequence of space or
822     tab characters.
823   - A comment, a newline, or EOF indicates the end of processing.
824   - Error on non-digit characters different from these.
825   - No line skip is performed.
826
827   Return: New IntArray of the retrieved arguments.
828*/
829IntArray *
830get_possibly_integer_args()
831{
832  bool done = false;
833  StringBuf buf = StringBuf();
834  Char c = get_char();
835  IntArray *args = new IntArray();
836  while (!done) {
837    buf.reset();
838    while (is_space_or_tab(c))
839      c = get_char();
840    if (c == '-') {
841      Char c1 = get_char();
842      if (isdigit((int) c1)) {
843	buf.append(c);
844	c = c1;
845      }
846      else
847	unget_char(c1);
848    }
849    while (isdigit((int) c)) {
850      buf.append(c);
851      c = get_char();
852    }
853    if (!buf.is_empty()) {
854      char *s = buf.make_string();
855      errno = 0;
856      long int x = strtol(s, 0, 10);
857      if (errno
858	  || x > INTARG_MAX || x < -INTARG_MAX) {
859	error("invalid integer argument, set to 0");
860	x = 0;
861      }
862      args->append((IntArg) x);
863      a_delete s;
864    }
865    // Here, c is not a digit.
866    // Terminate on comment, end of line, or end of file, while
867    // space or tab indicate continuation; otherwise error.
868    switch((int) c) {
869    case '#':
870      skip_to_end_of_line();
871      done = true;
872      break;
873    case '\n':
874      done = true;
875      unget_char(c);
876      break;
877    case EOF:
878      done = true;
879      break;
880    case ' ':
881    case '\t':
882      break;
883    default:
884      error("integer argument expected");
885      break;
886    }
887  }
888  return args;
889}
890
891//////////////////////////////////////////////////////////////////////
892/* get_string_arg():
893   Retrieve string arg.
894
895   - Skip leading spaces and tabs; error on EOL or newline.
896   - Return all following characters before the next space, tab,
897     newline, or EOF character (in-word '#' is not a comment character).
898   - The terminating space, tab, newline, or EOF character is restored
899     onto the input queue, so no line skip.
900
901   Return: Retrieved string as char *, allocated by 'new'.
902*/
903char *
904get_string_arg(void)
905{
906  StringBuf buf = StringBuf();
907  Char c = next_arg_begin();
908  while (!is_space_or_tab(c)
909	 && c != Char('\n') && c != Char(EOF)) {
910    buf.append(c);
911    c = get_char();
912  }
913  unget_char(c);		// restore white space
914  return buf.make_string();
915}
916
917//////////////////////////////////////////////////////////////////////
918/* is_space_or_tab():
919   Test a character if it is a space or tab.
920
921   c: In-parameter, character to be tested.
922
923   Return: True, if c is a space or tab character, false otherwise.
924*/
925inline bool
926is_space_or_tab(const Char c)
927{
928  return (c == Char(' ') || c == Char('\t')) ? true : false;
929}
930
931//////////////////////////////////////////////////////////////////////
932/* next_arg_begin():
933   Return first character of next argument.
934
935   Skip space and tab characters; error on newline or EOF.
936
937   Return: The first character different from these (including '#').
938*/
939Char
940next_arg_begin(void)
941{
942  Char c;
943  while (1) {
944    c = get_char();
945    switch ((int) c) {
946    case ' ':
947    case '\t':
948      break;
949    case '\n':
950    case EOF:
951      error("missing argument");
952      break;
953    default:			// first essential character
954      return c;
955    }
956  }
957}
958
959//////////////////////////////////////////////////////////////////////
960/* next_command():
961   Find the first character of the next command.
962
963   Skip spaces, tabs, comments (introduced by #), and newlines.
964
965   Return: The first character different from these (including EOF).
966*/
967Char
968next_command(void)
969{
970  Char c;
971  while (1) {
972    c = get_char();
973    switch ((int) c) {
974    case ' ':
975    case '\t':
976      break;
977    case '\n':
978      current_lineno++;
979      break;
980    case '#':			// comment
981      skip_line();
982      break;
983    default:			// EOF or first essential character
984      return c;
985    }
986  }
987}
988
989//////////////////////////////////////////////////////////////////////
990/* odd():
991   Test whether argument is an odd number.
992
993   n: In-parameter, the integer to be tested.
994
995   Return: True if odd, false otherwise.
996*/
997inline bool
998odd(const int n)
999{
1000  return (n & 1) ? true : false;
1001}
1002
1003//////////////////////////////////////////////////////////////////////
1004/* position_to_end_of_args():
1005   Move graphical pointer to end of drawn figure.
1006
1007   This is used by the D commands that draw open geometrical figures.
1008   The algorithm simply sums up all horizontal displacements (arguments
1009   with even number) for the horizontal component.  Similarly, the
1010   vertical component is the sum of the odd arguments.
1011
1012   args: In-parameter, the arguments of a former drawing command.
1013*/
1014void
1015position_to_end_of_args(const IntArray * const args)
1016{
1017  size_t i;
1018  const size_t n = args->len();
1019  for (i = 0; i < n; i += 2)
1020    current_env->hpos += (*args)[i];
1021  for (i = 1; i < n; i += 2)
1022    current_env->vpos += (*args)[i];
1023}
1024
1025//////////////////////////////////////////////////////////////////////
1026/* remember_filename():
1027   Set global variable current_filename.
1028
1029   The actual filename is stored in current_filename.  This is used by
1030   the postprocessors, expecting the name "<standard input>" for stdin.
1031
1032   filename: In-out-parameter; is changed to the new value also.
1033*/
1034void
1035remember_filename(const char *filename)
1036{
1037  char *fname;
1038  if (strcmp(filename, "-") == 0)
1039    fname = (char *)"<standard input>";
1040  else
1041    fname = (char *)filename;
1042  size_t len = strlen(fname) + 1;
1043  if (current_filename != 0)
1044    free((char *)current_filename);
1045  current_filename = (const char *)malloc(len);
1046  if (current_filename == 0)
1047    fatal("can't malloc space for filename");
1048  strncpy((char *)current_filename, (char *)fname, len);
1049}
1050
1051//////////////////////////////////////////////////////////////////////
1052/* remember_source_filename():
1053   Set global variable current_source_filename.
1054
1055   The actual filename is stored in current_filename.  This is used by
1056   the postprocessors, expecting the name "<standard input>" for stdin.
1057
1058   filename: In-out-parameter; is changed to the new value also.
1059*/
1060void
1061remember_source_filename(const char *filename)
1062{
1063  char *fname;
1064  if (strcmp(filename, "-") == 0)
1065    fname = (char *)"<standard input>";
1066  else
1067    fname = (char *)filename;
1068  size_t len = strlen(fname) + 1;
1069  if (current_source_filename != 0)
1070    free((char *)current_source_filename);
1071  current_source_filename = (const char *)malloc(len);
1072  if (current_source_filename == 0)
1073    fatal("can't malloc space for filename");
1074  strncpy((char *)current_source_filename, (char *)fname, len);
1075}
1076
1077//////////////////////////////////////////////////////////////////////
1078/* send_draw():
1079   Call draw method of printer class.
1080
1081   subcmd: Letter of actual D subcommand.
1082   args: Array of integer arguments of actual D subcommand.
1083*/
1084void
1085send_draw(const Char subcmd, const IntArray * const args)
1086{
1087  EnvInt n = (EnvInt) args->len();
1088  pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
1089}
1090
1091//////////////////////////////////////////////////////////////////////
1092/* skip_line():
1093   Go to next line within the input queue.
1094
1095   Skip the rest of the current line, including the newline character.
1096   The global variable current_lineno is adjusted.
1097   No errors are raised.
1098*/
1099void
1100skip_line(void)
1101{
1102  Char c = get_char();
1103  while (1) {
1104    if (c == '\n') {
1105      current_lineno++;
1106      break;
1107    }
1108    if (c == EOF)
1109      break;
1110    c = get_char();
1111  }
1112}
1113
1114//////////////////////////////////////////////////////////////////////
1115/* skip_line_checked ():
1116   Check that there aren't any arguments left on the rest of the line,
1117   then skip line.
1118
1119   Spaces, tabs, and a comment are allowed before newline or EOF.
1120   All other characters raise an error.
1121*/
1122bool
1123skip_line_checked(void)
1124{
1125  bool ok = true;
1126  Char c = get_char();
1127  while (is_space_or_tab(c))
1128    c = get_char();
1129  switch((int) c) {
1130  case '#':			// comment
1131    skip_line();
1132    break;
1133  case '\n':
1134    current_lineno++;
1135    break;
1136  case EOF:
1137    break;
1138  default:
1139    ok = false;
1140    skip_line();
1141    break;
1142  }
1143  return ok;
1144}
1145
1146//////////////////////////////////////////////////////////////////////
1147/* skip_line_fatal ():
1148   Fatal error if arguments left, otherwise skip line.
1149
1150   Spaces, tabs, and a comment are allowed before newline or EOF.
1151   All other characters trigger the error.
1152*/
1153void
1154skip_line_fatal(void)
1155{
1156  bool ok = skip_line_checked();
1157  if (!ok) {
1158    current_lineno--;
1159    error("too many arguments");
1160    current_lineno++;
1161  }
1162}
1163
1164//////////////////////////////////////////////////////////////////////
1165/* skip_line_warn ():
1166   Skip line, but warn if arguments are left on actual line.
1167
1168   Spaces, tabs, and a comment are allowed before newline or EOF.
1169   All other characters raise a warning
1170*/
1171void
1172skip_line_warn(void)
1173{
1174  bool ok = skip_line_checked();
1175  if (!ok) {
1176    current_lineno--;
1177    warning("too many arguments on current line");
1178    current_lineno++;
1179  }
1180}
1181
1182//////////////////////////////////////////////////////////////////////
1183/* skip_line_D ():
1184   Skip line in `D' commands.
1185
1186   Decide whether in case of an additional argument a fatal error is
1187   raised (the documented classical behavior), only a warning is
1188   issued, or the line is just skipped (former groff behavior).
1189   Actually decided for the warning.
1190*/
1191void
1192skip_line_D(void)
1193{
1194  skip_line_warn();
1195  // or: skip_line_fatal();
1196  // or: skip_line();
1197}
1198
1199//////////////////////////////////////////////////////////////////////
1200/* skip_line_x ():
1201   Skip line in `x' commands.
1202
1203   Decide whether in case of an additional argument a fatal error is
1204   raised (the documented classical behavior), only a warning is
1205   issued, or the line is just skipped (former groff behavior).
1206   Actually decided for the warning.
1207*/
1208void
1209skip_line_x(void)
1210{
1211  skip_line_warn();
1212  // or: skip_line_fatal();
1213  // or: skip_line();
1214}
1215
1216//////////////////////////////////////////////////////////////////////
1217/* skip_to_end_of_line():
1218   Go to the end of the current line.
1219
1220   Skip the rest of the current line, excluding the newline character.
1221   The global variable current_lineno is not changed.
1222   No errors are raised.
1223*/
1224void
1225skip_to_end_of_line(void)
1226{
1227  Char c = get_char();
1228  while (1) {
1229    if (c == '\n') {
1230      unget_char(c);
1231      return;
1232    }
1233    if (c == EOF)
1234      return;
1235    c = get_char();
1236  }
1237}
1238
1239//////////////////////////////////////////////////////////////////////
1240/* unget_char(c):
1241   Restore character c onto input queue.
1242
1243   Write a character back onto the input stream.
1244   EOF is gracefully handled.
1245
1246   c: In-parameter; character to be pushed onto the input queue.
1247*/
1248inline void
1249unget_char(const Char c)
1250{
1251  if (c != EOF) {
1252    int ch = (int) c;
1253    if (ungetc(ch, current_file) == EOF)
1254      fatal("could not unget character");
1255  }
1256}
1257
1258
1259/**********************************************************************
1260                       parser subcommands
1261 **********************************************************************/
1262
1263//////////////////////////////////////////////////////////////////////
1264/* parse_color_command:
1265   Process the commands m and DF, but not Df.
1266
1267   col: In-out-parameter; the color object to be set, must have
1268        been initialized before.
1269*/
1270void
1271parse_color_command(color *col)
1272{
1273  ColorArg gray = 0;
1274  ColorArg red = 0, green = 0, blue = 0;
1275  ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
1276  Char subcmd = next_arg_begin();
1277  switch((int) subcmd) {
1278  case 'c':			// DFc or mc: CMY
1279    cyan = get_color_arg();
1280    magenta = get_color_arg();
1281    yellow = get_color_arg();
1282    col->set_cmy(cyan, magenta, yellow);
1283    break;
1284  case 'd':			// DFd or md: set default color
1285    col->set_default();
1286    break;
1287  case 'g':			// DFg or mg: gray
1288    gray = get_color_arg();
1289    col->set_gray(gray);
1290    break;
1291  case 'k':			// DFk or mk: CMYK
1292    cyan = get_color_arg();
1293    magenta = get_color_arg();
1294    yellow = get_color_arg();
1295    black = get_color_arg();
1296    col->set_cmyk(cyan, magenta, yellow, black);
1297    break;
1298  case 'r':			// DFr or mr: RGB
1299    red = get_color_arg();
1300    green = get_color_arg();
1301    blue = get_color_arg();
1302    col->set_rgb(red, green, blue);
1303    break;
1304  default:
1305    error("invalid color scheme `%1'", (int) subcmd);
1306    break;
1307  } // end of color subcommands
1308}
1309
1310//////////////////////////////////////////////////////////////////////
1311/* parse_D_command():
1312   Parse the subcommands of graphical command D.
1313
1314   This is the part of the do_file() parser that scans the graphical
1315   subcommands.
1316   - Error on lacking or wrong arguments.
1317   - Warning on too many arguments.
1318   - Line is always skipped.
1319*/
1320void
1321parse_D_command()
1322{
1323  Char subcmd = next_arg_begin();
1324  switch((int) subcmd) {
1325  case '~':			// D~: draw B-spline
1326    // actually, this isn't available for some postprocessors
1327    // fall through
1328  default:			// unknown options are passed to device
1329    {
1330      IntArray *args = get_D_variable_args();
1331      send_draw(subcmd, args);
1332      position_to_end_of_args(args);
1333      delete args;
1334      break;
1335    }
1336  case 'a':			// Da: draw arc
1337    {
1338      IntArray *args = get_D_fixed_args(4);
1339      send_draw(subcmd, args);
1340      position_to_end_of_args(args);
1341      delete args;
1342      break;
1343    }
1344  case 'c':			// Dc: draw circle line
1345    {
1346      IntArray *args = get_D_fixed_args(1);
1347      send_draw(subcmd, args);
1348      // move to right end
1349      current_env->hpos += (*args)[0];
1350      delete args;
1351      break;
1352    }
1353  case 'C':			// DC: draw solid circle
1354    {
1355      IntArray *args = get_D_fixed_args_odd_dummy(1);
1356      send_draw(subcmd, args);
1357      // move to right end
1358      current_env->hpos += (*args)[0];
1359      delete args;
1360      break;
1361    }
1362  case 'e':			// De: draw ellipse line
1363  case 'E':			// DE: draw solid ellipse
1364    {
1365      IntArray *args = get_D_fixed_args(2);
1366      send_draw(subcmd, args);
1367      // move to right end
1368      current_env->hpos += (*args)[0];
1369      delete args;
1370      break;
1371    }
1372  case 'f':			// Df: set fill gray; obsoleted by DFg
1373    {
1374      IntArg arg = get_integer_arg();
1375      if ((arg >= 0) && (arg <= 1000)) {
1376	// convert arg and treat it like DFg
1377	ColorArg gray = color_from_Df_command(arg);
1378        current_env->fill->set_gray(gray);
1379      }
1380      else {
1381	// set fill color to the same value as the current outline color
1382	delete current_env->fill;
1383	current_env->fill = new color(current_env->col);
1384      }
1385      pr->change_fill_color(current_env);
1386      // skip unused `vertical' component (\D'...' always emits pairs)
1387      (void) get_integer_arg();
1388#   ifdef STUPID_DRAWING_POSITIONING
1389      current_env->hpos += arg;
1390#   endif
1391      skip_line_x();
1392      break;
1393    }
1394  case 'F':			// DF: set fill color, several formats
1395    parse_color_command(current_env->fill);
1396    pr->change_fill_color(current_env);
1397    // no positioning (setting-only command)
1398    skip_line_x();
1399    break;
1400  case 'l':			// Dl: draw line
1401    {
1402      IntArray *args = get_D_fixed_args(2);
1403      send_draw(subcmd, args);
1404      position_to_end_of_args(args);
1405      delete args;
1406      break;
1407    }
1408  case 'p':			// Dp: draw closed polygon line
1409  case 'P':			// DP: draw solid closed polygon
1410    {
1411      IntArray *args = get_D_variable_args();
1412      send_draw(subcmd, args);
1413#   ifdef STUPID_DRAWING_POSITIONING
1414      // final args positioning
1415      position_to_end_of_args(args);
1416#   endif
1417      delete args;
1418      break;
1419    }
1420  case 't':			// Dt: set line thickness
1421    {
1422      IntArray *args = get_D_fixed_args_odd_dummy(1);
1423      send_draw(subcmd, args);
1424#   ifdef STUPID_DRAWING_POSITIONING
1425      // final args positioning
1426      position_to_end_of_args(args);
1427#   endif
1428      delete args;
1429      break;
1430    }
1431  } // end of D subcommands
1432}
1433
1434//////////////////////////////////////////////////////////////////////
1435/* parse_x_command():
1436   Parse subcommands of the device control command x.
1437
1438   This is the part of the do_file() parser that scans the device
1439   controlling commands.
1440   - Error on duplicate prologue commands.
1441   - Error on wrong or lacking arguments.
1442   - Warning on too many arguments.
1443   - Line is always skipped.
1444
1445   Globals:
1446   - current_env: is set by many subcommands.
1447   - npages: page counting variable
1448
1449   Return: boolean in the meaning of `stopped'
1450           - true if parsing should be stopped (`x stop').
1451           - false if parsing should continue.
1452*/
1453bool
1454parse_x_command(void)
1455{
1456  bool stopped = false;
1457  char *subcmd_str = get_string_arg();
1458  char subcmd = subcmd_str[0];
1459  switch (subcmd) {
1460  case 'f':			// x font: mount font
1461    {
1462      IntArg n = get_integer_arg();
1463      char *name = get_string_arg();
1464      pr->load_font(n, name);
1465      a_delete name;
1466      skip_line_x();
1467      break;
1468    }
1469  case 'F':			// x Filename: set filename for errors
1470    {
1471      char *str_arg = get_string_arg();
1472      if (str_arg == 0)
1473	warning("empty argument for `x F' command");
1474      else {
1475	remember_source_filename(str_arg);
1476	a_delete str_arg;
1477      }
1478      break;
1479    }
1480  case 'H':			// x Height: set character height
1481    current_env->height = get_integer_arg();
1482    if (current_env->height == current_env->size)
1483      current_env->height = 0;
1484    skip_line_x();
1485    break;
1486  case 'i':			// x init: initialize device
1487    error("duplicate `x init' command");
1488    skip_line_x();
1489    break;
1490  case 'p':			// x pause: pause device
1491    skip_line_x();
1492    break;
1493  case 'r':			// x res: set resolution
1494    error("duplicate `x res' command");
1495    skip_line_x();
1496    break;
1497  case 's':			// x stop: stop device
1498    stopped = true;
1499    skip_line_x();
1500    break;
1501  case 'S':			// x Slant: set slant
1502    current_env->slant = get_integer_arg();
1503    skip_line_x();
1504    break;
1505  case 't':			// x trailer: generate trailer info
1506    skip_line_x();
1507    break;
1508  case 'T':			// x Typesetter: set typesetter
1509    error("duplicate `x T' command");
1510    skip_line();
1511    break;
1512  case 'u':			// x underline: from .cu
1513    {
1514      char *str_arg = get_string_arg();
1515      pr->special(str_arg, current_env, 'u');
1516      a_delete str_arg;
1517      skip_line_x();
1518      break;
1519    }
1520  case 'X':			// x X: send uninterpretedly to device
1521    {
1522      char *str_arg = get_extended_arg(); // includes line skip
1523      if (npages <= 0)
1524	error("`x X' command invalid before first `p' command");
1525      else if (str_arg && (strncmp(str_arg, "devtag:",
1526				   strlen("devtag:")) == 0))
1527	pr->devtag(str_arg, current_env);
1528      else
1529	pr->special(str_arg, current_env);
1530      a_delete str_arg;
1531      break;
1532    }
1533  default:			// ignore unknown x commands, but warn
1534    warning("unknown command `x %1'", subcmd);
1535    skip_line();
1536  }
1537  a_delete subcmd_str;
1538  return stopped;
1539}
1540
1541
1542/**********************************************************************
1543                     exported part (by driver.h)
1544 **********************************************************************/
1545
1546////////////////////////////////////////////////////////////////////////
1547/* do_file():
1548   Parse and postprocess groff intermediate output.
1549
1550   filename: "-" for standard input, normal file name otherwise
1551*/
1552void
1553do_file(const char *filename)
1554{
1555  Char command;
1556  bool stopped = false;		// terminating condition
1557
1558#ifdef USE_ENV_STACK
1559  EnvStack env_stack = EnvStack();
1560#endif // USE_ENV_STACK
1561
1562  // setup of global variables
1563  npages = 0;
1564  current_lineno = 1;
1565  // `pr' is initialized after the prologue.
1566  // `device' is set by the 1st prologue command.
1567
1568  if (filename[0] == '-' && filename[1] == '\0')
1569    current_file = stdin;
1570  else {
1571    errno = 0;
1572    current_file = fopen(filename, "r");
1573    if (errno != 0 || current_file == 0) {
1574      error("can't open file `%1'", filename);
1575      return;
1576    }
1577  }
1578  remember_filename(filename);
1579
1580  if (current_env != 0)
1581    delete_current_env();
1582  current_env = new environment;
1583  current_env->col = new color;
1584  current_env->fill = new color;
1585  current_env->fontno = -1;
1586  current_env->height = 0;
1587  current_env->hpos = -1;
1588  current_env->slant = 0;
1589  current_env->size = 0;
1590  current_env->vpos = -1;
1591
1592  // parsing of prologue (first 3 commands)
1593  {
1594    char *str_arg;
1595    IntArg int_arg;
1596
1597    // 1st command `x T'
1598    command = next_command();
1599    if ((int) command == EOF)
1600      return;
1601    if ((int) command != 'x')
1602      fatal("the first command must be `x T'");
1603    str_arg = get_string_arg();
1604    if (str_arg[0] != 'T')
1605      fatal("the first command must be `x T'");
1606    a_delete str_arg;
1607    char *tmp_dev = get_string_arg();
1608    if (pr == 0) {		// note: `pr' initialized after prologue
1609      device = tmp_dev;
1610      if (!font::load_desc())
1611	fatal("couldn't load DESC file, can't continue");
1612    }
1613    else {
1614      if (device == 0 || strcmp(device, tmp_dev) != 0)
1615	fatal("all files must use the same device");
1616      a_delete tmp_dev;
1617    }
1618    skip_line_x();		// ignore further arguments
1619    current_env->size = 10 * font::sizescale;
1620
1621    // 2nd command `x res'
1622    command = next_command();
1623    if ((int) command != 'x')
1624      fatal("the second command must be `x res'");
1625    str_arg = get_string_arg();
1626    if (str_arg[0] != 'r')
1627      fatal("the second command must be `x res'");
1628    a_delete str_arg;
1629    int_arg = get_integer_arg();
1630    EnvInt font_res = font::res;
1631    if (int_arg != font_res)
1632      fatal("resolution does not match");
1633    int_arg = get_integer_arg();
1634    if (int_arg != font::hor)
1635      fatal("minimum horizontal motion does not match");
1636    int_arg = get_integer_arg();
1637    if (int_arg != font::vert)
1638      fatal("minimum vertical motion does not match");
1639    skip_line_x();		// ignore further arguments
1640
1641    // 3rd command `x init'
1642    command = next_command();
1643    if (command != 'x')
1644      fatal("the third command must be `x init'");
1645    str_arg = get_string_arg();
1646    if (str_arg[0] != 'i')
1647      fatal("the third command must be `x init'");
1648    a_delete str_arg;
1649    skip_line_x();
1650  }
1651
1652  // parsing of body
1653  if (pr == 0)
1654    pr = make_printer();
1655  while (!stopped) {
1656    command = next_command();
1657    if (command == EOF)
1658      break;
1659    // spaces, tabs, comments, and newlines are skipped here
1660    switch ((int) command) {
1661    case '#':			// #: comment, ignore up to end of line
1662      skip_line();
1663      break;
1664#ifdef USE_ENV_STACK
1665    case '{':			// {: start a new environment (a copy)
1666      env_stack.push(current_env);
1667      break;
1668    case '}':			// }: pop previous env from stack
1669      delete_current_env();
1670      current_env = env_stack.pop();
1671      break;
1672#endif // USE_ENV_STACK
1673    case '0':			// ddc: obsolete jump and print command
1674    case '1':
1675    case '2':
1676    case '3':
1677    case '4':
1678    case '5':
1679    case '6':
1680    case '7':
1681    case '8':
1682    case '9':
1683      {				// expect 2 digits and a character
1684	char s[3];
1685	Char c = next_arg_begin();
1686	if (npages <= 0)
1687	  fatal_command(command);
1688	if (!isdigit((int) c)) {
1689	  error("digit expected");
1690	  c = 0;
1691	}
1692	s[0] = (char) command;
1693	s[1] = (char) c;
1694	s[2] = '\0';
1695	errno = 0;
1696	long int x = strtol(s, 0, 10);
1697	if (errno != 0)
1698	  error("couldn't convert 2 digits");
1699	EnvInt hor_pos = (EnvInt) x;
1700	current_env->hpos += hor_pos;
1701	c = next_arg_begin();
1702	if ((int) c == '\n' || (int) c == EOF)
1703	  error("character argument expected");
1704	else
1705	  pr->set_ascii_char((unsigned char) c, current_env);
1706	break;
1707      }
1708    case 'c':			// c: print ascii char without moving
1709      {
1710	if (npages <= 0)
1711	  fatal_command(command);
1712	Char c = next_arg_begin();
1713	if (c == '\n' || c == EOF)
1714	  error("missing argument to `c' command");
1715	else
1716	  pr->set_ascii_char((unsigned char) c, current_env);
1717	break;
1718      }
1719    case 'C':			// C: print named special character
1720      {
1721	if (npages <= 0)
1722	  fatal_command(command);
1723	char *str_arg = get_string_arg();
1724	pr->set_special_char(str_arg, current_env);
1725	a_delete str_arg;
1726	break;
1727      }
1728    case 'D':			// drawing commands
1729      if (npages <= 0)
1730	fatal_command(command);
1731      parse_D_command();
1732      break;
1733    case 'f':			// f: set font to number
1734      current_env->fontno = get_integer_arg();
1735      break;
1736    case 'F':			// F: obsolete, replaced by `x F'
1737      {
1738	char *str_arg = get_string_arg();
1739	remember_source_filename(str_arg);
1740	a_delete str_arg;
1741	break;
1742      }
1743    case 'h':			// h: relative horizontal move
1744      current_env->hpos += (EnvInt) get_integer_arg();
1745      break;
1746    case 'H':			// H: absolute horizontal positioning
1747      current_env->hpos = (EnvInt) get_integer_arg();
1748      break;
1749    case 'm':			// m: glyph color
1750      parse_color_command(current_env->col);
1751      pr->change_color(current_env);
1752      break;
1753    case 'n':			// n: print end of line
1754				// ignore two arguments (historically)
1755      if (npages <= 0)
1756	fatal_command(command);
1757      pr->end_of_line();
1758      (void) get_integer_arg();
1759      (void) get_integer_arg();
1760      break;
1761    case 'N':			// N: print char with given int code
1762      if (npages <= 0)
1763	fatal_command(command);
1764      pr->set_numbered_char(get_integer_arg(), current_env);
1765      break;
1766    case 'p':			// p: start new page with given number
1767      if (npages > 0)
1768	pr->end_page(current_env->vpos);
1769      npages++;			// increment # of processed pages
1770      pr->begin_page(get_integer_arg());
1771      current_env->vpos = 0;
1772      break;
1773    case 's':			// s: set point size
1774      current_env->size = get_integer_arg();
1775      if (current_env->height == current_env->size)
1776	current_env->height = 0;
1777      break;
1778    case 't':			// t: print a text word
1779      {
1780	char c;
1781	if (npages <= 0)
1782	  fatal_command(command);
1783	char *str_arg = get_string_arg();
1784	size_t i = 0;
1785	while ((c = str_arg[i++]) != '\0') {
1786	  EnvInt w;
1787	  pr->set_ascii_char((unsigned char) c, current_env, &w);
1788	  current_env->hpos += w;
1789	}
1790	a_delete str_arg;
1791	break;
1792      }
1793    case 'u':			// u: print spaced word
1794      {
1795	char c;
1796	if (npages <= 0)
1797	  fatal_command(command);
1798	EnvInt kern = (EnvInt) get_integer_arg();
1799	char *str_arg = get_string_arg();
1800	size_t i = 0;
1801	while ((c = str_arg[i++]) != '\0') {
1802	  EnvInt w;
1803	  pr->set_ascii_char((unsigned char) c, current_env, &w);
1804	  current_env->hpos += w + kern;
1805	}
1806	a_delete str_arg;
1807	break;
1808      }
1809    case 'v':			// v: relative vertical move
1810      current_env->vpos += (EnvInt) get_integer_arg();
1811      break;
1812    case 'V':			// V: absolute vertical positioning
1813      current_env->vpos = (EnvInt) get_integer_arg();
1814      break;
1815    case 'w':			// w: inform about paddable space
1816      break;
1817    case 'x':			// device controlling commands
1818      stopped = parse_x_command();
1819      break;
1820    default:
1821      warning("unrecognized command `%1'", (unsigned char) command);
1822      skip_line();
1823      break;
1824    } // end of switch
1825  } // end of while
1826
1827  // end of file reached
1828  if (npages > 0)
1829    pr->end_page(current_env->vpos);
1830  delete pr;
1831  pr = 0;
1832  fclose(current_file);
1833  // If `stopped' is not `true' here then there wasn't any `x stop'.
1834  if (!stopped)
1835    warning("no final `x stop' command");
1836  delete_current_env();
1837}
1838