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