| Commit | Line | Data |
|---|---|---|
| 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) | |
| 257 | typedef int EnvInt; | |
| 258 | ||
| 259 | // integer arguments of groff_out commands, must be >= 32 bits | |
| 260 | typedef int IntArg; | |
| 261 | ||
| 262 | // color components of groff_out color commands, must be >= 32 bits | |
| 263 | typedef unsigned int ColorArg; | |
| 264 | ||
| 265 | // Array for IntArg values. | |
| 266 | class IntArray { | |
| 267 | size_t num_allocated; | |
| 268 | size_t num_stored; | |
| 269 | IntArg *data; | |
| 270 | public: | |
| 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. | |
| 286 | class Char { | |
| 287 | int data; | |
| 288 | public: | |
| 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). | |
| 304 | class StringBuf { | |
| 305 | size_t num_allocated; | |
| 306 | size_t num_stored; | |
| 307 | Char *data; // not terminated by '\0' | |
| 308 | public: | |
| 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 | |
| 320 | class EnvStack { | |
| 321 | environment **data; | |
| 322 | size_t num_allocated; | |
| 323 | size_t num_stored; | |
| 324 | public: | |
| 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) | |
| 339 | const char *current_filename = 0; // printable name of the current file | |
| 340 | // printable name of current source file | |
| 341 | const char *current_source_filename = 0; | |
| 342 | int current_lineno = 0; // current line number of printout | |
| 343 | ||
| 344 | // exported as extern by device.h; | |
| 345 | const char *device = 0; // cancel former init with literal | |
| 346 | ||
| 347 | printer *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 | ||
| 359 | FILE *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'). | |
| 363 | int npages = 0; | |
| 364 | ||
| 365 | const ColorArg | |
| 366 | COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000 | |
| 367 | ||
| 368 | const IntArg | |
| 369 | INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number | |
| 370 | ||
| 371 | // parser environment, created and deleted by each run of do_file() | |
| 372 | environment *current_env = 0; | |
| 373 | ||
| 374 | #ifdef USE_ENV_STACK | |
| 375 | const size_t | |
| 376 | envp_size = sizeof(environment *); | |
| 377 | #endif // USE_ENV_STACK | |
| 378 | ||
| 379 | ||
| 380 | /********************************************************************** | |
| 381 | function declarations | |
| 382 | **********************************************************************/ | |
| 383 | ||
| 384 | // utility functions | |
| 385 | ColorArg color_from_Df_command(IntArg); | |
| 386 | // transform old color into new | |
| 387 | void delete_current_env(void); // delete global var current_env | |
| 388 | void fatal_command(char); // abort for invalid command | |
| 389 | inline Char get_char(void); // read next character from input stream | |
| 390 | ColorArg get_color_arg(void); // read in argument for new color cmds | |
| 391 | IntArray *get_D_fixed_args(const size_t); | |
| 392 | // read in fixed number of integer | |
| 393 | // arguments | |
| 394 | IntArray *get_D_fixed_args_odd_dummy(const size_t); | |
| 395 | // read in a fixed number of integer | |
| 396 | // arguments plus optional dummy | |
| 397 | IntArray *get_D_variable_args(void); | |
| 398 | // variable, even number of int args | |
| 399 | char *get_extended_arg(void); // argument for `x X' (several lines) | |
| 400 | IntArg get_integer_arg(void); // read in next integer argument | |
| 401 | IntArray *get_possibly_integer_args(); | |
| 402 | // 0 or more integer arguments | |
| 403 | char *get_string_arg(void); // read in next string arg, ended by WS | |
| 404 | inline bool is_space_or_tab(const Char); | |
| 405 | // test on space/tab char | |
| 406 | Char next_arg_begin(void); // skip white space on current line | |
| 407 | Char next_command(void); // go to next command, evt. diff. line | |
| 408 | inline bool odd(const int); // test if integer is odd | |
| 409 | void position_to_end_of_args(const IntArray * const); | |
| 410 | // positioning after drawing | |
| 411 | void remember_filename(const char *); | |
| 412 | // set global current_filename | |
| 413 | void remember_source_filename(const char *); | |
| 414 | // set global current_source_filename | |
| 415 | void send_draw(const Char, const IntArray * const); | |
| 416 | // call pr->draw | |
| 417 | void skip_line(void); // unconditionally skip to next line | |
| 418 | bool skip_line_checked(void); // skip line, false if args are left | |
| 419 | void skip_line_fatal(void); // skip line, fatal if args are left | |
| 420 | void skip_line_warn(void); // skip line, warn if args are left | |
| 421 | void skip_line_D(void); // skip line in D commands | |
| 422 | void skip_line_x(void); // skip line in x commands | |
| 423 | void skip_to_end_of_line(void); // skip to the end of the current line | |
| 424 | inline void unget_char(const Char); | |
| 425 | // restore character onto input | |
| 426 | ||
| 427 | // parser subcommands | |
| 428 | void parse_color_command(color *); | |
| 429 | // color sub(sub)commands m and DF | |
| 430 | void parse_D_command(void); // graphical subcommands | |
| 431 | bool parse_x_command(void); // device controller subcommands | |
| 432 | ||
| 433 | ||
| 434 | /********************************************************************** | |
| 435 | class methods | |
| 436 | **********************************************************************/ | |
| 437 | ||
| 438 | #ifdef USE_ENV_STACK | |
| 439 | EnvStack::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 | ||
| 449 | EnvStack::~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 | |
| 459 | environment * | |
| 460 | EnvStack::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 | |
| 469 | void | |
| 470 | EnvStack::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 | ||
| 498 | IntArray::IntArray(void) | |
| 499 | { | |
| 500 | num_allocated = 4; | |
| 501 | data = new IntArg[num_allocated]; | |
| 502 | num_stored = 0; | |
| 503 | } | |
| 504 | ||
| 505 | IntArray::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 | ||
| 514 | IntArray::~IntArray(void) | |
| 515 | { | |
| 516 | a_delete data; | |
| 517 | } | |
| 518 | ||
| 519 | void | |
| 520 | IntArray::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 | ||
| 534 | StringBuf::StringBuf(void) | |
| 535 | { | |
| 536 | num_stored = 0; | |
| 537 | num_allocated = 128; | |
| 538 | data = new Char[num_allocated]; | |
| 539 | } | |
| 540 | ||
| 541 | StringBuf::~StringBuf(void) | |
| 542 | { | |
| 543 | a_delete data; | |
| 544 | } | |
| 545 | ||
| 546 | void | |
| 547 | StringBuf::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 | ||
| 561 | char * | |
| 562 | StringBuf::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 | ||
| 571 | void | |
| 572 | StringBuf::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 | */ | |
| 592 | ColorArg | |
| 593 | color_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 | */ | |
| 604 | void 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 | */ | |
| 616 | void | |
| 617 | fatal_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 | */ | |
| 628 | inline Char | |
| 629 | get_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 | */ | |
| 640 | ColorArg | |
| 641 | get_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 | */ | |
| 666 | IntArray * | |
| 667 | get_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 | */ | |
| 692 | IntArray * | |
| 693 | get_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 | */ | |
| 724 | IntArray * | |
| 725 | get_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 | */ | |
| 749 | char * | |
| 750 | get_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 | */ | |
| 783 | IntArg | |
| 784 | get_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 | */ | |
| 826 | IntArray * | |
| 827 | get_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 | */ | |
| 900 | char * | |
| 901 | get_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 | */ | |
| 922 | inline bool | |
| 923 | is_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 | */ | |
| 936 | Char | |
| 937 | next_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 | */ | |
| 964 | Char | |
| 965 | next_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 | */ | |
| 994 | inline bool | |
| 995 | odd(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 | */ | |
| 1011 | void | |
| 1012 | position_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 | */ | |
| 1031 | void | |
| 1032 | remember_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 | */ | |
| 1057 | void | |
| 1058 | remember_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 | */ | |
| 1081 | void | |
| 1082 | send_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 | */ | |
| 1096 | void | |
| 1097 | skip_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 | */ | |
| 1119 | bool | |
| 1120 | skip_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 | */ | |
| 1150 | void | |
| 1151 | skip_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 | */ | |
| 1168 | void | |
| 1169 | skip_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 | */ | |
| 1188 | void | |
| 1189 | skip_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 | */ | |
| 1205 | void | |
| 1206 | skip_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 | */ | |
| 1221 | void | |
| 1222 | skip_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 | */ | |
| 1245 | inline void | |
| 1246 | unget_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 | */ | |
| 1267 | void | |
| 1268 | parse_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 | */ | |
| 1317 | void | |
| 1318 | parse_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 | */ | |
| 1450 | bool | |
| 1451 | parse_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 | */ | |
| 1549 | void | |
| 1550 | do_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 | } |