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