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