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