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