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