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