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