1 /* Copyright (C) 1989-2020 Free Software Foundation, Inc.
3 Written by James Clark (jjc@jclark.com)
4 Major rewrite 2001 by Bernd Warken <groff-bernd.warken-72@web.de>
6 This file is part of groff, the GNU roff text processing system.
8 groff is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 groff is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 This file implements the parser for the intermediate groff output,
25 see groff_out(5), and does the printout for the given device.
27 All parsed information is processed within the function do_file().
28 A device postprocessor just needs to fill in the methods for the class
29 'printer' (or rather a derived class) without having to worry about
30 the syntax of the intermediate output format. Consequently, the
31 programming of groff postprocessors is similar to the development of
34 The prototyping for this file is done in driver.h (and error.h).
37 /* Changes of the 2001 rewrite of this file.
39 The interface to the outside and the handling of the global
40 variables was not changed, but internally many necessary changes
43 The main aim for this rewrite is to provide a first step towards
44 making groff fully compatible with classical troff without pain.
47 - Unknown subcommands of 'D' and 'x' are now ignored like in the
48 classical case, but a warning is issued. This was also
49 implemented for the other commands.
50 - A warning is emitted if 'x stop' is missing.
51 - 'DC' and 'DE' commands didn't position to the right end after
52 drawing (now they do), see discussion below.
53 - So far, 'x stop' was ignored. Now it terminates the processing
54 of the current intermediate output file like the classical troff.
55 - The command 'c' didn't check correctly on white-space.
56 - The environment stack wasn't suitable for the color extensions
57 (replaced by a class).
58 - The old groff parser could only handle a prologue with the first
59 3 lines having a fixed structure, while classical troff specified
60 the sequence of the first 3 commands without further
61 restrictions. Now the parser is smart about additional
62 white space, comments, and empty lines in the prologue.
63 - The old parser allowed space characters only as syntactical
64 separators, while classical troff had tab characters as well.
65 Now any sequence of tabs and/or spaces is a syntactical
66 separator between commands and/or arguments.
67 - Range checks for numbers implemented.
69 New and improved features
70 - The color commands 'm' and 'DF' are added.
71 - The old color command 'Df' is now converted and delegated to 'DFg'.
72 - The command 'F' is implemented as 'use intended file name'. It
73 checks whether its argument agrees with the file name used so far,
74 otherwise a warning is issued. Then the new name is remembered
75 and used for the following error messages.
76 - For the positioning after drawing commands, an alternative, easier
77 scheme is provided, but not yet activated; it can be chosen by
78 undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
79 It extends the rule of the classical troff output language in a
80 logical way instead of the rather strange actual positioning.
81 For details, see the discussion below.
82 - For the 'D' commands that only set the environment, the calling of
83 pr->send_draw() was removed because this doesn't make sense for
84 the 'DF' commands; the (changed) environment is sent with the
86 - Error handling was clearly separated into warnings and fatal.
87 - The error behavior on additional arguments for 'D' and 'x'
88 commands with a fixed number of arguments was changed from being
89 ignored (former groff) to issue a warning and ignore (now), see
90 skip_line_x(). No fatal was chosen because both string and
91 integer arguments can occur.
92 - The gtroff program issues a trailing dummy integer argument for
93 some drawing commands with an odd number of arguments to make the
94 number of arguments even, e.g. the DC and Dt commands; this is
96 - All D commands with a variable number of args expect an even
97 number of trailing integer arguments, so fatal on error was
99 - Disable environment stack and the commands '{' and '}' by making
100 them conditional on macro USE_ENV_STACK; actually, this is
101 undefined by default. There isn't any known application for these
105 - Nested 'switch' commands are avoided by using more functions.
106 Dangerous 'fall-through's avoided.
107 - Commands and functions are sorted alphabetically (where possible).
108 - Dynamic arrays/buffers are now implemented as container classes.
109 - Some functions had an ugly return structure; this has been
110 streamlined by using classes.
111 - Use standard C math functions for number handling, so getting rid
112 of differences to '0'.
113 - The macro 'IntArg' has been created for an easier transition
114 to guaranteed 32 bits integers ('int' is enough for GNU, while
115 ANSI only guarantees 'long int' to have a length of 32 bits).
116 - The many usages of type 'int' are differentiated by using 'Char',
117 'bool', and 'IntArg' where appropriate.
118 - To ease the calls of the local utility functions, the parser
119 variables 'current_file', 'npages', and 'current_env'
120 (formerly env) were made global to the file (formerly they were
121 local to the do_file() function)
122 - Various comments were added.
125 - Get rid of the stupid drawing positioning.
126 - Can the 'Dt' command be completely handled by setting environment
127 within do_file() instead of sending to pr?
128 - Integer arguments must be >= 32 bits, use conditional #define.
129 - Add scaling facility for classical device independence and
130 non-groff devices. Classical troff output had a quasi device
131 independence by scaling the intermediate output to the resolution
132 of the postprocessor device if different from the one specified
133 with 'x T', groff have not. So implement full quasi device
134 independence, including the mapping of the strange classical
135 devices to the postprocessor device (seems to be reasonably
137 - The external, global pointer variables are not optimally handled.
138 - The global variables 'current_filename',
139 'current_source_filename', and 'current_lineno' are only used for
140 error reporting. So implement a static class 'Error'
142 - The global 'device' is the name used during the formatting
143 process; there should be a new variable for the device name used
144 during the postprocessing.
145 - Implement the B-spline drawing 'D~' for all graphical devices.
146 - Make 'environment' a class with an overflow check for its members
147 and a delete method to get rid of delete_current_env().
148 - Implement the 'EnvStack' to use 'new' instead of 'malloc'.
149 - The class definitions of this document could go into a new file.
150 - The comments in this section should go to a 'Changelog' or some
151 'README' file in this directory.
155 Discussion of the positioning by drawing commands
157 There was some confusion about the positioning of the graphical
158 pointer at the printout after having executed a 'D' command.
159 The classical troff manual of Ossanna & Kernighan specified,
161 'The position after a graphical object has been drawn is
162 at its end; for circles and ellipses, the "end" is at the
165 From this, it follows that
166 - all open figures (args, splines, and lines) should position at their
168 - all circles and ellipses should position at their right-most point
169 (as if 2 halves had been drawn).
170 - all closed figures apart from circles and ellipses shouldn't change
171 the position because they return to their origin.
172 - all setting commands should not change position because they do not
173 draw any graphical object.
175 In the case of the open figures, this means that the horizontal
176 displacement is the sum of all odd arguments and the vertical offset
177 the sum of all even arguments, called the alternate arguments sum
178 displacement in the following.
180 Unfortunately, groff did not implement this simple rule. The former
181 documentation in groff_out(5) differed from the source code, and
182 neither of them is compatible with the classical rule.
184 The former groff_out(5) specified to use the alternative arguments
185 sum displacement for calculating the drawing positioning of
186 non-classical commands, including the 'Dt' command (setting-only)
187 and closed polygons. Applying this to the new groff color commands
188 will lead to disaster. For their arguments can take large values (>
189 65000). On low resolution devices, the displacement of such large
190 values will corrupt the display or kill the printer. So the
191 nonsense specification has come to a natural end anyway.
193 The groff source code, however, had no positioning for the
194 setting-only commands (esp. 'Dt'), the right-end positioning for
195 outlined circles and ellipses, and the alternative argument sum
196 displacement for all other commands (including filled circles and
199 The reason why no one seems to have suffered from this mayhem so
200 far is that the graphical objects are usually generated by
201 preprocessors like pic that do not depend on the automatic
202 positioning. When using the low level '\D' escape sequences or 'D'
203 output commands, the strange positionings can be circumvented by
204 absolute positionings or by tricks like '\Z'.
206 So doing an exorcism on the strange, incompatible displacements might
207 not harm any existing documents, but will make the usage of the
208 graphical escape sequences and commands natural.
210 That's why the rewrite of this file returned to the reasonable,
211 classical specification with its clear end-of-drawing rule that is
212 suitable for all cases. But a macro STUPID_DRAWING_POSITIONING is
213 provided for testing the funny former behavior.
215 The new rule implies the following behavior.
216 - Setting commands ('Dt', 'Df', 'DF') and polygons ('Dp' and 'DP')
217 do not change position now.
218 - Filled circles and ellipses ('DC' and 'DE') position at their
219 most right point (outlined ones 'Dc' and 'De' did this anyway).
220 - As before, all open graphical objects position to their final
221 drawing point (alternate sum of the command arguments).
225 #ifndef STUPID_DRAWING_POSITIONING
226 // uncomment next line if all non-classical D commands shall position
227 // to the strange alternate sum of args displacement
228 #define STUPID_DRAWING_POSITIONING
231 // Decide whether the commands '{' and '}' for different environments
244 /**********************************************************************
246 **********************************************************************/
248 // integer type used in the fields of struct environment (see printer.h)
251 // integer arguments of groff_out commands, must be >= 32 bits
254 // color components of groff_out color commands, must be >= 32 bits
255 typedef unsigned int ColorArg;
257 // Array for IntArg values.
259 size_t num_allocated;
264 IntArray(const size_t);
266 IntArg operator[](const size_t i) const
269 fatal("index out of range");
270 return (IntArg) data[i];
273 IntArg *get_data(void) const { return (IntArg *)data; }
274 size_t len(void) const { return num_stored; }
277 // Characters read from the input queue.
281 Char(void) : data('\0') {}
282 Char(const int c) : data(c) {}
283 bool operator==(char c) const { return (data == c) ? true : false; }
284 bool operator==(int c) const { return (data == c) ? true : false; }
285 bool operator==(const Char c) const
286 { return (data == c.data) ? true : false; }
287 bool operator!=(char c) const { return !(*this == c); }
288 bool operator!=(int c) const { return !(*this == c); }
289 bool operator!=(const Char c) const { return !(*this == c); }
290 operator int() const { return (int) data; }
291 operator unsigned char() const { return (unsigned char) data; }
292 operator char() const { return (char) data; }
295 // Buffer for string arguments (Char, not char).
297 size_t num_allocated;
299 Char *data; // not terminated by '\0'
301 StringBuf(void); // allocate without storing
303 void append(const Char); // append character to 'data'
304 char *make_string(void); // return new copy of 'data' with '\0'
305 bool is_empty(void) { // true if none stored
306 return (num_stored > 0) ? false : true;
308 void reset(void); // set 'num_stored' to 0
314 size_t num_allocated;
319 environment *pop(void);
320 void push(environment *e);
322 #endif // USE_ENV_STACK
325 /**********************************************************************
327 **********************************************************************/
329 // exported as extern by error.h (called from driver.h)
330 // needed for error messages (see ../libgroff/error.cpp)
331 const char *current_filename = 0; // printable name of the current file
332 // printable name of current source file
333 const char *current_source_filename = 0;
334 int current_lineno = 0; // current line number of printout
336 // exported as extern by device.h;
337 const char *device = 0; // cancel former init with literal
343 // We rely on an implementation of the 'new' operator which aborts
344 // gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
347 /**********************************************************************
348 static local variables
349 **********************************************************************/
351 FILE *current_file = 0; // current input stream for parser
353 // npages: number of pages processed so far (including current page),
354 // _not_ the page number in the printout (can be set with 'p').
358 COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
361 INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
363 // parser environment, created and deleted by each run of do_file()
364 environment *current_env = 0;
368 envp_size = sizeof(environment *);
369 #endif // USE_ENV_STACK
372 /**********************************************************************
373 function declarations
374 **********************************************************************/
377 ColorArg color_from_Df_command(IntArg);
378 // transform old color into new
379 void delete_current_env(void); // delete global var current_env
380 void fatal_command(char); // abort for invalid command
381 inline Char get_char(void); // read next character from input stream
382 ColorArg get_color_arg(void); // read in argument for new color cmds
383 IntArray *get_D_fixed_args(const size_t);
384 // read in fixed number of integer
386 IntArray *get_D_fixed_args_odd_dummy(const size_t);
387 // read in a fixed number of integer
388 // arguments plus optional dummy
389 IntArray *get_D_variable_args(void);
390 // variable, even number of int args
391 char *get_extended_arg(void); // argument for 'x X' (several lines)
392 IntArg get_integer_arg(void); // read in next integer argument
393 IntArray *get_possibly_integer_args();
394 // 0 or more integer arguments
395 char *get_string_arg(void); // read in next string arg, ended by WS
396 inline bool is_space_or_tab(const Char);
397 // test on space/tab char
398 Char next_arg_begin(void); // skip white space on current line
399 Char next_command(void); // go to next command, evt. diff. line
400 inline bool odd(const int); // test if integer is odd
401 void position_to_end_of_args(const IntArray * const);
402 // positioning after drawing
403 void remember_filename(const char *);
404 // set global current_filename
405 void remember_source_filename(const char *);
406 // set global current_source_filename
407 void send_draw(const Char, const IntArray * const);
409 void skip_line(void); // unconditionally skip to next line
410 bool skip_line_checked(void); // skip line, false if args are left
411 void skip_line_fatal(void); // skip line, fatal if args are left
412 void skip_line_warn(void); // skip line, warn if args are left
413 void skip_line_D(void); // skip line in D commands
414 void skip_line_x(void); // skip line in x commands
415 void skip_to_end_of_line(void); // skip to the end of the current line
416 inline void unget_char(const Char);
417 // restore character onto input
419 // parser subcommands
420 void parse_color_command(color *);
421 // color sub(sub)commands m and DF
422 void parse_D_command(void); // graphical subcommands
423 bool parse_x_command(void); // device controller subcommands
426 /**********************************************************************
428 **********************************************************************/
431 EnvStack::EnvStack(void)
434 // allocate pointer to array of num_allocated pointers to environment
435 data = (environment **)malloc(envp_size * num_allocated);
437 fatal("could not allocate environment data");
441 EnvStack::~EnvStack(void)
443 for (size_t i = 0; i < num_stored; i++)
448 // return top element from stack and decrease stack pointer
450 // the calling function must take care of properly deleting the result
455 environment *result = data[num_stored];
456 data[num_stored] = 0;
460 // copy argument and push this onto the stack
462 EnvStack::push(environment *e)
464 environment *e_copy = new environment;
465 if (num_stored >= num_allocated) {
466 environment **old_data = data;
468 data = (environment **)malloc(envp_size * num_allocated);
470 fatal("could not allocate data");
471 for (size_t i = 0; i < num_stored; i++)
472 data[i] = old_data[i];
475 e_copy->col = new color;
476 e_copy->fill = new color;
477 *e_copy->col = *e->col;
478 *e_copy->fill = *e->fill;
479 e_copy->fontno = e->fontno;
480 e_copy->height = e->height;
481 e_copy->hpos = e->hpos;
482 e_copy->size = e->size;
483 e_copy->slant = e->slant;
484 e_copy->vpos = e->vpos;
485 data[num_stored] = e_copy;
488 #endif // USE_ENV_STACK
490 IntArray::IntArray(void)
493 data = new IntArg[num_allocated];
497 IntArray::IntArray(const size_t n)
500 fatal("number of integers to be allocated must be > 0");
502 data = new IntArg[num_allocated];
506 IntArray::~IntArray(void)
512 IntArray::append(IntArg x)
514 if (num_stored >= num_allocated) {
515 IntArg *old_data = data;
517 data = new IntArg[num_allocated];
518 for (size_t i = 0; i < num_stored; i++)
519 data[i] = old_data[i];
522 data[num_stored] = x;
526 StringBuf::StringBuf(void)
530 data = new Char[num_allocated];
533 StringBuf::~StringBuf(void)
539 StringBuf::append(const Char c)
541 if (num_stored >= num_allocated) {
542 Char *old_data = data;
544 data = new Char[num_allocated];
545 for (size_t i = 0; i < num_stored; i++)
546 data[i] = old_data[i];
549 data[num_stored] = c;
554 StringBuf::make_string(void)
556 char *result = new char[num_stored + 1];
557 for (size_t i = 0; i < num_stored; i++)
558 result[i] = (char) data[i];
559 result[num_stored] = '\0';
564 StringBuf::reset(void)
569 /**********************************************************************
571 **********************************************************************/
573 //////////////////////////////////////////////////////////////////////
574 /* color_from_Df_command:
575 Process the gray shade setting command Df.
577 Transform Df style color into DF style color.
578 Df color: 0-1000, 0 is white
579 DF color: 0-65536, 0 is black
581 The Df command is obsoleted by command DFg, but kept for
585 color_from_Df_command(IntArg Df_gray)
587 return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
590 //////////////////////////////////////////////////////////////////////
591 /* delete_current_env():
592 Delete global variable current_env and its pointer members.
594 This should be a class method of environment.
596 void delete_current_env(void)
598 delete current_env->col;
599 delete current_env->fill;
604 //////////////////////////////////////////////////////////////////////
606 Emit error message about invalid command and abort.
609 fatal_command(char command)
611 fatal("'%1' command invalid before first 'p' command", command);
614 //////////////////////////////////////////////////////////////////////
616 Retrieve the next character from the input queue.
618 Return: The retrieved character (incl. EOF), converted to Char.
623 return (Char) getc(current_file);
626 //////////////////////////////////////////////////////////////////////
628 Retrieve an argument suitable for the color commands m and DF.
630 Return: The retrieved color argument.
635 IntArg x = get_integer_arg();
636 if (x < 0 || x > (IntArg)COLORARG_MAX) {
637 error("color component argument out of range");
643 //////////////////////////////////////////////////////////////////////
644 /* get_D_fixed_args():
645 Get a fixed number of integer arguments for D commands.
647 Fatal if wrong number of arguments.
648 Too many arguments on the line raise a warning.
651 number: In-parameter, the number of arguments to be retrieved.
652 ignore: In-parameter, ignore next argument -- GNU troff always emits
653 pairs of parameters for 'D' extensions added by groff.
656 Return: New IntArray containing the arguments.
659 get_D_fixed_args(const size_t number)
662 fatal("requested number of arguments must be > 0");
663 IntArray *args = new IntArray(number);
664 for (size_t i = 0; i < number; i++)
665 args->append(get_integer_arg());
670 //////////////////////////////////////////////////////////////////////
671 /* get_D_fixed_args_odd_dummy():
672 Get a fixed number of integer arguments for D commands and optionally
673 ignore a dummy integer argument if the requested number is odd.
675 The gtroff program adds a dummy argument to some commands to get
676 an even number of arguments.
677 Error if the number of arguments differs from the scheme above.
680 number: In-parameter, the number of arguments to be retrieved.
682 Return: New IntArray containing the arguments.
685 get_D_fixed_args_odd_dummy(const size_t number)
688 fatal("requested number of arguments must be > 0");
689 IntArray *args = new IntArray(number);
690 for (size_t i = 0; i < number; i++)
691 args->append(get_integer_arg());
693 IntArray *a = get_possibly_integer_args();
695 error("too many arguments");
702 //////////////////////////////////////////////////////////////////////
703 /* get_D_variable_args():
704 Get a variable even number of integer arguments for D commands.
706 Get as many integer arguments as possible from the rest of the
708 - The arguments are separated by an arbitrary sequence of space or
710 - A comment, a newline, or EOF indicates the end of processing.
711 - Error on non-digit characters different from these.
712 - A final line skip is performed (except for EOF).
714 Return: New IntArray of the retrieved arguments.
717 get_D_variable_args()
719 IntArray *args = get_possibly_integer_args();
720 size_t n = args->len();
722 error("no arguments found");
724 error("even number of arguments expected");
729 //////////////////////////////////////////////////////////////////////
730 /* get_extended_arg():
731 Retrieve extended arg for 'x X' command.
733 - Skip leading spaces and tabs, error on EOL or newline.
734 - Return everything before the next NL or EOF ('#' is not a comment);
735 as long as the following line starts with '+' this is returned
736 as well, with the '+' replaced by a newline.
737 - Final line skip is always performed.
739 Return: Allocated (new) string of retrieved text argument.
742 get_extended_arg(void)
744 StringBuf buf = StringBuf();
745 Char c = next_arg_begin();
746 while ((int) c != EOF) {
747 if ((int) c == '\n') {
751 buf.append((Char) '\n');
753 unget_char(c); // first character of next line
761 return buf.make_string();
764 //////////////////////////////////////////////////////////////////////
765 /* get_integer_arg(): Retrieve integer argument.
767 Skip leading spaces and tabs, collect an optional '-' and all
768 following decimal digits (at least one) up to the next non-digit,
769 which is restored onto the input queue.
771 Fatal error on all other situations.
773 Return: Retrieved integer.
776 get_integer_arg(void)
778 StringBuf buf = StringBuf();
779 Char c = next_arg_begin();
780 if ((int) c == '-') {
784 if (!isdigit((int) c))
785 fatal("integer argument expected");
786 while (isdigit((int) c)) {
792 char *s = buf.make_string();
794 long int number = strtol(s, 0, 10);
796 || number > INTARG_MAX || number < -INTARG_MAX) {
797 error("integer argument too large");
801 return (IntArg) number;
804 //////////////////////////////////////////////////////////////////////
805 /* get_possibly_integer_args():
806 Parse the rest of the input line as a list of integer arguments.
808 Get as many integer arguments as possible from the rest of the
809 current line, even none.
810 - The arguments are separated by an arbitrary sequence of space or
812 - A comment, a newline, or EOF indicates the end of processing.
813 - Error on non-digit characters different from these.
814 - No line skip is performed.
816 Return: New IntArray of the retrieved arguments.
819 get_possibly_integer_args()
822 StringBuf buf = StringBuf();
824 IntArray *args = new IntArray();
827 while (is_space_or_tab(c))
830 Char c1 = get_char();
831 if (isdigit((int) c1)) {
838 while (isdigit((int) c)) {
842 if (!buf.is_empty()) {
843 char *s = buf.make_string();
845 long int x = strtol(s, 0, 10);
847 || x > INTARG_MAX || x < -INTARG_MAX) {
848 error("invalid integer argument, set to 0");
851 args->append((IntArg) x);
854 // Here, c is not a digit.
855 // Terminate on comment, end of line, or end of file, while
856 // space or tab indicate continuation; otherwise error.
859 skip_to_end_of_line();
873 error("integer argument expected");
881 //////////////////////////////////////////////////////////////////////
885 - Skip leading spaces and tabs; error on EOL or newline.
886 - Return all following characters before the next space, tab,
887 newline, or EOF character (in-word '#' is not a comment character).
888 - The terminating space, tab, newline, or EOF character is restored
889 onto the input queue, so no line skip.
891 Return: Retrieved string as char *, allocated by 'new'.
896 StringBuf buf = StringBuf();
897 Char c = next_arg_begin();
898 while (!is_space_or_tab(c)
899 && c != Char('\n') && c != Char(EOF)) {
903 unget_char(c); // restore white space
904 return buf.make_string();
907 //////////////////////////////////////////////////////////////////////
908 /* is_space_or_tab():
909 Test a character if it is a space or tab.
911 c: In-parameter, character to be tested.
913 Return: True, if c is a space or tab character, false otherwise.
916 is_space_or_tab(const Char c)
918 return (c == Char(' ') || c == Char('\t')) ? true : false;
921 //////////////////////////////////////////////////////////////////////
923 Return first character of next argument.
925 Skip space and tab characters; error on newline or EOF.
927 Return: The first character different from these (including '#').
941 error("missing argument");
943 default: // first essential character
949 //////////////////////////////////////////////////////////////////////
951 Find the first character of the next command.
953 Skip spaces, tabs, comments (introduced by #), and newlines.
955 Return: The first character different from these (including EOF).
973 default: // EOF or first essential character
979 //////////////////////////////////////////////////////////////////////
981 Test whether argument is an odd number.
983 n: In-parameter, the integer to be tested.
985 Return: True if odd, false otherwise.
990 return ((n & 1) == 1) ? true : false;
993 //////////////////////////////////////////////////////////////////////
994 /* position_to_end_of_args():
995 Move graphical pointer to end of drawn figure.
997 This is used by the D commands that draw open geometrical figures.
998 The algorithm simply sums up all horizontal displacements (arguments
999 with even number) for the horizontal component. Similarly, the
1000 vertical component is the sum of the odd arguments.
1002 args: In-parameter, the arguments of a former drawing command.
1005 position_to_end_of_args(const IntArray * const args)
1008 const size_t n = args->len();
1009 for (i = 0; i < n; i += 2)
1010 current_env->hpos += (*args)[i];
1011 for (i = 1; i < n; i += 2)
1012 current_env->vpos += (*args)[i];
1015 //////////////////////////////////////////////////////////////////////
1016 /* remember_filename():
1017 Set global variable current_filename.
1019 The actual filename is stored in current_filename. This is used by
1020 the postprocessors, expecting the name "<standard input>" for stdin.
1022 filename: In-out-parameter; is changed to the new value also.
1025 remember_filename(const char *filename)
1028 if (strcmp(filename, "-") == 0)
1029 fname = (char *)"<standard input>";
1031 fname = (char *)filename;
1032 size_t len = strlen(fname) + 1;
1033 if (current_filename != 0)
1034 free((char *)current_filename);
1035 current_filename = (const char *)malloc(len);
1036 if (current_filename == 0)
1037 fatal("can't malloc space for filename");
1038 strncpy((char *)current_filename, (char *)fname, len);
1041 //////////////////////////////////////////////////////////////////////
1042 /* remember_source_filename():
1043 Set global variable current_source_filename.
1045 The actual filename is stored in current_filename. This is used by
1046 the postprocessors, expecting the name "<standard input>" for stdin.
1048 filename: In-out-parameter; is changed to the new value also.
1051 remember_source_filename(const char *filename)
1054 if (strcmp(filename, "-") == 0)
1055 fname = (char *)"<standard input>";
1057 fname = (char *)filename;
1058 size_t len = strlen(fname) + 1;
1059 if (current_source_filename != 0)
1060 free((char *)current_source_filename);
1061 current_source_filename = (const char *)malloc(len);
1062 if (current_source_filename == 0)
1063 fatal("can't malloc space for filename");
1064 strncpy((char *)current_source_filename, (char *)fname, len);
1067 //////////////////////////////////////////////////////////////////////
1069 Call draw method of printer class.
1071 subcmd: Letter of actual D subcommand.
1072 args: Array of integer arguments of actual D subcommand.
1075 send_draw(const Char subcmd, const IntArray * const args)
1077 EnvInt n = (EnvInt) args->len();
1078 pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
1081 //////////////////////////////////////////////////////////////////////
1083 Go to next line within the input queue.
1085 Skip the rest of the current line, including the newline character.
1086 The global variable current_lineno is adjusted.
1087 No errors are raised.
1092 Char c = get_char();
1104 //////////////////////////////////////////////////////////////////////
1105 /* skip_line_checked ():
1106 Check that there aren't any arguments left on the rest of the line,
1109 Spaces, tabs, and a comment are allowed before newline or EOF.
1110 All other characters raise an error.
1113 skip_line_checked(void)
1116 Char c = get_char();
1117 while (is_space_or_tab(c))
1120 case '#': // comment
1136 //////////////////////////////////////////////////////////////////////
1137 /* skip_line_fatal ():
1138 Fatal error if arguments left, otherwise skip line.
1140 Spaces, tabs, and a comment are allowed before newline or EOF.
1141 All other characters trigger the error.
1144 skip_line_fatal(void)
1146 bool ok = skip_line_checked();
1149 error("too many arguments");
1154 //////////////////////////////////////////////////////////////////////
1155 /* skip_line_warn ():
1156 Skip line, but warn if arguments are left on actual line.
1158 Spaces, tabs, and a comment are allowed before newline or EOF.
1159 All other characters raise a warning
1162 skip_line_warn(void)
1164 bool ok = skip_line_checked();
1167 warning("too many arguments on current line");
1172 //////////////////////////////////////////////////////////////////////
1174 Skip line in 'D' commands.
1176 Decide whether in case of an additional argument a fatal error is
1177 raised (the documented classical behavior), only a warning is
1178 issued, or the line is just skipped (former groff behavior).
1179 Actually decided for the warning.
1185 // or: skip_line_fatal();
1189 //////////////////////////////////////////////////////////////////////
1191 Skip line in 'x' commands.
1193 Decide whether in case of an additional argument a fatal error is
1194 raised (the documented classical behavior), only a warning is
1195 issued, or the line is just skipped (former groff behavior).
1196 Actually decided for the warning.
1202 // or: skip_line_fatal();
1206 //////////////////////////////////////////////////////////////////////
1207 /* skip_to_end_of_line():
1208 Go to the end of the current line.
1210 Skip the rest of the current line, excluding the newline character.
1211 The global variable current_lineno is not changed.
1212 No errors are raised.
1215 skip_to_end_of_line(void)
1217 Char c = get_char();
1229 //////////////////////////////////////////////////////////////////////
1231 Restore character c onto input queue.
1233 Write a character back onto the input stream.
1234 EOF is gracefully handled.
1236 c: In-parameter; character to be pushed onto the input queue.
1239 unget_char(const Char c)
1243 if (ungetc(ch, current_file) == EOF)
1244 fatal("could not unget character");
1249 /**********************************************************************
1251 **********************************************************************/
1253 //////////////////////////////////////////////////////////////////////
1254 /* parse_color_command:
1255 Process the commands m and DF, but not Df.
1257 col: In-out-parameter; the color object to be set, must have
1258 been initialized before.
1261 parse_color_command(color *col)
1264 ColorArg red = 0, green = 0, blue = 0;
1265 ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
1266 Char subcmd = next_arg_begin();
1267 switch((int) subcmd) {
1268 case 'c': // DFc or mc: CMY
1269 cyan = get_color_arg();
1270 magenta = get_color_arg();
1271 yellow = get_color_arg();
1272 col->set_cmy(cyan, magenta, yellow);
1274 case 'd': // DFd or md: set default color
1277 case 'g': // DFg or mg: gray
1278 gray = get_color_arg();
1279 col->set_gray(gray);
1281 case 'k': // DFk or mk: CMYK
1282 cyan = get_color_arg();
1283 magenta = get_color_arg();
1284 yellow = get_color_arg();
1285 black = get_color_arg();
1286 col->set_cmyk(cyan, magenta, yellow, black);
1288 case 'r': // DFr or mr: RGB
1289 red = get_color_arg();
1290 green = get_color_arg();
1291 blue = get_color_arg();
1292 col->set_rgb(red, green, blue);
1295 error("invalid color scheme '%1'", (int) subcmd);
1297 } // end of color subcommands
1300 //////////////////////////////////////////////////////////////////////
1301 /* parse_D_command():
1302 Parse the subcommands of graphical command D.
1304 This is the part of the do_file() parser that scans the graphical
1306 - Error on lacking or wrong arguments.
1307 - Warning on too many arguments.
1308 - Line is always skipped.
1313 Char subcmd = next_arg_begin();
1314 switch((int) subcmd) {
1315 case '~': // D~: draw B-spline
1316 // actually, this isn't available for some postprocessors
1318 default: // unknown options are passed to device
1320 IntArray *args = get_D_variable_args();
1321 send_draw(subcmd, args);
1322 position_to_end_of_args(args);
1326 case 'a': // Da: draw arc
1328 IntArray *args = get_D_fixed_args(4);
1329 send_draw(subcmd, args);
1330 position_to_end_of_args(args);
1334 case 'c': // Dc: draw circle line
1336 IntArray *args = get_D_fixed_args(1);
1337 send_draw(subcmd, args);
1338 // move to right end
1339 current_env->hpos += (*args)[0];
1343 case 'C': // DC: draw solid circle
1345 IntArray *args = get_D_fixed_args_odd_dummy(1);
1346 send_draw(subcmd, args);
1347 // move to right end
1348 current_env->hpos += (*args)[0];
1352 case 'e': // De: draw ellipse line
1353 case 'E': // DE: draw solid ellipse
1355 IntArray *args = get_D_fixed_args(2);
1356 send_draw(subcmd, args);
1357 // move to right end
1358 current_env->hpos += (*args)[0];
1362 case 'f': // Df: set fill gray; obsoleted by DFg
1364 IntArg arg = get_integer_arg();
1365 if ((arg >= 0) && (arg <= 1000)) {
1366 // convert arg and treat it like DFg
1367 ColorArg gray = color_from_Df_command(arg);
1368 current_env->fill->set_gray(gray);
1371 // set fill color to the same value as the current outline color
1372 delete current_env->fill;
1373 current_env->fill = new color(current_env->col);
1375 pr->change_fill_color(current_env);
1376 // skip unused 'vertical' component (\D'...' always emits pairs)
1377 (void) get_integer_arg();
1378 # ifdef STUPID_DRAWING_POSITIONING
1379 current_env->hpos += arg;
1384 case 'F': // DF: set fill color, several formats
1385 parse_color_command(current_env->fill);
1386 pr->change_fill_color(current_env);
1387 // no positioning (setting-only command)
1390 case 'l': // Dl: draw line
1392 IntArray *args = get_D_fixed_args(2);
1393 send_draw(subcmd, args);
1394 position_to_end_of_args(args);
1398 case 'p': // Dp: draw closed polygon line
1399 case 'P': // DP: draw solid closed polygon
1401 IntArray *args = get_D_variable_args();
1402 send_draw(subcmd, args);
1403 # ifdef STUPID_DRAWING_POSITIONING
1404 // final args positioning
1405 position_to_end_of_args(args);
1410 case 't': // Dt: set line thickness
1412 IntArray *args = get_D_fixed_args_odd_dummy(1);
1413 send_draw(subcmd, args);
1414 # ifdef STUPID_DRAWING_POSITIONING
1415 // final args positioning
1416 position_to_end_of_args(args);
1421 } // end of D subcommands
1424 //////////////////////////////////////////////////////////////////////
1425 /* parse_x_command():
1426 Parse subcommands of the device control command x.
1428 This is the part of the do_file() parser that scans the device
1429 controlling commands.
1430 - Error on duplicate prologue commands.
1431 - Error on wrong or lacking arguments.
1432 - Warning on too many arguments.
1433 - Line is always skipped.
1436 - current_env: is set by many subcommands.
1437 - npages: page counting variable
1439 Return: boolean in the meaning of 'stopped'
1440 - true if parsing should be stopped ('x stop').
1441 - false if parsing should continue.
1444 parse_x_command(void)
1446 bool stopped = false;
1447 char *subcmd_str = get_string_arg();
1448 char subcmd = subcmd_str[0];
1450 case 'f': // x font: mount font
1452 IntArg n = get_integer_arg();
1453 char *name = get_string_arg();
1454 pr->load_font(n, name);
1459 case 'F': // x Filename: set filename for errors
1461 char *str_arg = get_extended_arg();
1463 warning("empty argument for 'x F' command");
1465 remember_source_filename(str_arg);
1470 case 'H': // x Height: set character height
1471 current_env->height = get_integer_arg();
1472 if (current_env->height == current_env->size)
1473 current_env->height = 0;
1476 case 'i': // x init: initialize device
1477 error("duplicate 'x init' command");
1480 case 'p': // x pause: pause device
1483 case 'r': // x res: set resolution
1484 error("duplicate 'x res' command");
1487 case 's': // x stop: stop device
1491 case 'S': // x Slant: set slant
1492 current_env->slant = get_integer_arg();
1495 case 't': // x trailer: generate trailer info
1498 case 'T': // x Typesetter: set typesetter
1499 error("duplicate 'x T' command");
1502 case 'u': // x underline: from .cu
1504 char *str_arg = get_string_arg();
1505 pr->special(str_arg, current_env, 'u');
1510 case 'X': // x X: send uninterpretedly to device
1512 char *str_arg = get_extended_arg(); // includes line skip
1514 error("'x X' command invalid before first 'p' command");
1515 else if (str_arg && (strncmp(str_arg, "devtag:",
1516 strlen("devtag:")) == 0))
1517 pr->devtag(str_arg, current_env);
1519 pr->special(str_arg, current_env);
1523 default: // ignore unknown x commands, but warn
1524 warning("unknown command 'x %1'", subcmd);
1527 delete[] subcmd_str;
1532 /**********************************************************************
1533 exported part (by driver.h)
1534 **********************************************************************/
1536 ////////////////////////////////////////////////////////////////////////
1538 Parse and postprocess groff intermediate output.
1540 filename: "-" for standard input, normal file name otherwise
1543 do_file(const char *filename)
1546 bool stopped = false; // terminating condition
1548 #ifdef USE_ENV_STACK
1549 EnvStack env_stack = EnvStack();
1550 #endif // USE_ENV_STACK
1552 // setup of global variables
1555 // 'pr' is initialized after the prologue.
1556 // 'device' is set by the 1st prologue command.
1558 if (filename[0] == '-' && filename[1] == '\0')
1559 current_file = stdin;
1562 current_file = fopen(filename, "r");
1563 if (errno != 0 || current_file == 0) {
1564 error("can't open file '%1'", filename);
1568 remember_filename(filename);
1570 if (current_env != 0)
1571 delete_current_env();
1572 current_env = new environment;
1573 current_env->col = new color;
1574 current_env->fill = new color;
1575 current_env->fontno = -1;
1576 current_env->height = 0;
1577 current_env->hpos = -1;
1578 current_env->slant = 0;
1579 current_env->size = 0;
1580 current_env->vpos = -1;
1582 // parsing of prologue (first 3 commands)
1587 // 1st command 'x T'
1588 command = next_command();
1589 if ((int) command == EOF)
1591 if ((int) command != 'x')
1592 fatal("the first command must be 'x T'");
1593 str_arg = get_string_arg();
1594 if (str_arg[0] != 'T')
1595 fatal("the first command must be 'x T'");
1597 char *tmp_dev = get_string_arg();
1598 if (pr == 0) { // note: 'pr' initialized after prologue
1600 if (0 /* nullptr */ == font::load_desc())
1601 fatal("cannot load description of '%1' device", tmp_dev);
1604 if (device == 0 || strcmp(device, tmp_dev) != 0)
1605 fatal("all files must use the same device");
1608 skip_line_x(); // ignore further arguments
1609 current_env->size = 10 * font::sizescale;
1611 // 2nd command 'x res'
1612 command = next_command();
1613 if ((int) command != 'x')
1614 fatal("the second command must be 'x res'");
1615 str_arg = get_string_arg();
1616 if (str_arg[0] != 'r')
1617 fatal("the second command must be 'x res'");
1619 int_arg = get_integer_arg();
1620 EnvInt font_res = font::res;
1621 if (int_arg != font_res)
1622 fatal("resolution does not match");
1623 int_arg = get_integer_arg();
1624 if (int_arg != font::hor)
1625 fatal("minimum horizontal motion does not match");
1626 int_arg = get_integer_arg();
1627 if (int_arg != font::vert)
1628 fatal("minimum vertical motion does not match");
1629 skip_line_x(); // ignore further arguments
1631 // 3rd command 'x init'
1632 command = next_command();
1634 fatal("the third command must be 'x init'");
1635 str_arg = get_string_arg();
1636 if (str_arg[0] != 'i')
1637 fatal("the third command must be 'x init'");
1644 pr = make_printer();
1646 command = next_command();
1649 // spaces, tabs, comments, and newlines are skipped here
1650 switch ((int) command) {
1651 case '#': // #: comment, ignore up to end of line
1654 #ifdef USE_ENV_STACK
1655 case '{': // {: start a new environment (a copy)
1656 env_stack.push(current_env);
1658 case '}': // }: pop previous env from stack
1659 delete_current_env();
1660 current_env = env_stack.pop();
1662 #endif // USE_ENV_STACK
1663 case '0': // ddc: obsolete jump and print command
1673 { // expect 2 digits and a character
1675 Char c = next_arg_begin();
1677 fatal_command(command);
1678 if (!isdigit((int) c)) {
1679 error("digit expected");
1682 s[0] = (char) command;
1686 long int x = strtol(s, 0, 10);
1688 error("couldn't convert 2 digits");
1689 EnvInt hor_pos = (EnvInt) x;
1690 current_env->hpos += hor_pos;
1691 c = next_arg_begin();
1692 if ((int) c == '\n' || (int) c == EOF)
1693 error("character argument expected");
1695 pr->set_ascii_char((unsigned char) c, current_env);
1698 case 'c': // c: print ascii char without moving
1701 fatal_command(command);
1702 Char c = next_arg_begin();
1703 if (c == '\n' || c == EOF)
1704 error("missing argument to 'c' command");
1706 pr->set_ascii_char((unsigned char) c, current_env);
1709 case 'C': // C: print named special character
1712 fatal_command(command);
1713 char *str_arg = get_string_arg();
1714 pr->set_special_char(str_arg, current_env);
1718 case 'D': // drawing commands
1720 fatal_command(command);
1723 case 'f': // f: set font to number
1724 current_env->fontno = get_integer_arg();
1726 case 'F': // F: obsolete, replaced by 'x F'
1728 char *str_arg = get_extended_arg();
1729 remember_source_filename(str_arg);
1733 case 'h': // h: relative horizontal move
1735 fatal_command(command);
1736 current_env->hpos += (EnvInt) get_integer_arg();
1738 case 'H': // H: absolute horizontal positioning
1740 fatal_command(command);
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
1805 fatal_command(command);
1806 current_env->vpos += (EnvInt) get_integer_arg();
1808 case 'V': // V: absolute vertical positioning
1810 fatal_command(command);
1811 current_env->vpos = (EnvInt) get_integer_arg();
1813 case 'w': // w: inform about paddable space
1815 case 'x': // device controlling commands
1816 stopped = parse_x_command();
1819 warning("unrecognized command '%1'", (unsigned char) command);
1825 // end of file reached
1827 pr->end_page(current_env->vpos);
1830 fclose(current_file);
1831 // If 'stopped' is not 'true' here then there wasn't any 'x stop'.
1833 warning("no final 'x stop' command");
1834 delete_current_env();
1841 // vim: set cindent noexpandtab shiftwidth=2 textwidth=72: