3 // <groff_src_dir>/src/libs/libdriver/input.cpp
5 /* Copyright (C) 1989-2018 Free Software Foundation, Inc.
7 Written by James Clark (jjc@jclark.com)
8 Major rewrite 2001 by Bernd Warken <groff-bernd.warken-72@web.de>
10 This file is part of groff, the GNU roff text processing system.
12 groff is free software; you can redistribute it and/or modify it
13 under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
17 groff is distributed in the hope that it will be useful, but
18 WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 General Public License for more details.
22 You should have received a copy of the GNU General Public License
23 along with this program. If not, see <http://www.gnu.org/licenses/>.
28 This file implements the parser for the intermediate groff output,
29 see groff_out(5), and does the printout for the given device.
31 All parsed information is processed within the function do_file().
32 A device postprocessor just needs to fill in the methods for the class
33 'printer' (or rather a derived class) without having to worry about
34 the syntax of the intermediate output format. Consequently, the
35 programming of groff postprocessors is similar to the development of
38 The prototyping for this file is done in driver.h (and error.h).
41 /* Changes of the 2001 rewrite of this file.
43 The interface to the outside and the handling of the global
44 variables was not changed, but internally many necessary changes
47 The main aim for this rewrite is to provide a first step towards
48 making groff fully compatible with classical troff without pain.
51 - Unknown subcommands of 'D' and 'x' are now ignored like in the
52 classical case, but a warning is issued. This was also
53 implemented for the other commands.
54 - A warning is emitted if 'x stop' is missing.
55 - 'DC' and 'DE' commands didn't position to the right end after
56 drawing (now they do), see discussion below.
57 - So far, 'x stop' was ignored. Now it terminates the processing
58 of the current intermediate output file like the classical troff.
59 - The command 'c' didn't check correctly on white-space.
60 - The environment stack wasn't suitable for the color extensions
61 (replaced by a class).
62 - The old groff parser could only handle a prologue with the first
63 3 lines having a fixed structure, while classical troff specified
64 the sequence of the first 3 commands without further
65 restrictions. Now the parser is smart about additional
66 white space, comments, and empty lines in the prologue.
67 - The old parser allowed space characters only as syntactical
68 separators, while classical troff had tab characters as well.
69 Now any sequence of tabs and/or spaces is a syntactical
70 separator between commands and/or arguments.
71 - Range checks for numbers implemented.
73 New and improved features
74 - The color commands 'm' and 'DF' are added.
75 - The old color command 'Df' is now converted and delegated to 'DFg'.
76 - The command 'F' is implemented as 'use intended file name'. It
77 checks whether its argument agrees with the file name used so far,
78 otherwise a warning is issued. Then the new name is remembered
79 and used for the following error messages.
80 - For the positioning after drawing commands, an alternative, easier
81 scheme is provided, but not yet activated; it can be chosen by
82 undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
83 It extends the rule of the classical troff output language in a
84 logical way instead of the rather strange actual positioning.
85 For details, see the discussion below.
86 - For the 'D' commands that only set the environment, the calling of
87 pr->send_draw() was removed because this doesn't make sense for
88 the 'DF' commands; the (changed) environment is sent with the
90 - Error handling was clearly separated into warnings and fatal.
91 - The error behavior on additional arguments for 'D' and 'x'
92 commands with a fixed number of arguments was changed from being
93 ignored (former groff) to issue a warning and ignore (now), see
94 skip_line_x(). No fatal was chosen because both string and
95 integer arguments can occur.
96 - The gtroff program issues a trailing dummy integer argument for
97 some drawing commands with an odd number of arguments to make the
98 number of arguments even, e.g. the DC and Dt commands; this is
100 - All D commands with a variable number of args expect an even
101 number of trailing integer arguments, so fatal on error was
103 - Disable environment stack and the commands '{' and '}' by making
104 them conditional on macro USE_ENV_STACK; actually, this is
105 undefined by default. There isn't any known application for these
109 - Nested 'switch' commands are avoided by using more functions.
110 Dangerous 'fall-through's avoided.
111 - Commands and functions are sorted alphabetically (where possible).
112 - Dynamic arrays/buffers are now implemented as container classes.
113 - Some functions had an ugly return structure; this has been
114 streamlined by using classes.
115 - Use standard C math functions for number handling, so getting rid
116 of differences to '0'.
117 - The macro 'IntArg' has been created for an easier transition
118 to guaranteed 32 bits integers ('int' is enough for GNU, while
119 ANSI only guarantees 'long int' to have a length of 32 bits).
120 - The many usages of type 'int' are differentiated by using 'Char',
121 'bool', and 'IntArg' where appropriate.
122 - To ease the calls of the local utility functions, the parser
123 variables 'current_file', 'npages', and 'current_env'
124 (formerly env) were made global to the file (formerly they were
125 local to the do_file() function)
126 - Various comments were added.
129 - Get rid of the stupid drawing positioning.
130 - Can the 'Dt' command be completely handled by setting environment
131 within do_file() instead of sending to pr?
132 - Integer arguments must be >= 32 bits, use conditional #define.
133 - Add scaling facility for classical device independence and
134 non-groff devices. Classical troff output had a quasi device
135 independence by scaling the intermediate output to the resolution
136 of the postprocessor device if different from the one specified
137 with 'x T', groff have not. So implement full quasi device
138 indepedence, including the mapping of the strange classical
139 devices to the postprocessor device (seems to be reasonably
141 - The external, global pointer variables are not optimally handled.
142 - The global variables 'current_filename',
143 'current_source_filename', and 'current_lineno' are only used for
144 error reporting. So implement a static class 'Error'
146 - The global 'device' is the name used during the formatting
147 process; there should be a new variable for the device name used
148 during the postprocessing.
149 - Implement the B-spline drawing 'D~' for all graphical devices.
150 - Make 'environment' a class with an overflow check for its members
151 and a delete method to get rid of delete_current_env().
152 - Implement the 'EnvStack' to use 'new' instead of 'malloc'.
153 - The class definitions of this document could go into a new file.
154 - The comments in this section should go to a 'Changelog' or some
155 'README' file in this directory.
159 Discussion of the positioning by drawing commands
161 There was some confusion about the positioning of the graphical
162 pointer at the printout after having executed a 'D' command.
163 The classical troff manual of Ossanna & Kernighan specified,
165 'The position after a graphical object has been drawn is
166 at its end; for circles and ellipses, the "end" is at the
169 From this, it follows that
170 - all open figures (args, splines, and lines) should position at their
172 - all circles and ellipses should position at their right-most point
173 (as if 2 halves had been drawn).
174 - all closed figures apart from circles and ellipses shouldn't change
175 the position because they return to their origin.
176 - all setting commands should not change position because they do not
177 draw any graphical object.
179 In the case of the open figures, this means that the horizontal
180 displacement is the sum of all odd arguments and the vertical offset
181 the sum of all even arguments, called the alternate arguments sum
182 displacement in the following.
184 Unfortunately, groff did not implement this simple rule. The former
185 documentation in groff_out(5) differed from the source code, and
186 neither of them is compatible with the classical rule.
188 The former groff_out(5) specified to use the alternative arguments
189 sum displacement for calculating the drawing positioning of
190 non-classical commands, including the 'Dt' command (setting-only)
191 and closed polygons. Applying this to the new groff color commands
192 will lead to disaster. For their arguments can take large values (>
193 65000). On low resolution devices, the displacement of such large
194 values will corrupt the display or kill the printer. So the
195 nonsense specification has come to a natural end anyway.
197 The groff source code, however, had no positioning for the
198 setting-only commands (esp. 'Dt'), the right-end positioning for
199 outlined circles and ellipses, and the alternative argument sum
200 displacement for all other commands (including filled circles and
203 The reason why no one seems to have suffered from this mayhem so
204 far is that the graphical objects are usually generated by
205 preprocessors like pic that do not depend on the automatic
206 positioning. When using the low level '\D' escape sequences or 'D'
207 output commands, the strange positionings can be circumvented by
208 absolute positionings or by tricks like '\Z'.
210 So doing an exorcism on the strange, incompatible displacements might
211 not harm any existing documents, but will make the usage of the
212 graphical escape sequences and commands natural.
214 That's why the rewrite of this file returned to the reasonable,
215 classical specification with its clear end-of-drawing rule that is
216 suitable for all cases. But a macro STUPID_DRAWING_POSITIONING is
217 provided for testing the funny former behavior.
219 The new rule implies the following behavior.
220 - Setting commands ('Dt', 'Df', 'DF') and polygons ('Dp' and 'DP')
221 do not change position now.
222 - Filled circles and ellipses ('DC' and 'DE') position at their
223 most right point (outlined ones 'Dc' and 'De' did this anyway).
224 - As before, all open graphical objects position to their final
225 drawing point (alternate sum of the command arguments).
229 #ifndef STUPID_DRAWING_POSITIONING
230 // uncomment next line if all non-classical D commands shall position
231 // to the strange alternate sum of args displacement
232 #define STUPID_DRAWING_POSITIONING
235 // Decide whether the commands '{' and '}' for different environments
248 /**********************************************************************
250 **********************************************************************/
252 // integer type used in the fields of struct environment (see printer.h)
255 // integer arguments of groff_out commands, must be >= 32 bits
258 // color components of groff_out color commands, must be >= 32 bits
259 typedef unsigned int ColorArg;
261 // Array for IntArg values.
263 size_t num_allocated;
268 IntArray(const size_t);
270 IntArg operator[](const size_t i) const
273 fatal("index out of range");
274 return (IntArg) data[i];
277 IntArg *get_data(void) const { return (IntArg *)data; }
278 size_t len(void) const { return num_stored; }
281 // Characters read from the input queue.
285 Char(void) : data('\0') {}
286 Char(const int c) : data(c) {}
287 bool operator==(char c) const { return (data == c) ? true : false; }
288 bool operator==(int c) const { return (data == c) ? true : false; }
289 bool operator==(const Char c) const
290 { return (data == c.data) ? true : false; }
291 bool operator!=(char c) const { return !(*this == c); }
292 bool operator!=(int c) const { return !(*this == c); }
293 bool operator!=(const Char c) const { return !(*this == c); }
294 operator int() const { return (int) data; }
295 operator unsigned char() const { return (unsigned char) data; }
296 operator char() const { return (char) data; }
299 // Buffer for string arguments (Char, not char).
301 size_t num_allocated;
303 Char *data; // not terminated by '\0'
305 StringBuf(void); // allocate without storing
307 void append(const Char); // append character to 'data'
308 char *make_string(void); // return new copy of 'data' with '\0'
309 bool is_empty(void) { // true if none stored
310 return (num_stored > 0) ? false : true;
312 void reset(void); // set 'num_stored' to 0
318 size_t num_allocated;
323 environment *pop(void);
324 void push(environment *e);
326 #endif // USE_ENV_STACK
329 /**********************************************************************
331 **********************************************************************/
333 // exported as extern by error.h (called from driver.h)
334 // needed for error messages (see ../libgroff/error.cpp)
335 const char *current_filename = 0; // printable name of the current file
336 // printable name of current source file
337 const char *current_source_filename = 0;
338 int current_lineno = 0; // current line number of printout
340 // exported as extern by device.h;
341 const char *device = 0; // cancel former init with literal
347 // We rely on an implementation of the 'new' operator which aborts
348 // gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
351 /**********************************************************************
352 static local variables
353 **********************************************************************/
355 FILE *current_file = 0; // current input stream for parser
357 // npages: number of pages processed so far (including current page),
358 // _not_ the page number in the printout (can be set with 'p').
362 COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
365 INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
367 // parser environment, created and deleted by each run of do_file()
368 environment *current_env = 0;
372 envp_size = sizeof(environment *);
373 #endif // USE_ENV_STACK
376 /**********************************************************************
377 function declarations
378 **********************************************************************/
381 ColorArg color_from_Df_command(IntArg);
382 // transform old color into new
383 void delete_current_env(void); // delete global var current_env
384 void fatal_command(char); // abort for invalid command
385 inline Char get_char(void); // read next character from input stream
386 ColorArg get_color_arg(void); // read in argument for new color cmds
387 IntArray *get_D_fixed_args(const size_t);
388 // read in fixed number of integer
390 IntArray *get_D_fixed_args_odd_dummy(const size_t);
391 // read in a fixed number of integer
392 // arguments plus optional dummy
393 IntArray *get_D_variable_args(void);
394 // variable, even number of int args
395 char *get_extended_arg(void); // argument for 'x X' (several lines)
396 IntArg get_integer_arg(void); // read in next integer argument
397 IntArray *get_possibly_integer_args();
398 // 0 or more integer arguments
399 char *get_string_arg(void); // read in next string arg, ended by WS
400 inline bool is_space_or_tab(const Char);
401 // test on space/tab char
402 Char next_arg_begin(void); // skip white space on current line
403 Char next_command(void); // go to next command, evt. diff. line
404 inline bool odd(const int); // test if integer is odd
405 void position_to_end_of_args(const IntArray * const);
406 // positioning after drawing
407 void remember_filename(const char *);
408 // set global current_filename
409 void remember_source_filename(const char *);
410 // set global current_source_filename
411 void send_draw(const Char, const IntArray * const);
413 void skip_line(void); // unconditionally skip to next line
414 bool skip_line_checked(void); // skip line, false if args are left
415 void skip_line_fatal(void); // skip line, fatal if args are left
416 void skip_line_warn(void); // skip line, warn if args are left
417 void skip_line_D(void); // skip line in D commands
418 void skip_line_x(void); // skip line in x commands
419 void skip_to_end_of_line(void); // skip to the end of the current line
420 inline void unget_char(const Char);
421 // restore character onto input
423 // parser subcommands
424 void parse_color_command(color *);
425 // color sub(sub)commands m and DF
426 void parse_D_command(void); // graphical subcommands
427 bool parse_x_command(void); // device controller subcommands
430 /**********************************************************************
432 **********************************************************************/
435 EnvStack::EnvStack(void)
438 // allocate pointer to array of num_allocated pointers to environment
439 data = (environment **)malloc(envp_size * num_allocated);
441 fatal("could not allocate environment data");
445 EnvStack::~EnvStack(void)
447 for (size_t i = 0; i < num_stored; i++)
452 // return top element from stack and decrease stack pointer
454 // the calling function must take care of properly deleting the result
459 environment *result = data[num_stored];
460 data[num_stored] = 0;
464 // copy argument and push this onto the stack
466 EnvStack::push(environment *e)
468 environment *e_copy = new environment;
469 if (num_stored >= num_allocated) {
470 environment **old_data = data;
472 data = (environment **)malloc(envp_size * num_allocated);
474 fatal("could not allocate data");
475 for (size_t i = 0; i < num_stored; i++)
476 data[i] = old_data[i];
479 e_copy->col = new color;
480 e_copy->fill = new color;
481 *e_copy->col = *e->col;
482 *e_copy->fill = *e->fill;
483 e_copy->fontno = e->fontno;
484 e_copy->height = e->height;
485 e_copy->hpos = e->hpos;
486 e_copy->size = e->size;
487 e_copy->slant = e->slant;
488 e_copy->vpos = e->vpos;
489 data[num_stored] = e_copy;
492 #endif // USE_ENV_STACK
494 IntArray::IntArray(void)
497 data = new IntArg[num_allocated];
501 IntArray::IntArray(const size_t n)
504 fatal("number of integers to be allocated must be > 0");
506 data = new IntArg[num_allocated];
510 IntArray::~IntArray(void)
516 IntArray::append(IntArg x)
518 if (num_stored >= num_allocated) {
519 IntArg *old_data = data;
521 data = new IntArg[num_allocated];
522 for (size_t i = 0; i < num_stored; i++)
523 data[i] = old_data[i];
526 data[num_stored] = x;
530 StringBuf::StringBuf(void)
534 data = new Char[num_allocated];
537 StringBuf::~StringBuf(void)
543 StringBuf::append(const Char c)
545 if (num_stored >= num_allocated) {
546 Char *old_data = data;
548 data = new Char[num_allocated];
549 for (size_t i = 0; i < num_stored; i++)
550 data[i] = old_data[i];
553 data[num_stored] = c;
558 StringBuf::make_string(void)
560 char *result = new char[num_stored + 1];
561 for (size_t i = 0; i < num_stored; i++)
562 result[i] = (char) data[i];
563 result[num_stored] = '\0';
568 StringBuf::reset(void)
573 /**********************************************************************
575 **********************************************************************/
577 //////////////////////////////////////////////////////////////////////
578 /* color_from_Df_command:
579 Process the gray shade setting command Df.
581 Transform Df style color into DF style color.
582 Df color: 0-1000, 0 is white
583 DF color: 0-65536, 0 is black
585 The Df command is obsoleted by command DFg, but kept for
589 color_from_Df_command(IntArg Df_gray)
591 return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
594 //////////////////////////////////////////////////////////////////////
595 /* delete_current_env():
596 Delete global variable current_env and its pointer members.
598 This should be a class method of environment.
600 void delete_current_env(void)
602 delete current_env->col;
603 delete current_env->fill;
608 //////////////////////////////////////////////////////////////////////
610 Emit error message about invalid command and abort.
613 fatal_command(char command)
615 fatal("'%1' command invalid before first 'p' command", command);
618 //////////////////////////////////////////////////////////////////////
620 Retrieve the next character from the input queue.
622 Return: The retrieved character (incl. EOF), converted to Char.
627 return (Char) getc(current_file);
630 //////////////////////////////////////////////////////////////////////
632 Retrieve an argument suitable for the color commands m and DF.
634 Return: The retrieved color argument.
639 IntArg x = get_integer_arg();
640 if (x < 0 || x > (IntArg)COLORARG_MAX) {
641 error("color component argument out of range");
647 //////////////////////////////////////////////////////////////////////
648 /* get_D_fixed_args():
649 Get a fixed number of integer arguments for D commands.
651 Fatal if wrong number of arguments.
652 Too many arguments on the line raise a warning.
655 number: In-parameter, the number of arguments to be retrieved.
656 ignore: In-parameter, ignore next argument -- GNU troff always emits
657 pairs of parameters for 'D' extensions added by groff.
660 Return: New IntArray containing the arguments.
663 get_D_fixed_args(const size_t number)
666 fatal("requested number of arguments must be > 0");
667 IntArray *args = new IntArray(number);
668 for (size_t i = 0; i < number; i++)
669 args->append(get_integer_arg());
674 //////////////////////////////////////////////////////////////////////
675 /* get_D_fixed_args_odd_dummy():
676 Get a fixed number of integer arguments for D commands and optionally
677 ignore a dummy integer argument if the requested number is odd.
679 The gtroff program adds a dummy argument to some commands to get
680 an even number of arguments.
681 Error if the number of arguments differs from the scheme above.
684 number: In-parameter, the number of arguments to be retrieved.
686 Return: New IntArray containing the arguments.
689 get_D_fixed_args_odd_dummy(const size_t number)
692 fatal("requested number of arguments must be > 0");
693 IntArray *args = new IntArray(number);
694 for (size_t i = 0; i < number; i++)
695 args->append(get_integer_arg());
697 IntArray *a = get_possibly_integer_args();
699 error("too many arguments");
706 //////////////////////////////////////////////////////////////////////
707 /* get_D_variable_args():
708 Get a variable even number of integer arguments for D commands.
710 Get as many integer arguments as possible from the rest of the
712 - The arguments are separated by an arbitrary sequence of space or
714 - A comment, a newline, or EOF indicates the end of processing.
715 - Error on non-digit characters different from these.
716 - A final line skip is performed (except for EOF).
718 Return: New IntArray of the retrieved arguments.
721 get_D_variable_args()
723 IntArray *args = get_possibly_integer_args();
724 size_t n = args->len();
726 error("no arguments found");
728 error("even number of arguments expected");
733 //////////////////////////////////////////////////////////////////////
734 /* get_extended_arg():
735 Retrieve extended arg for 'x X' command.
737 - Skip leading spaces and tabs, error on EOL or newline.
738 - Return everything before the next NL or EOF ('#' is not a comment);
739 as long as the following line starts with '+' this is returned
740 as well, with the '+' replaced by a newline.
741 - Final line skip is always performed.
743 Return: Allocated (new) string of retrieved text argument.
746 get_extended_arg(void)
748 StringBuf buf = StringBuf();
749 Char c = next_arg_begin();
750 while ((int) c != EOF) {
751 if ((int) c == '\n') {
755 buf.append((Char) '\n');
757 unget_char(c); // first character of next line
765 return buf.make_string();
768 //////////////////////////////////////////////////////////////////////
769 /* get_integer_arg(): Retrieve integer argument.
771 Skip leading spaces and tabs, collect an optional '-' and all
772 following decimal digits (at least one) up to the next non-digit,
773 which is restored onto the input queue.
775 Fatal error on all other situations.
777 Return: Retrieved integer.
780 get_integer_arg(void)
782 StringBuf buf = StringBuf();
783 Char c = next_arg_begin();
784 if ((int) c == '-') {
788 if (!isdigit((int) c))
789 fatal("integer argument expected");
790 while (isdigit((int) c)) {
796 char *s = buf.make_string();
798 long int number = strtol(s, 0, 10);
800 || number > INTARG_MAX || number < -INTARG_MAX) {
801 error("integer argument too large");
805 return (IntArg) number;
808 //////////////////////////////////////////////////////////////////////
809 /* get_possibly_integer_args():
810 Parse the rest of the input line as a list of integer arguments.
812 Get as many integer arguments as possible from the rest of the
813 current line, even none.
814 - The arguments are separated by an arbitrary sequence of space or
816 - A comment, a newline, or EOF indicates the end of processing.
817 - Error on non-digit characters different from these.
818 - No line skip is performed.
820 Return: New IntArray of the retrieved arguments.
823 get_possibly_integer_args()
826 StringBuf buf = StringBuf();
828 IntArray *args = new IntArray();
831 while (is_space_or_tab(c))
834 Char c1 = get_char();
835 if (isdigit((int) c1)) {
842 while (isdigit((int) c)) {
846 if (!buf.is_empty()) {
847 char *s = buf.make_string();
849 long int x = strtol(s, 0, 10);
851 || x > INTARG_MAX || x < -INTARG_MAX) {
852 error("invalid integer argument, set to 0");
855 args->append((IntArg) x);
858 // Here, c is not a digit.
859 // Terminate on comment, end of line, or end of file, while
860 // space or tab indicate continuation; otherwise error.
863 skip_to_end_of_line();
877 error("integer argument expected");
885 //////////////////////////////////////////////////////////////////////
889 - Skip leading spaces and tabs; error on EOL or newline.
890 - Return all following characters before the next space, tab,
891 newline, or EOF character (in-word '#' is not a comment character).
892 - The terminating space, tab, newline, or EOF character is restored
893 onto the input queue, so no line skip.
895 Return: Retrieved string as char *, allocated by 'new'.
900 StringBuf buf = StringBuf();
901 Char c = next_arg_begin();
902 while (!is_space_or_tab(c)
903 && c != Char('\n') && c != Char(EOF)) {
907 unget_char(c); // restore white space
908 return buf.make_string();
911 //////////////////////////////////////////////////////////////////////
912 /* is_space_or_tab():
913 Test a character if it is a space or tab.
915 c: In-parameter, character to be tested.
917 Return: True, if c is a space or tab character, false otherwise.
920 is_space_or_tab(const Char c)
922 return (c == Char(' ') || c == Char('\t')) ? true : false;
925 //////////////////////////////////////////////////////////////////////
927 Return first character of next argument.
929 Skip space and tab characters; error on newline or EOF.
931 Return: The first character different from these (including '#').
945 error("missing argument");
947 default: // first essential character
953 //////////////////////////////////////////////////////////////////////
955 Find the first character of the next command.
957 Skip spaces, tabs, comments (introduced by #), and newlines.
959 Return: The first character different from these (including EOF).
977 default: // EOF or first essential character
983 //////////////////////////////////////////////////////////////////////
985 Test whether argument is an odd number.
987 n: In-parameter, the integer to be tested.
989 Return: True if odd, false otherwise.
994 return ((n & 1) == 1) ? true : false;
997 //////////////////////////////////////////////////////////////////////
998 /* position_to_end_of_args():
999 Move graphical pointer to end of drawn figure.
1001 This is used by the D commands that draw open geometrical figures.
1002 The algorithm simply sums up all horizontal displacements (arguments
1003 with even number) for the horizontal component. Similarly, the
1004 vertical component is the sum of the odd arguments.
1006 args: In-parameter, the arguments of a former drawing command.
1009 position_to_end_of_args(const IntArray * const args)
1012 const size_t n = args->len();
1013 for (i = 0; i < n; i += 2)
1014 current_env->hpos += (*args)[i];
1015 for (i = 1; i < n; i += 2)
1016 current_env->vpos += (*args)[i];
1019 //////////////////////////////////////////////////////////////////////
1020 /* remember_filename():
1021 Set global variable current_filename.
1023 The actual filename is stored in current_filename. This is used by
1024 the postprocessors, expecting the name "<standard input>" for stdin.
1026 filename: In-out-parameter; is changed to the new value also.
1029 remember_filename(const char *filename)
1032 if (strcmp(filename, "-") == 0)
1033 fname = (char *)"<standard input>";
1035 fname = (char *)filename;
1036 size_t len = strlen(fname) + 1;
1037 if (current_filename != 0)
1038 free((char *)current_filename);
1039 current_filename = (const char *)malloc(len);
1040 if (current_filename == 0)
1041 fatal("can't malloc space for filename");
1042 strncpy((char *)current_filename, (char *)fname, len);
1045 //////////////////////////////////////////////////////////////////////
1046 /* remember_source_filename():
1047 Set global variable current_source_filename.
1049 The actual filename is stored in current_filename. This is used by
1050 the postprocessors, expecting the name "<standard input>" for stdin.
1052 filename: In-out-parameter; is changed to the new value also.
1055 remember_source_filename(const char *filename)
1058 if (strcmp(filename, "-") == 0)
1059 fname = (char *)"<standard input>";
1061 fname = (char *)filename;
1062 size_t len = strlen(fname) + 1;
1063 if (current_source_filename != 0)
1064 free((char *)current_source_filename);
1065 current_source_filename = (const char *)malloc(len);
1066 if (current_source_filename == 0)
1067 fatal("can't malloc space for filename");
1068 strncpy((char *)current_source_filename, (char *)fname, len);
1071 //////////////////////////////////////////////////////////////////////
1073 Call draw method of printer class.
1075 subcmd: Letter of actual D subcommand.
1076 args: Array of integer arguments of actual D subcommand.
1079 send_draw(const Char subcmd, const IntArray * const args)
1081 EnvInt n = (EnvInt) args->len();
1082 pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
1085 //////////////////////////////////////////////////////////////////////
1087 Go to next line within the input queue.
1089 Skip the rest of the current line, including the newline character.
1090 The global variable current_lineno is adjusted.
1091 No errors are raised.
1096 Char c = get_char();
1108 //////////////////////////////////////////////////////////////////////
1109 /* skip_line_checked ():
1110 Check that there aren't any arguments left on the rest of the line,
1113 Spaces, tabs, and a comment are allowed before newline or EOF.
1114 All other characters raise an error.
1117 skip_line_checked(void)
1120 Char c = get_char();
1121 while (is_space_or_tab(c))
1124 case '#': // comment
1140 //////////////////////////////////////////////////////////////////////
1141 /* skip_line_fatal ():
1142 Fatal error if arguments left, otherwise skip line.
1144 Spaces, tabs, and a comment are allowed before newline or EOF.
1145 All other characters trigger the error.
1148 skip_line_fatal(void)
1150 bool ok = skip_line_checked();
1153 error("too many arguments");
1158 //////////////////////////////////////////////////////////////////////
1159 /* skip_line_warn ():
1160 Skip line, but warn if arguments are left on actual line.
1162 Spaces, tabs, and a comment are allowed before newline or EOF.
1163 All other characters raise a warning
1166 skip_line_warn(void)
1168 bool ok = skip_line_checked();
1171 warning("too many arguments on current line");
1176 //////////////////////////////////////////////////////////////////////
1178 Skip line in 'D' commands.
1180 Decide whether in case of an additional argument a fatal error is
1181 raised (the documented classical behavior), only a warning is
1182 issued, or the line is just skipped (former groff behavior).
1183 Actually decided for the warning.
1189 // or: skip_line_fatal();
1193 //////////////////////////////////////////////////////////////////////
1195 Skip line in 'x' commands.
1197 Decide whether in case of an additional argument a fatal error is
1198 raised (the documented classical behavior), only a warning is
1199 issued, or the line is just skipped (former groff behavior).
1200 Actually decided for the warning.
1206 // or: skip_line_fatal();
1210 //////////////////////////////////////////////////////////////////////
1211 /* skip_to_end_of_line():
1212 Go to the end of the current line.
1214 Skip the rest of the current line, excluding the newline character.
1215 The global variable current_lineno is not changed.
1216 No errors are raised.
1219 skip_to_end_of_line(void)
1221 Char c = get_char();
1233 //////////////////////////////////////////////////////////////////////
1235 Restore character c onto input queue.
1237 Write a character back onto the input stream.
1238 EOF is gracefully handled.
1240 c: In-parameter; character to be pushed onto the input queue.
1243 unget_char(const Char c)
1247 if (ungetc(ch, current_file) == EOF)
1248 fatal("could not unget character");
1253 /**********************************************************************
1255 **********************************************************************/
1257 //////////////////////////////////////////////////////////////////////
1258 /* parse_color_command:
1259 Process the commands m and DF, but not Df.
1261 col: In-out-parameter; the color object to be set, must have
1262 been initialized before.
1265 parse_color_command(color *col)
1268 ColorArg red = 0, green = 0, blue = 0;
1269 ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
1270 Char subcmd = next_arg_begin();
1271 switch((int) subcmd) {
1272 case 'c': // DFc or mc: CMY
1273 cyan = get_color_arg();
1274 magenta = get_color_arg();
1275 yellow = get_color_arg();
1276 col->set_cmy(cyan, magenta, yellow);
1278 case 'd': // DFd or md: set default color
1281 case 'g': // DFg or mg: gray
1282 gray = get_color_arg();
1283 col->set_gray(gray);
1285 case 'k': // DFk or mk: CMYK
1286 cyan = get_color_arg();
1287 magenta = get_color_arg();
1288 yellow = get_color_arg();
1289 black = get_color_arg();
1290 col->set_cmyk(cyan, magenta, yellow, black);
1292 case 'r': // DFr or mr: RGB
1293 red = get_color_arg();
1294 green = get_color_arg();
1295 blue = get_color_arg();
1296 col->set_rgb(red, green, blue);
1299 error("invalid color scheme '%1'", (int) subcmd);
1301 } // end of color subcommands
1304 //////////////////////////////////////////////////////////////////////
1305 /* parse_D_command():
1306 Parse the subcommands of graphical command D.
1308 This is the part of the do_file() parser that scans the graphical
1310 - Error on lacking or wrong arguments.
1311 - Warning on too many arguments.
1312 - Line is always skipped.
1317 Char subcmd = next_arg_begin();
1318 switch((int) subcmd) {
1319 case '~': // D~: draw B-spline
1320 // actually, this isn't available for some postprocessors
1322 default: // unknown options are passed to device
1324 IntArray *args = get_D_variable_args();
1325 send_draw(subcmd, args);
1326 position_to_end_of_args(args);
1330 case 'a': // Da: draw arc
1332 IntArray *args = get_D_fixed_args(4);
1333 send_draw(subcmd, args);
1334 position_to_end_of_args(args);
1338 case 'c': // Dc: draw circle line
1340 IntArray *args = get_D_fixed_args(1);
1341 send_draw(subcmd, args);
1342 // move to right end
1343 current_env->hpos += (*args)[0];
1347 case 'C': // DC: draw solid circle
1349 IntArray *args = get_D_fixed_args_odd_dummy(1);
1350 send_draw(subcmd, args);
1351 // move to right end
1352 current_env->hpos += (*args)[0];
1356 case 'e': // De: draw ellipse line
1357 case 'E': // DE: draw solid ellipse
1359 IntArray *args = get_D_fixed_args(2);
1360 send_draw(subcmd, args);
1361 // move to right end
1362 current_env->hpos += (*args)[0];
1366 case 'f': // Df: set fill gray; obsoleted by DFg
1368 IntArg arg = get_integer_arg();
1369 if ((arg >= 0) && (arg <= 1000)) {
1370 // convert arg and treat it like DFg
1371 ColorArg gray = color_from_Df_command(arg);
1372 current_env->fill->set_gray(gray);
1375 // set fill color to the same value as the current outline color
1376 delete current_env->fill;
1377 current_env->fill = new color(current_env->col);
1379 pr->change_fill_color(current_env);
1380 // skip unused 'vertical' component (\D'...' always emits pairs)
1381 (void) get_integer_arg();
1382 # ifdef STUPID_DRAWING_POSITIONING
1383 current_env->hpos += arg;
1388 case 'F': // DF: set fill color, several formats
1389 parse_color_command(current_env->fill);
1390 pr->change_fill_color(current_env);
1391 // no positioning (setting-only command)
1394 case 'l': // Dl: draw line
1396 IntArray *args = get_D_fixed_args(2);
1397 send_draw(subcmd, args);
1398 position_to_end_of_args(args);
1402 case 'p': // Dp: draw closed polygon line
1403 case 'P': // DP: draw solid closed polygon
1405 IntArray *args = get_D_variable_args();
1406 send_draw(subcmd, args);
1407 # ifdef STUPID_DRAWING_POSITIONING
1408 // final args positioning
1409 position_to_end_of_args(args);
1414 case 't': // Dt: set line thickness
1416 IntArray *args = get_D_fixed_args_odd_dummy(1);
1417 send_draw(subcmd, args);
1418 # ifdef STUPID_DRAWING_POSITIONING
1419 // final args positioning
1420 position_to_end_of_args(args);
1425 } // end of D subcommands
1428 //////////////////////////////////////////////////////////////////////
1429 /* parse_x_command():
1430 Parse subcommands of the device control command x.
1432 This is the part of the do_file() parser that scans the device
1433 controlling commands.
1434 - Error on duplicate prologue commands.
1435 - Error on wrong or lacking arguments.
1436 - Warning on too many arguments.
1437 - Line is always skipped.
1440 - current_env: is set by many subcommands.
1441 - npages: page counting variable
1443 Return: boolean in the meaning of 'stopped'
1444 - true if parsing should be stopped ('x stop').
1445 - false if parsing should continue.
1448 parse_x_command(void)
1450 bool stopped = false;
1451 char *subcmd_str = get_string_arg();
1452 char subcmd = subcmd_str[0];
1454 case 'f': // x font: mount font
1456 IntArg n = get_integer_arg();
1457 char *name = get_string_arg();
1458 pr->load_font(n, name);
1463 case 'F': // x Filename: set filename for errors
1465 char *str_arg = get_extended_arg();
1467 warning("empty argument for 'x F' command");
1469 remember_source_filename(str_arg);
1474 case 'H': // x Height: set character height
1475 current_env->height = get_integer_arg();
1476 if (current_env->height == current_env->size)
1477 current_env->height = 0;
1480 case 'i': // x init: initialize device
1481 error("duplicate 'x init' command");
1484 case 'p': // x pause: pause device
1487 case 'r': // x res: set resolution
1488 error("duplicate 'x res' command");
1491 case 's': // x stop: stop device
1495 case 'S': // x Slant: set slant
1496 current_env->slant = get_integer_arg();
1499 case 't': // x trailer: generate trailer info
1502 case 'T': // x Typesetter: set typesetter
1503 error("duplicate 'x T' command");
1506 case 'u': // x underline: from .cu
1508 char *str_arg = get_string_arg();
1509 pr->special(str_arg, current_env, 'u');
1514 case 'X': // x X: send uninterpretedly to device
1516 char *str_arg = get_extended_arg(); // includes line skip
1518 error("'x X' command invalid before first 'p' command");
1519 else if (str_arg && (strncmp(str_arg, "devtag:",
1520 strlen("devtag:")) == 0))
1521 pr->devtag(str_arg, current_env);
1523 pr->special(str_arg, current_env);
1527 default: // ignore unknown x commands, but warn
1528 warning("unknown command 'x %1'", subcmd);
1531 a_delete subcmd_str;
1536 /**********************************************************************
1537 exported part (by driver.h)
1538 **********************************************************************/
1540 ////////////////////////////////////////////////////////////////////////
1542 Parse and postprocess groff intermediate output.
1544 filename: "-" for standard input, normal file name otherwise
1547 do_file(const char *filename)
1550 bool stopped = false; // terminating condition
1552 #ifdef USE_ENV_STACK
1553 EnvStack env_stack = EnvStack();
1554 #endif // USE_ENV_STACK
1556 // setup of global variables
1559 // 'pr' is initialized after the prologue.
1560 // 'device' is set by the 1st prologue command.
1562 if (filename[0] == '-' && filename[1] == '\0')
1563 current_file = stdin;
1566 current_file = fopen(filename, "r");
1567 if (errno != 0 || current_file == 0) {
1568 error("can't open file '%1'", filename);
1572 remember_filename(filename);
1574 if (current_env != 0)
1575 delete_current_env();
1576 current_env = new environment;
1577 current_env->col = new color;
1578 current_env->fill = new color;
1579 current_env->fontno = -1;
1580 current_env->height = 0;
1581 current_env->hpos = -1;
1582 current_env->slant = 0;
1583 current_env->size = 0;
1584 current_env->vpos = -1;
1586 // parsing of prologue (first 3 commands)
1591 // 1st command 'x T'
1592 command = next_command();
1593 if ((int) command == EOF)
1595 if ((int) command != 'x')
1596 fatal("the first command must be 'x T'");
1597 str_arg = get_string_arg();
1598 if (str_arg[0] != 'T')
1599 fatal("the first command must be 'x T'");
1601 char *tmp_dev = get_string_arg();
1602 if (pr == 0) { // note: 'pr' initialized after prologue
1604 if (!font::load_desc())
1605 fatal("couldn't load DESC file, can't continue");
1608 if (device == 0 || strcmp(device, tmp_dev) != 0)
1609 fatal("all files must use the same device");
1612 skip_line_x(); // ignore further arguments
1613 current_env->size = 10 * font::sizescale;
1615 // 2nd command 'x res'
1616 command = next_command();
1617 if ((int) command != 'x')
1618 fatal("the second command must be 'x res'");
1619 str_arg = get_string_arg();
1620 if (str_arg[0] != 'r')
1621 fatal("the second command must be 'x res'");
1623 int_arg = get_integer_arg();
1624 EnvInt font_res = font::res;
1625 if (int_arg != font_res)
1626 fatal("resolution does not match");
1627 int_arg = get_integer_arg();
1628 if (int_arg != font::hor)
1629 fatal("minimum horizontal motion does not match");
1630 int_arg = get_integer_arg();
1631 if (int_arg != font::vert)
1632 fatal("minimum vertical motion does not match");
1633 skip_line_x(); // ignore further arguments
1635 // 3rd command 'x init'
1636 command = next_command();
1638 fatal("the third command must be 'x init'");
1639 str_arg = get_string_arg();
1640 if (str_arg[0] != 'i')
1641 fatal("the third command must be 'x init'");
1648 pr = make_printer();
1650 command = next_command();
1653 // spaces, tabs, comments, and newlines are skipped here
1654 switch ((int) command) {
1655 case '#': // #: comment, ignore up to end of line
1658 #ifdef USE_ENV_STACK
1659 case '{': // {: start a new environment (a copy)
1660 env_stack.push(current_env);
1662 case '}': // }: pop previous env from stack
1663 delete_current_env();
1664 current_env = env_stack.pop();
1666 #endif // USE_ENV_STACK
1667 case '0': // ddc: obsolete jump and print command
1677 { // expect 2 digits and a character
1679 Char c = next_arg_begin();
1681 fatal_command(command);
1682 if (!isdigit((int) c)) {
1683 error("digit expected");
1686 s[0] = (char) command;
1690 long int x = strtol(s, 0, 10);
1692 error("couldn't convert 2 digits");
1693 EnvInt hor_pos = (EnvInt) x;
1694 current_env->hpos += hor_pos;
1695 c = next_arg_begin();
1696 if ((int) c == '\n' || (int) c == EOF)
1697 error("character argument expected");
1699 pr->set_ascii_char((unsigned char) c, current_env);
1702 case 'c': // c: print ascii char without moving
1705 fatal_command(command);
1706 Char c = next_arg_begin();
1707 if (c == '\n' || c == EOF)
1708 error("missing argument to 'c' command");
1710 pr->set_ascii_char((unsigned char) c, current_env);
1713 case 'C': // C: print named special character
1716 fatal_command(command);
1717 char *str_arg = get_string_arg();
1718 pr->set_special_char(str_arg, current_env);
1722 case 'D': // drawing commands
1724 fatal_command(command);
1727 case 'f': // f: set font to number
1728 current_env->fontno = get_integer_arg();
1730 case 'F': // F: obsolete, replaced by 'x F'
1732 char *str_arg = get_extended_arg();
1733 remember_source_filename(str_arg);
1737 case 'h': // h: relative horizontal move
1738 current_env->hpos += (EnvInt) get_integer_arg();
1740 case 'H': // H: absolute horizontal positioning
1741 current_env->hpos = (EnvInt) get_integer_arg();
1743 case 'm': // m: glyph color
1744 parse_color_command(current_env->col);
1745 pr->change_color(current_env);
1747 case 'n': // n: print end of line
1748 // ignore two arguments (historically)
1750 fatal_command(command);
1752 (void) get_integer_arg();
1753 (void) get_integer_arg();
1755 case 'N': // N: print char with given int code
1757 fatal_command(command);
1758 pr->set_numbered_char(get_integer_arg(), current_env);
1760 case 'p': // p: start new page with given number
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;
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;
1772 case 't': // t: print a text word
1776 fatal_command(command);
1777 char *str_arg = get_string_arg();
1779 while ((c = str_arg[i++]) != '\0') {
1781 pr->set_ascii_char((unsigned char) c, current_env, &w);
1782 current_env->hpos += w;
1787 case 'u': // u: print spaced word
1791 fatal_command(command);
1792 EnvInt kern = (EnvInt) get_integer_arg();
1793 char *str_arg = get_string_arg();
1795 while ((c = str_arg[i++]) != '\0') {
1797 pr->set_ascii_char((unsigned char) c, current_env, &w);
1798 current_env->hpos += w + kern;
1803 case 'v': // v: relative vertical move
1804 current_env->vpos += (EnvInt) get_integer_arg();
1806 case 'V': // V: absolute vertical positioning
1807 current_env->vpos = (EnvInt) get_integer_arg();
1809 case 'w': // w: inform about paddable space
1811 case 'x': // device controlling commands
1812 stopped = parse_x_command();
1815 warning("unrecognized command '%1'", (unsigned char) command);
1821 // end of file reached
1823 pr->end_page(current_env->vpos);
1826 fclose(current_file);
1827 // If 'stopped' is not 'true' here then there wasn't any 'x stop'.
1829 warning("no final 'x stop' command");
1830 delete_current_env();