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