mirror of
https://github.com/opnsense/src.git
synced 2026-02-22 01:11:30 -05:00
1835 lines
53 KiB
C++
1835 lines
53 KiB
C++
// -*- C++ -*-
|
|
|
|
// <groff_src_dir>/src/libs/libdriver/input.cpp
|
|
|
|
/* Copyright (C) 1989, 1990, 1991, 1992, 2001, 2002, 2003, 2004, 2005
|
|
Free Software Foundation, Inc.
|
|
|
|
Written by James Clark (jjc@jclark.com)
|
|
Major rewrite 2001 by Bernd Warken (bwarken@mayn.de)
|
|
|
|
Last update: 15 Jun 2005
|
|
|
|
This file is part of groff, the GNU roff text processing system.
|
|
|
|
groff is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
groff is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with groff; see the file COPYING. If not, write to the Free
|
|
Software Foundation, 51 Franklin St - Fifth Floor, Boston, MA
|
|
02110-1301, USA.
|
|
*/
|
|
|
|
/* Description
|
|
|
|
This file implements the parser for the intermediate groff output,
|
|
see groff_out(5), and does the printout for the given device.
|
|
|
|
All parsed information is processed within the function do_file().
|
|
A device postprocessor just needs to fill in the methods for the class
|
|
`printer' (or rather a derived class) without having to worry about
|
|
the syntax of the intermediate output format. Consequently, the
|
|
programming of groff postprocessors is similar to the development of
|
|
device drivers.
|
|
|
|
The prototyping for this file is done in driver.h (and error.h).
|
|
*/
|
|
|
|
/* Changes of the 2001 rewrite of this file.
|
|
|
|
The interface to the outside and the handling of the global
|
|
variables was not changed, but internally many necessary changes
|
|
were performed.
|
|
|
|
The main aim for this rewrite is to provide a first step towards
|
|
making groff fully compatible with classical troff without pain.
|
|
|
|
Bugs fixed
|
|
- Unknown subcommands of `D' and `x' are now ignored like in the
|
|
classical case, but a warning is issued. This was also
|
|
implemented for the other commands.
|
|
- A warning is emitted if `x stop' is missing.
|
|
- `DC' and `DE' commands didn't position to the right end after
|
|
drawing (now they do), see discussion below.
|
|
- So far, `x stop' was ignored. Now it terminates the processing
|
|
of the current intermediate output file like the classical troff.
|
|
- The command `c' didn't check correctly on white-space.
|
|
- The environment stack wasn't suitable for the color extensions
|
|
(replaced by a class).
|
|
- The old groff parser could only handle a prologue with the first
|
|
3 lines having a fixed structure, while classical troff specified
|
|
the sequence of the first 3 commands without further
|
|
restrictions. Now the parser is smart about additional
|
|
white space, comments, and empty lines in the prologue.
|
|
- The old parser allowed space characters only as syntactical
|
|
separators, while classical troff had tab characters as well.
|
|
Now any sequence of tabs and/or spaces is a syntactical
|
|
separator between commands and/or arguments.
|
|
- Range checks for numbers implemented.
|
|
|
|
New and improved features
|
|
- The color commands `m' and `DF' are added.
|
|
- The old color command `Df' is now converted and delegated to `DFg'.
|
|
- The command `F' is implemented as `use intended file name'. It
|
|
checks whether its argument agrees with the file name used so far,
|
|
otherwise a warning is issued. Then the new name is remembered
|
|
and used for the following error messages.
|
|
- For the positioning after drawing commands, an alternative, easier
|
|
scheme is provided, but not yet activated; it can be chosen by
|
|
undefining the preprocessor macro STUPID_DRAWING_POSITIONING.
|
|
It extends the rule of the classical troff output language in a
|
|
logical way instead of the rather strange actual positioning.
|
|
For details, see the discussion below.
|
|
- For the `D' commands that only set the environment, the calling of
|
|
pr->send_draw() was removed because this doesn't make sense for
|
|
the `DF' commands; the (changed) environment is sent with the
|
|
next command anyway.
|
|
- Error handling was clearly separated into warnings and fatal.
|
|
- The error behavior on additional arguments for `D' and `x'
|
|
commands with a fixed number of arguments was changed from being
|
|
ignored (former groff) to issue a warning and ignore (now), see
|
|
skip_line_x(). No fatal was chosen because both string and
|
|
integer arguments can occur.
|
|
- The gtroff program issues a trailing dummy integer argument for
|
|
some drawing commands with an odd number of arguments to make the
|
|
number of arguments even, e.g. the DC and Dt commands; this is
|
|
honored now.
|
|
- All D commands with a variable number of args expect an even
|
|
number of trailing integer arguments, so fatal on error was
|
|
implemented.
|
|
- Disable environment stack and the commands `{' and `}' by making
|
|
them conditional on macro USE_ENV_STACK; actually, this is
|
|
undefined by default. There isn't any known application for these
|
|
features.
|
|
|
|
Cosmetics
|
|
- Nested `switch' commands are avoided by using more functions.
|
|
Dangerous 'fall-through's avoided.
|
|
- Commands and functions are sorted alphabetically (where possible).
|
|
- Dynamic arrays/buffers are now implemented as container classes.
|
|
- Some functions had an ugly return structure; this has been
|
|
streamlined by using classes.
|
|
- Use standard C math functions for number handling, so getting rid
|
|
of differences to '0'.
|
|
- The macro `IntArg' has been created for an easier transition
|
|
to guaranteed 32 bits integers (`int' is enough for GNU, while
|
|
ANSI only guarantees `long int' to have a length of 32 bits).
|
|
- The many usages of type `int' are differentiated by using `Char',
|
|
`bool', and `IntArg' where appropriate.
|
|
- To ease the calls of the local utility functions, the parser
|
|
variables `current_file', `npages', and `current_env'
|
|
(formerly env) were made global to the file (formerly they were
|
|
local to the do_file() function)
|
|
- Various comments were added.
|
|
|
|
TODO
|
|
- Get rid of the stupid drawing positioning.
|
|
- Can the `Dt' command be completely handled by setting environment
|
|
within do_file() instead of sending to pr?
|
|
- Integer arguments must be >= 32 bits, use conditional #define.
|
|
- Add scaling facility for classical device independence and
|
|
non-groff devices. Classical troff output had a quasi device
|
|
independence by scaling the intermediate output to the resolution
|
|
of the postprocessor device if different from the one specified
|
|
with `x T', groff have not. So implement full quasi device
|
|
indepedence, including the mapping of the strange classical
|
|
devices to the postprocessor device (seems to be reasonably
|
|
easy).
|
|
- The external, global pointer variables are not optimally handled.
|
|
- The global variables `current_filename',
|
|
`current_source_filename', and `current_lineno' are only used for
|
|
error reporting. So implement a static class `Error'
|
|
(`::' calls).
|
|
- The global `device' is the name used during the formatting
|
|
process; there should be a new variable for the device name used
|
|
during the postprocessing.
|
|
- Implement the B-spline drawing `D~' for all graphical devices.
|
|
- Make `environment' a class with an overflow check for its members
|
|
and a delete method to get rid of delete_current_env().
|
|
- Implement the `EnvStack' to use `new' instead of `malloc'.
|
|
- The class definitions of this document could go into a new file.
|
|
- The comments in this section should go to a `Changelog' or some
|
|
`README' file in this directory.
|
|
*/
|
|
|
|
/*
|
|
Discussion of the positioning by drawing commands
|
|
|
|
There was some confusion about the positioning of the graphical
|
|
pointer at the printout after having executed a `D' command.
|
|
The classical troff manual of Osanna & Kernighan specified,
|
|
|
|
`The position after a graphical object has been drawn is
|
|
at its end; for circles and ellipses, the "end" is at the
|
|
right side.'
|
|
|
|
From this, it follows that
|
|
- all open figures (args, splines, and lines) should position at their
|
|
final point.
|
|
- all circles and ellipses should position at their right-most point
|
|
(as if 2 halves had been drawn).
|
|
- all closed figures apart from circles and ellipses shouldn't change
|
|
the position because they return to their origin.
|
|
- all setting commands should not change position because they do not
|
|
draw any graphical object.
|
|
|
|
In the case of the open figures, this means that the horizontal
|
|
displacement is the sum of all odd arguments and the vertical offset
|
|
the sum of all even arguments, called the alternate arguments sum
|
|
displacement in the following.
|
|
|
|
Unfortunately, groff did not implement this simple rule. The former
|
|
documentation in groff_out(5) differed from the source code, and
|
|
neither of them is compatible with the classical rule.
|
|
|
|
The former groff_out(5) specified to use the alternative arguments
|
|
sum displacement for calculating the drawing positioning of
|
|
non-classical commands, including the `Dt' command (setting-only)
|
|
and closed polygons. Applying this to the new groff color commands
|
|
will lead to disaster. For their arguments can take large values (>
|
|
65000). On low resolution devices, the displacement of such large
|
|
values will corrupt the display or kill the printer. So the
|
|
nonsense specification has come to a natural end anyway.
|
|
|
|
The groff source code, however, had no positioning for the
|
|
setting-only commands (esp. `Dt'), the right-end positioning for
|
|
outlined circles and ellipses, and the alternative argument sum
|
|
displacement for all other commands (including filled circles and
|
|
ellipses).
|
|
|
|
The reason why no one seems to have suffered from this mayhem so
|
|
far is that the graphical objects are usually generated by
|
|
preprocessors like pic that do not depend on the automatic
|
|
positioning. When using the low level `\D' escape sequences or `D'
|
|
output commands, the strange positionings can be circumvented by
|
|
absolute positionings or by tricks like `\Z'.
|
|
|
|
So doing an exorcism on the strange, incompatible displacements might
|
|
not harm any existing documents, but will make the usage of the
|
|
graphical escape sequences and commands natural.
|
|
|
|
That's why the rewrite of this file returned to the reasonable,
|
|
classical specification with its clear end-of-drawing rule that is
|
|
suitable for all cases. But a macro STUPID_DRAWING_POSITIONING is
|
|
provided for testing the funny former behavior.
|
|
|
|
The new rule implies the following behavior.
|
|
- Setting commands (`Dt', `Df', `DF') and polygons (`Dp' and `DP')
|
|
do not change position now.
|
|
- Filled circles and ellipses (`DC' and `DE') position at their
|
|
most right point (outlined ones `Dc' and `De' did this anyway).
|
|
- As before, all open graphical objects position to their final
|
|
drawing point (alternate sum of the command arguments).
|
|
|
|
*/
|
|
|
|
#ifndef STUPID_DRAWING_POSITIONING
|
|
// uncomment next line if all non-classical D commands shall position
|
|
// to the strange alternate sum of args displacement
|
|
#define STUPID_DRAWING_POSITIONING
|
|
#endif
|
|
|
|
// Decide whether the commands `{' and `}' for different environments
|
|
// should be used.
|
|
#undef USE_ENV_STACK
|
|
|
|
#include "driver.h"
|
|
#include "device.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
|
|
|
|
/**********************************************************************
|
|
local types
|
|
**********************************************************************/
|
|
|
|
// integer type used in the fields of struct environment (see printer.h)
|
|
typedef int EnvInt;
|
|
|
|
// integer arguments of groff_out commands, must be >= 32 bits
|
|
typedef int IntArg;
|
|
|
|
// color components of groff_out color commands, must be >= 32 bits
|
|
typedef unsigned int ColorArg;
|
|
|
|
// Array for IntArg values.
|
|
class IntArray {
|
|
size_t num_allocated;
|
|
size_t num_stored;
|
|
IntArg *data;
|
|
public:
|
|
IntArray(void);
|
|
IntArray(const size_t);
|
|
~IntArray(void);
|
|
IntArg operator[](const size_t i) const
|
|
{
|
|
if (i >= num_stored)
|
|
fatal("index out of range");
|
|
return (IntArg) data[i];
|
|
}
|
|
void append(IntArg);
|
|
IntArg *get_data(void) const { return (IntArg *)data; }
|
|
size_t len(void) const { return num_stored; }
|
|
};
|
|
|
|
// Characters read from the input queue.
|
|
class Char {
|
|
int data;
|
|
public:
|
|
Char(void) : data('\0') {}
|
|
Char(const int c) : data(c) {}
|
|
bool operator==(char c) const { return (data == c) ? true : false; }
|
|
bool operator==(int c) const { return (data == c) ? true : false; }
|
|
bool operator==(const Char c) const
|
|
{ return (data == c.data) ? true : false; }
|
|
bool operator!=(char c) const { return !(*this == c); }
|
|
bool operator!=(int c) const { return !(*this == c); }
|
|
bool operator!=(const Char c) const { return !(*this == c); }
|
|
operator int() const { return (int) data; }
|
|
operator unsigned char() const { return (unsigned char) data; }
|
|
operator char() const { return (char) data; }
|
|
};
|
|
|
|
// Buffer for string arguments (Char, not char).
|
|
class StringBuf {
|
|
size_t num_allocated;
|
|
size_t num_stored;
|
|
Char *data; // not terminated by '\0'
|
|
public:
|
|
StringBuf(void); // allocate without storing
|
|
~StringBuf(void);
|
|
void append(const Char); // append character to `data'
|
|
char *make_string(void); // return new copy of `data' with '\0'
|
|
bool is_empty(void) { // true if none stored
|
|
return (num_stored > 0) ? false : true;
|
|
}
|
|
void reset(void); // set `num_stored' to 0
|
|
};
|
|
|
|
#ifdef USE_ENV_STACK
|
|
class EnvStack {
|
|
environment **data;
|
|
size_t num_allocated;
|
|
size_t num_stored;
|
|
public:
|
|
EnvStack(void);
|
|
~EnvStack(void);
|
|
environment *pop(void);
|
|
void push(environment *e);
|
|
};
|
|
#endif // USE_ENV_STACK
|
|
|
|
|
|
/**********************************************************************
|
|
external variables
|
|
**********************************************************************/
|
|
|
|
// exported as extern by error.h (called from driver.h)
|
|
// needed for error messages (see ../libgroff/error.cpp)
|
|
const char *current_filename = 0; // printable name of the current file
|
|
// printable name of current source file
|
|
const char *current_source_filename = 0;
|
|
int current_lineno = 0; // current line number of printout
|
|
|
|
// exported as extern by device.h;
|
|
const char *device = 0; // cancel former init with literal
|
|
|
|
printer *pr;
|
|
|
|
// Note:
|
|
//
|
|
// We rely on an implementation of the `new' operator which aborts
|
|
// gracefully if it can't allocate memory (e.g. from libgroff/new.cpp).
|
|
|
|
|
|
/**********************************************************************
|
|
static local variables
|
|
**********************************************************************/
|
|
|
|
FILE *current_file = 0; // current input stream for parser
|
|
|
|
// npages: number of pages processed so far (including current page),
|
|
// _not_ the page number in the printout (can be set with `p').
|
|
int npages = 0;
|
|
|
|
const ColorArg
|
|
COLORARG_MAX = (ColorArg) 65536U; // == 0xFFFF + 1 == 0x10000
|
|
|
|
const IntArg
|
|
INTARG_MAX = (IntArg) 0x7FFFFFFF; // maximal signed 32 bits number
|
|
|
|
// parser environment, created and deleted by each run of do_file()
|
|
environment *current_env = 0;
|
|
|
|
#ifdef USE_ENV_STACK
|
|
const size_t
|
|
envp_size = sizeof(environment *);
|
|
#endif // USE_ENV_STACK
|
|
|
|
|
|
/**********************************************************************
|
|
function declarations
|
|
**********************************************************************/
|
|
|
|
// utility functions
|
|
ColorArg color_from_Df_command(IntArg);
|
|
// transform old color into new
|
|
void delete_current_env(void); // delete global var current_env
|
|
void fatal_command(char); // abort for invalid command
|
|
inline Char get_char(void); // read next character from input stream
|
|
ColorArg get_color_arg(void); // read in argument for new color cmds
|
|
IntArray *get_D_fixed_args(const size_t);
|
|
// read in fixed number of integer
|
|
// arguments
|
|
IntArray *get_D_fixed_args_odd_dummy(const size_t);
|
|
// read in a fixed number of integer
|
|
// arguments plus optional dummy
|
|
IntArray *get_D_variable_args(void);
|
|
// variable, even number of int args
|
|
char *get_extended_arg(void); // argument for `x X' (several lines)
|
|
IntArg get_integer_arg(void); // read in next integer argument
|
|
IntArray *get_possibly_integer_args();
|
|
// 0 or more integer arguments
|
|
char *get_string_arg(void); // read in next string arg, ended by WS
|
|
inline bool is_space_or_tab(const Char);
|
|
// test on space/tab char
|
|
Char next_arg_begin(void); // skip white space on current line
|
|
Char next_command(void); // go to next command, evt. diff. line
|
|
inline bool odd(const int); // test if integer is odd
|
|
void position_to_end_of_args(const IntArray * const);
|
|
// positioning after drawing
|
|
void remember_filename(const char *);
|
|
// set global current_filename
|
|
void remember_source_filename(const char *);
|
|
// set global current_source_filename
|
|
void send_draw(const Char, const IntArray * const);
|
|
// call pr->draw
|
|
void skip_line(void); // unconditionally skip to next line
|
|
bool skip_line_checked(void); // skip line, false if args are left
|
|
void skip_line_fatal(void); // skip line, fatal if args are left
|
|
void skip_line_warn(void); // skip line, warn if args are left
|
|
void skip_line_D(void); // skip line in D commands
|
|
void skip_line_x(void); // skip line in x commands
|
|
void skip_to_end_of_line(void); // skip to the end of the current line
|
|
inline void unget_char(const Char);
|
|
// restore character onto input
|
|
|
|
// parser subcommands
|
|
void parse_color_command(color *);
|
|
// color sub(sub)commands m and DF
|
|
void parse_D_command(void); // graphical subcommands
|
|
bool parse_x_command(void); // device controller subcommands
|
|
|
|
|
|
/**********************************************************************
|
|
class methods
|
|
**********************************************************************/
|
|
|
|
#ifdef USE_ENV_STACK
|
|
EnvStack::EnvStack(void)
|
|
{
|
|
num_allocated = 4;
|
|
// allocate pointer to array of num_allocated pointers to environment
|
|
data = (environment **)malloc(envp_size * num_allocated);
|
|
if (data == 0)
|
|
fatal("could not allocate environment data");
|
|
num_stored = 0;
|
|
}
|
|
|
|
EnvStack::~EnvStack(void)
|
|
{
|
|
for (size_t i = 0; i < num_stored; i++)
|
|
delete data[i];
|
|
free(data);
|
|
}
|
|
|
|
// return top element from stack and decrease stack pointer
|
|
//
|
|
// the calling function must take care of properly deleting the result
|
|
environment *
|
|
EnvStack::pop(void)
|
|
{
|
|
num_stored--;
|
|
environment *result = data[num_stored];
|
|
data[num_stored] = 0;
|
|
return result;
|
|
}
|
|
|
|
// copy argument and push this onto the stack
|
|
void
|
|
EnvStack::push(environment *e)
|
|
{
|
|
environment *e_copy = new environment;
|
|
if (num_stored >= num_allocated) {
|
|
environment **old_data = data;
|
|
num_allocated *= 2;
|
|
data = (environment **)malloc(envp_size * num_allocated);
|
|
if (data == 0)
|
|
fatal("could not allocate data");
|
|
for (size_t i = 0; i < num_stored; i++)
|
|
data[i] = old_data[i];
|
|
free(old_data);
|
|
}
|
|
e_copy->col = new color;
|
|
e_copy->fill = new color;
|
|
*e_copy->col = *e->col;
|
|
*e_copy->fill = *e->fill;
|
|
e_copy->fontno = e->fontno;
|
|
e_copy->height = e->height;
|
|
e_copy->hpos = e->hpos;
|
|
e_copy->size = e->size;
|
|
e_copy->slant = e->slant;
|
|
e_copy->vpos = e->vpos;
|
|
data[num_stored] = e_copy;
|
|
num_stored++;
|
|
}
|
|
#endif // USE_ENV_STACK
|
|
|
|
IntArray::IntArray(void)
|
|
{
|
|
num_allocated = 4;
|
|
data = new IntArg[num_allocated];
|
|
num_stored = 0;
|
|
}
|
|
|
|
IntArray::IntArray(const size_t n)
|
|
{
|
|
if (n <= 0)
|
|
fatal("number of integers to be allocated must be > 0");
|
|
num_allocated = n;
|
|
data = new IntArg[num_allocated];
|
|
num_stored = 0;
|
|
}
|
|
|
|
IntArray::~IntArray(void)
|
|
{
|
|
a_delete data;
|
|
}
|
|
|
|
void
|
|
IntArray::append(IntArg x)
|
|
{
|
|
if (num_stored >= num_allocated) {
|
|
IntArg *old_data = data;
|
|
num_allocated *= 2;
|
|
data = new IntArg[num_allocated];
|
|
for (size_t i = 0; i < num_stored; i++)
|
|
data[i] = old_data[i];
|
|
a_delete old_data;
|
|
}
|
|
data[num_stored] = x;
|
|
num_stored++;
|
|
}
|
|
|
|
StringBuf::StringBuf(void)
|
|
{
|
|
num_stored = 0;
|
|
num_allocated = 128;
|
|
data = new Char[num_allocated];
|
|
}
|
|
|
|
StringBuf::~StringBuf(void)
|
|
{
|
|
a_delete data;
|
|
}
|
|
|
|
void
|
|
StringBuf::append(const Char c)
|
|
{
|
|
if (num_stored >= num_allocated) {
|
|
Char *old_data = data;
|
|
num_allocated *= 2;
|
|
data = new Char[num_allocated];
|
|
for (size_t i = 0; i < num_stored; i++)
|
|
data[i] = old_data[i];
|
|
a_delete old_data;
|
|
}
|
|
data[num_stored] = c;
|
|
num_stored++;
|
|
}
|
|
|
|
char *
|
|
StringBuf::make_string(void)
|
|
{
|
|
char *result = new char[num_stored + 1];
|
|
for (size_t i = 0; i < num_stored; i++)
|
|
result[i] = (char) data[i];
|
|
result[num_stored] = '\0';
|
|
return result;
|
|
}
|
|
|
|
void
|
|
StringBuf::reset(void)
|
|
{
|
|
num_stored = 0;
|
|
}
|
|
|
|
/**********************************************************************
|
|
utility functions
|
|
**********************************************************************/
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* color_from_Df_command:
|
|
Process the gray shade setting command Df.
|
|
|
|
Transform Df style color into DF style color.
|
|
Df color: 0-1000, 0 is white
|
|
DF color: 0-65536, 0 is black
|
|
|
|
The Df command is obsoleted by command DFg, but kept for
|
|
compatibility.
|
|
*/
|
|
ColorArg
|
|
color_from_Df_command(IntArg Df_gray)
|
|
{
|
|
return ColorArg((1000-Df_gray) * COLORARG_MAX / 1000); // scaling
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* delete_current_env():
|
|
Delete global variable current_env and its pointer members.
|
|
|
|
This should be a class method of environment.
|
|
*/
|
|
void delete_current_env(void)
|
|
{
|
|
delete current_env->col;
|
|
delete current_env->fill;
|
|
delete current_env;
|
|
current_env = 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* fatal_command():
|
|
Emit error message about invalid command and abort.
|
|
*/
|
|
void
|
|
fatal_command(char command)
|
|
{
|
|
fatal("`%1' command invalid before first `p' command", command);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_char():
|
|
Retrieve the next character from the input queue.
|
|
|
|
Return: The retrieved character (incl. EOF), converted to Char.
|
|
*/
|
|
inline Char
|
|
get_char(void)
|
|
{
|
|
return (Char) getc(current_file);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_color_arg():
|
|
Retrieve an argument suitable for the color commands m and DF.
|
|
|
|
Return: The retrieved color argument.
|
|
*/
|
|
ColorArg
|
|
get_color_arg(void)
|
|
{
|
|
IntArg x = get_integer_arg();
|
|
if (x < 0 || x > (IntArg)COLORARG_MAX) {
|
|
error("color component argument out of range");
|
|
x = 0;
|
|
}
|
|
return (ColorArg) x;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_D_fixed_args():
|
|
Get a fixed number of integer arguments for D commands.
|
|
|
|
Fatal if wrong number of arguments.
|
|
Too many arguments on the line raise a warning.
|
|
A line skip is done.
|
|
|
|
number: In-parameter, the number of arguments to be retrieved.
|
|
ignore: In-parameter, ignore next argument -- GNU troff always emits
|
|
pairs of parameters for `D' extensions added by groff.
|
|
Default is `false'.
|
|
|
|
Return: New IntArray containing the arguments.
|
|
*/
|
|
IntArray *
|
|
get_D_fixed_args(const size_t number)
|
|
{
|
|
if (number <= 0)
|
|
fatal("requested number of arguments must be > 0");
|
|
IntArray *args = new IntArray(number);
|
|
for (size_t i = 0; i < number; i++)
|
|
args->append(get_integer_arg());
|
|
skip_line_D();
|
|
return args;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_D_fixed_args_odd_dummy():
|
|
Get a fixed number of integer arguments for D commands and optionally
|
|
ignore a dummy integer argument if the requested number is odd.
|
|
|
|
The gtroff program adds a dummy argument to some commands to get
|
|
an even number of arguments.
|
|
Error if the number of arguments differs from the scheme above.
|
|
A line skip is done.
|
|
|
|
number: In-parameter, the number of arguments to be retrieved.
|
|
|
|
Return: New IntArray containing the arguments.
|
|
*/
|
|
IntArray *
|
|
get_D_fixed_args_odd_dummy(const size_t number)
|
|
{
|
|
if (number <= 0)
|
|
fatal("requested number of arguments must be > 0");
|
|
IntArray *args = new IntArray(number);
|
|
for (size_t i = 0; i < number; i++)
|
|
args->append(get_integer_arg());
|
|
if (odd(number)) {
|
|
IntArray *a = get_possibly_integer_args();
|
|
if (a->len() > 1)
|
|
error("too many arguments");
|
|
delete a;
|
|
}
|
|
skip_line_D();
|
|
return args;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_D_variable_args():
|
|
Get a variable even number of integer arguments for D commands.
|
|
|
|
Get as many integer arguments as possible from the rest of the
|
|
current line.
|
|
- The arguments are separated by an arbitrary sequence of space or
|
|
tab characters.
|
|
- A comment, a newline, or EOF indicates the end of processing.
|
|
- Error on non-digit characters different from these.
|
|
- A final line skip is performed (except for EOF).
|
|
|
|
Return: New IntArray of the retrieved arguments.
|
|
*/
|
|
IntArray *
|
|
get_D_variable_args()
|
|
{
|
|
IntArray *args = get_possibly_integer_args();
|
|
size_t n = args->len();
|
|
if (n <= 0)
|
|
error("no arguments found");
|
|
if (odd(n))
|
|
error("even number of arguments expected");
|
|
skip_line_D();
|
|
return args;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_extended_arg():
|
|
Retrieve extended arg for `x X' command.
|
|
|
|
- Skip leading spaces and tabs, error on EOL or newline.
|
|
- Return everything before the next NL or EOF ('#' is not a comment);
|
|
as long as the following line starts with '+' this is returned
|
|
as well, with the '+' replaced by a newline.
|
|
- Final line skip is always performed.
|
|
|
|
Return: Allocated (new) string of retrieved text argument.
|
|
*/
|
|
char *
|
|
get_extended_arg(void)
|
|
{
|
|
StringBuf buf = StringBuf();
|
|
Char c = next_arg_begin();
|
|
while ((int) c != EOF) {
|
|
if ((int) c == '\n') {
|
|
current_lineno++;
|
|
c = get_char();
|
|
if ((int) c == '+')
|
|
buf.append((Char) '\n');
|
|
else {
|
|
unget_char(c); // first character of next line
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
buf.append(c);
|
|
c = get_char();
|
|
}
|
|
return buf.make_string();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_integer_arg(): Retrieve integer argument.
|
|
|
|
Skip leading spaces and tabs, collect an optional '-' and all
|
|
following decimal digits (at least one) up to the next non-digit,
|
|
which is restored onto the input queue.
|
|
|
|
Fatal error on all other situations.
|
|
|
|
Return: Retrieved integer.
|
|
*/
|
|
IntArg
|
|
get_integer_arg(void)
|
|
{
|
|
StringBuf buf = StringBuf();
|
|
Char c = next_arg_begin();
|
|
if ((int) c == '-') {
|
|
buf.append(c);
|
|
c = get_char();
|
|
}
|
|
if (!isdigit((int) c))
|
|
error("integer argument expected");
|
|
while (isdigit((int) c)) {
|
|
buf.append(c);
|
|
c = get_char();
|
|
}
|
|
// c is not a digit
|
|
unget_char(c);
|
|
char *s = buf.make_string();
|
|
errno = 0;
|
|
long int number = strtol(s, 0, 10);
|
|
if (errno != 0
|
|
|| number > INTARG_MAX || number < -INTARG_MAX) {
|
|
error("integer argument too large");
|
|
number = 0;
|
|
}
|
|
a_delete s;
|
|
return (IntArg) number;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_possibly_integer_args():
|
|
Parse the rest of the input line as a list of integer arguments.
|
|
|
|
Get as many integer arguments as possible from the rest of the
|
|
current line, even none.
|
|
- The arguments are separated by an arbitrary sequence of space or
|
|
tab characters.
|
|
- A comment, a newline, or EOF indicates the end of processing.
|
|
- Error on non-digit characters different from these.
|
|
- No line skip is performed.
|
|
|
|
Return: New IntArray of the retrieved arguments.
|
|
*/
|
|
IntArray *
|
|
get_possibly_integer_args()
|
|
{
|
|
bool done = false;
|
|
StringBuf buf = StringBuf();
|
|
Char c = get_char();
|
|
IntArray *args = new IntArray();
|
|
while (!done) {
|
|
buf.reset();
|
|
while (is_space_or_tab(c))
|
|
c = get_char();
|
|
if (c == '-') {
|
|
Char c1 = get_char();
|
|
if (isdigit((int) c1)) {
|
|
buf.append(c);
|
|
c = c1;
|
|
}
|
|
else
|
|
unget_char(c1);
|
|
}
|
|
while (isdigit((int) c)) {
|
|
buf.append(c);
|
|
c = get_char();
|
|
}
|
|
if (!buf.is_empty()) {
|
|
char *s = buf.make_string();
|
|
errno = 0;
|
|
long int x = strtol(s, 0, 10);
|
|
if (errno
|
|
|| x > INTARG_MAX || x < -INTARG_MAX) {
|
|
error("invalid integer argument, set to 0");
|
|
x = 0;
|
|
}
|
|
args->append((IntArg) x);
|
|
a_delete s;
|
|
}
|
|
// Here, c is not a digit.
|
|
// Terminate on comment, end of line, or end of file, while
|
|
// space or tab indicate continuation; otherwise error.
|
|
switch((int) c) {
|
|
case '#':
|
|
skip_to_end_of_line();
|
|
done = true;
|
|
break;
|
|
case '\n':
|
|
done = true;
|
|
unget_char(c);
|
|
break;
|
|
case EOF:
|
|
done = true;
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
break;
|
|
default:
|
|
error("integer argument expected");
|
|
break;
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* get_string_arg():
|
|
Retrieve string arg.
|
|
|
|
- Skip leading spaces and tabs; error on EOL or newline.
|
|
- Return all following characters before the next space, tab,
|
|
newline, or EOF character (in-word '#' is not a comment character).
|
|
- The terminating space, tab, newline, or EOF character is restored
|
|
onto the input queue, so no line skip.
|
|
|
|
Return: Retrieved string as char *, allocated by 'new'.
|
|
*/
|
|
char *
|
|
get_string_arg(void)
|
|
{
|
|
StringBuf buf = StringBuf();
|
|
Char c = next_arg_begin();
|
|
while (!is_space_or_tab(c)
|
|
&& c != Char('\n') && c != Char(EOF)) {
|
|
buf.append(c);
|
|
c = get_char();
|
|
}
|
|
unget_char(c); // restore white space
|
|
return buf.make_string();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* is_space_or_tab():
|
|
Test a character if it is a space or tab.
|
|
|
|
c: In-parameter, character to be tested.
|
|
|
|
Return: True, if c is a space or tab character, false otherwise.
|
|
*/
|
|
inline bool
|
|
is_space_or_tab(const Char c)
|
|
{
|
|
return (c == Char(' ') || c == Char('\t')) ? true : false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* next_arg_begin():
|
|
Return first character of next argument.
|
|
|
|
Skip space and tab characters; error on newline or EOF.
|
|
|
|
Return: The first character different from these (including '#').
|
|
*/
|
|
Char
|
|
next_arg_begin(void)
|
|
{
|
|
Char c;
|
|
while (1) {
|
|
c = get_char();
|
|
switch ((int) c) {
|
|
case ' ':
|
|
case '\t':
|
|
break;
|
|
case '\n':
|
|
case EOF:
|
|
error("missing argument");
|
|
break;
|
|
default: // first essential character
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* next_command():
|
|
Find the first character of the next command.
|
|
|
|
Skip spaces, tabs, comments (introduced by #), and newlines.
|
|
|
|
Return: The first character different from these (including EOF).
|
|
*/
|
|
Char
|
|
next_command(void)
|
|
{
|
|
Char c;
|
|
while (1) {
|
|
c = get_char();
|
|
switch ((int) c) {
|
|
case ' ':
|
|
case '\t':
|
|
break;
|
|
case '\n':
|
|
current_lineno++;
|
|
break;
|
|
case '#': // comment
|
|
skip_line();
|
|
break;
|
|
default: // EOF or first essential character
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* odd():
|
|
Test whether argument is an odd number.
|
|
|
|
n: In-parameter, the integer to be tested.
|
|
|
|
Return: True if odd, false otherwise.
|
|
*/
|
|
inline bool
|
|
odd(const int n)
|
|
{
|
|
return (n & 1 == 1) ? true : false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* position_to_end_of_args():
|
|
Move graphical pointer to end of drawn figure.
|
|
|
|
This is used by the D commands that draw open geometrical figures.
|
|
The algorithm simply sums up all horizontal displacements (arguments
|
|
with even number) for the horizontal component. Similarly, the
|
|
vertical component is the sum of the odd arguments.
|
|
|
|
args: In-parameter, the arguments of a former drawing command.
|
|
*/
|
|
void
|
|
position_to_end_of_args(const IntArray * const args)
|
|
{
|
|
size_t i;
|
|
const size_t n = args->len();
|
|
for (i = 0; i < n; i += 2)
|
|
current_env->hpos += (*args)[i];
|
|
for (i = 1; i < n; i += 2)
|
|
current_env->vpos += (*args)[i];
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* remember_filename():
|
|
Set global variable current_filename.
|
|
|
|
The actual filename is stored in current_filename. This is used by
|
|
the postprocessors, expecting the name "<standard input>" for stdin.
|
|
|
|
filename: In-out-parameter; is changed to the new value also.
|
|
*/
|
|
void
|
|
remember_filename(const char *filename)
|
|
{
|
|
char *fname;
|
|
if (strcmp(filename, "-") == 0)
|
|
fname = (char *)"<standard input>";
|
|
else
|
|
fname = (char *)filename;
|
|
size_t len = strlen(fname) + 1;
|
|
if (current_filename != 0)
|
|
free((char *)current_filename);
|
|
current_filename = (const char *)malloc(len);
|
|
if (current_filename == 0)
|
|
fatal("can't malloc space for filename");
|
|
strncpy((char *)current_filename, (char *)fname, len);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* remember_source_filename():
|
|
Set global variable current_source_filename.
|
|
|
|
The actual filename is stored in current_filename. This is used by
|
|
the postprocessors, expecting the name "<standard input>" for stdin.
|
|
|
|
filename: In-out-parameter; is changed to the new value also.
|
|
*/
|
|
void
|
|
remember_source_filename(const char *filename)
|
|
{
|
|
char *fname;
|
|
if (strcmp(filename, "-") == 0)
|
|
fname = (char *)"<standard input>";
|
|
else
|
|
fname = (char *)filename;
|
|
size_t len = strlen(fname) + 1;
|
|
if (current_source_filename != 0)
|
|
free((char *)current_source_filename);
|
|
current_source_filename = (const char *)malloc(len);
|
|
if (current_source_filename == 0)
|
|
fatal("can't malloc space for filename");
|
|
strncpy((char *)current_source_filename, (char *)fname, len);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* send_draw():
|
|
Call draw method of printer class.
|
|
|
|
subcmd: Letter of actual D subcommand.
|
|
args: Array of integer arguments of actual D subcommand.
|
|
*/
|
|
void
|
|
send_draw(const Char subcmd, const IntArray * const args)
|
|
{
|
|
EnvInt n = (EnvInt) args->len();
|
|
pr->draw((int) subcmd, (IntArg *)args->get_data(), n, current_env);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* skip_line():
|
|
Go to next line within the input queue.
|
|
|
|
Skip the rest of the current line, including the newline character.
|
|
The global variable current_lineno is adjusted.
|
|
No errors are raised.
|
|
*/
|
|
void
|
|
skip_line(void)
|
|
{
|
|
Char c = get_char();
|
|
while (1) {
|
|
if (c == '\n') {
|
|
current_lineno++;
|
|
break;
|
|
}
|
|
if (c == EOF)
|
|
break;
|
|
c = get_char();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* skip_line_checked ():
|
|
Check that there aren't any arguments left on the rest of the line,
|
|
then skip line.
|
|
|
|
Spaces, tabs, and a comment are allowed before newline or EOF.
|
|
All other characters raise an error.
|
|
*/
|
|
bool
|
|
skip_line_checked(void)
|
|
{
|
|
bool ok = true;
|
|
Char c = get_char();
|
|
while (is_space_or_tab(c))
|
|
c = get_char();
|
|
switch((int) c) {
|
|
case '#': // comment
|
|
skip_line();
|
|
break;
|
|
case '\n':
|
|
current_lineno++;
|
|
break;
|
|
case EOF:
|
|
break;
|
|
default:
|
|
ok = false;
|
|
skip_line();
|
|
break;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* skip_line_fatal ():
|
|
Fatal error if arguments left, otherwise skip line.
|
|
|
|
Spaces, tabs, and a comment are allowed before newline or EOF.
|
|
All other characters trigger the error.
|
|
*/
|
|
void
|
|
skip_line_fatal(void)
|
|
{
|
|
bool ok = skip_line_checked();
|
|
if (!ok) {
|
|
current_lineno--;
|
|
error("too many arguments");
|
|
current_lineno++;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* skip_line_warn ():
|
|
Skip line, but warn if arguments are left on actual line.
|
|
|
|
Spaces, tabs, and a comment are allowed before newline or EOF.
|
|
All other characters raise a warning
|
|
*/
|
|
void
|
|
skip_line_warn(void)
|
|
{
|
|
bool ok = skip_line_checked();
|
|
if (!ok) {
|
|
current_lineno--;
|
|
warning("too many arguments on current line");
|
|
current_lineno++;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* skip_line_D ():
|
|
Skip line in `D' commands.
|
|
|
|
Decide whether in case of an additional argument a fatal error is
|
|
raised (the documented classical behavior), only a warning is
|
|
issued, or the line is just skipped (former groff behavior).
|
|
Actually decided for the warning.
|
|
*/
|
|
void
|
|
skip_line_D(void)
|
|
{
|
|
skip_line_warn();
|
|
// or: skip_line_fatal();
|
|
// or: skip_line();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* skip_line_x ():
|
|
Skip line in `x' commands.
|
|
|
|
Decide whether in case of an additional argument a fatal error is
|
|
raised (the documented classical behavior), only a warning is
|
|
issued, or the line is just skipped (former groff behavior).
|
|
Actually decided for the warning.
|
|
*/
|
|
void
|
|
skip_line_x(void)
|
|
{
|
|
skip_line_warn();
|
|
// or: skip_line_fatal();
|
|
// or: skip_line();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* skip_to_end_of_line():
|
|
Go to the end of the current line.
|
|
|
|
Skip the rest of the current line, excluding the newline character.
|
|
The global variable current_lineno is not changed.
|
|
No errors are raised.
|
|
*/
|
|
void
|
|
skip_to_end_of_line(void)
|
|
{
|
|
Char c = get_char();
|
|
while (1) {
|
|
if (c == '\n') {
|
|
unget_char(c);
|
|
return;
|
|
}
|
|
if (c == EOF)
|
|
return;
|
|
c = get_char();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* unget_char(c):
|
|
Restore character c onto input queue.
|
|
|
|
Write a character back onto the input stream.
|
|
EOF is gracefully handled.
|
|
|
|
c: In-parameter; character to be pushed onto the input queue.
|
|
*/
|
|
inline void
|
|
unget_char(const Char c)
|
|
{
|
|
if (c != EOF) {
|
|
int ch = (int) c;
|
|
if (ungetc(ch, current_file) == EOF)
|
|
fatal("could not unget character");
|
|
}
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
parser subcommands
|
|
**********************************************************************/
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* parse_color_command:
|
|
Process the commands m and DF, but not Df.
|
|
|
|
col: In-out-parameter; the color object to be set, must have
|
|
been initialized before.
|
|
*/
|
|
void
|
|
parse_color_command(color *col)
|
|
{
|
|
ColorArg gray = 0;
|
|
ColorArg red = 0, green = 0, blue = 0;
|
|
ColorArg cyan = 0, magenta = 0, yellow = 0, black = 0;
|
|
Char subcmd = next_arg_begin();
|
|
switch((int) subcmd) {
|
|
case 'c': // DFc or mc: CMY
|
|
cyan = get_color_arg();
|
|
magenta = get_color_arg();
|
|
yellow = get_color_arg();
|
|
col->set_cmy(cyan, magenta, yellow);
|
|
break;
|
|
case 'd': // DFd or md: set default color
|
|
col->set_default();
|
|
break;
|
|
case 'g': // DFg or mg: gray
|
|
gray = get_color_arg();
|
|
col->set_gray(gray);
|
|
break;
|
|
case 'k': // DFk or mk: CMYK
|
|
cyan = get_color_arg();
|
|
magenta = get_color_arg();
|
|
yellow = get_color_arg();
|
|
black = get_color_arg();
|
|
col->set_cmyk(cyan, magenta, yellow, black);
|
|
break;
|
|
case 'r': // DFr or mr: RGB
|
|
red = get_color_arg();
|
|
green = get_color_arg();
|
|
blue = get_color_arg();
|
|
col->set_rgb(red, green, blue);
|
|
break;
|
|
default:
|
|
error("invalid color scheme `%1'", (int) subcmd);
|
|
break;
|
|
} // end of color subcommands
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* parse_D_command():
|
|
Parse the subcommands of graphical command D.
|
|
|
|
This is the part of the do_file() parser that scans the graphical
|
|
subcommands.
|
|
- Error on lacking or wrong arguments.
|
|
- Warning on too many arguments.
|
|
- Line is always skipped.
|
|
*/
|
|
void
|
|
parse_D_command()
|
|
{
|
|
Char subcmd = next_arg_begin();
|
|
switch((int) subcmd) {
|
|
case '~': // D~: draw B-spline
|
|
// actually, this isn't available for some postprocessors
|
|
// fall through
|
|
default: // unknown options are passed to device
|
|
{
|
|
IntArray *args = get_D_variable_args();
|
|
send_draw(subcmd, args);
|
|
position_to_end_of_args(args);
|
|
delete args;
|
|
break;
|
|
}
|
|
case 'a': // Da: draw arc
|
|
{
|
|
IntArray *args = get_D_fixed_args(4);
|
|
send_draw(subcmd, args);
|
|
position_to_end_of_args(args);
|
|
delete args;
|
|
break;
|
|
}
|
|
case 'c': // Dc: draw circle line
|
|
{
|
|
IntArray *args = get_D_fixed_args(1);
|
|
send_draw(subcmd, args);
|
|
// move to right end
|
|
current_env->hpos += (*args)[0];
|
|
delete args;
|
|
break;
|
|
}
|
|
case 'C': // DC: draw solid circle
|
|
{
|
|
IntArray *args = get_D_fixed_args_odd_dummy(1);
|
|
send_draw(subcmd, args);
|
|
// move to right end
|
|
current_env->hpos += (*args)[0];
|
|
delete args;
|
|
break;
|
|
}
|
|
case 'e': // De: draw ellipse line
|
|
case 'E': // DE: draw solid ellipse
|
|
{
|
|
IntArray *args = get_D_fixed_args(2);
|
|
send_draw(subcmd, args);
|
|
// move to right end
|
|
current_env->hpos += (*args)[0];
|
|
delete args;
|
|
break;
|
|
}
|
|
case 'f': // Df: set fill gray; obsoleted by DFg
|
|
{
|
|
IntArg arg = get_integer_arg();
|
|
if ((arg >= 0) && (arg <= 1000)) {
|
|
// convert arg and treat it like DFg
|
|
ColorArg gray = color_from_Df_command(arg);
|
|
current_env->fill->set_gray(gray);
|
|
}
|
|
else {
|
|
// set fill color to the same value as the current outline color
|
|
delete current_env->fill;
|
|
current_env->fill = new color(current_env->col);
|
|
}
|
|
pr->change_fill_color(current_env);
|
|
// skip unused `vertical' component (\D'...' always emits pairs)
|
|
(void) get_integer_arg();
|
|
# ifdef STUPID_DRAWING_POSITIONING
|
|
current_env->hpos += arg;
|
|
# endif
|
|
skip_line_x();
|
|
break;
|
|
}
|
|
case 'F': // DF: set fill color, several formats
|
|
parse_color_command(current_env->fill);
|
|
pr->change_fill_color(current_env);
|
|
// no positioning (setting-only command)
|
|
skip_line_x();
|
|
break;
|
|
case 'l': // Dl: draw line
|
|
{
|
|
IntArray *args = get_D_fixed_args(2);
|
|
send_draw(subcmd, args);
|
|
position_to_end_of_args(args);
|
|
delete args;
|
|
break;
|
|
}
|
|
case 'p': // Dp: draw closed polygon line
|
|
case 'P': // DP: draw solid closed polygon
|
|
{
|
|
IntArray *args = get_D_variable_args();
|
|
send_draw(subcmd, args);
|
|
# ifdef STUPID_DRAWING_POSITIONING
|
|
// final args positioning
|
|
position_to_end_of_args(args);
|
|
# endif
|
|
delete args;
|
|
break;
|
|
}
|
|
case 't': // Dt: set line thickness
|
|
{
|
|
IntArray *args = get_D_fixed_args_odd_dummy(1);
|
|
send_draw(subcmd, args);
|
|
# ifdef STUPID_DRAWING_POSITIONING
|
|
// final args positioning
|
|
position_to_end_of_args(args);
|
|
# endif
|
|
delete args;
|
|
break;
|
|
}
|
|
} // end of D subcommands
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
/* parse_x_command():
|
|
Parse subcommands of the device control command x.
|
|
|
|
This is the part of the do_file() parser that scans the device
|
|
controlling commands.
|
|
- Error on duplicate prologue commands.
|
|
- Error on wrong or lacking arguments.
|
|
- Warning on too many arguments.
|
|
- Line is always skipped.
|
|
|
|
Globals:
|
|
- current_env: is set by many subcommands.
|
|
- npages: page counting variable
|
|
|
|
Return: boolean in the meaning of `stopped'
|
|
- true if parsing should be stopped (`x stop').
|
|
- false if parsing should continue.
|
|
*/
|
|
bool
|
|
parse_x_command(void)
|
|
{
|
|
bool stopped = false;
|
|
char *subcmd_str = get_string_arg();
|
|
char subcmd = subcmd_str[0];
|
|
switch (subcmd) {
|
|
case 'f': // x font: mount font
|
|
{
|
|
IntArg n = get_integer_arg();
|
|
char *name = get_string_arg();
|
|
pr->load_font(n, name);
|
|
a_delete name;
|
|
skip_line_x();
|
|
break;
|
|
}
|
|
case 'F': // x Filename: set filename for errors
|
|
{
|
|
char *str_arg = get_string_arg();
|
|
if (str_arg == 0)
|
|
warning("empty argument for `x F' command");
|
|
else {
|
|
remember_source_filename(str_arg);
|
|
a_delete str_arg;
|
|
}
|
|
break;
|
|
}
|
|
case 'H': // x Height: set character height
|
|
current_env->height = get_integer_arg();
|
|
if (current_env->height == current_env->size)
|
|
current_env->height = 0;
|
|
skip_line_x();
|
|
break;
|
|
case 'i': // x init: initialize device
|
|
error("duplicate `x init' command");
|
|
skip_line_x();
|
|
break;
|
|
case 'p': // x pause: pause device
|
|
skip_line_x();
|
|
break;
|
|
case 'r': // x res: set resolution
|
|
error("duplicate `x res' command");
|
|
skip_line_x();
|
|
break;
|
|
case 's': // x stop: stop device
|
|
stopped = true;
|
|
skip_line_x();
|
|
break;
|
|
case 'S': // x Slant: set slant
|
|
current_env->slant = get_integer_arg();
|
|
skip_line_x();
|
|
break;
|
|
case 't': // x trailer: generate trailer info
|
|
skip_line_x();
|
|
break;
|
|
case 'T': // x Typesetter: set typesetter
|
|
error("duplicate `x T' command");
|
|
skip_line();
|
|
break;
|
|
case 'u': // x underline: from .cu
|
|
{
|
|
char *str_arg = get_string_arg();
|
|
pr->special(str_arg, current_env, 'u');
|
|
a_delete str_arg;
|
|
skip_line_x();
|
|
break;
|
|
}
|
|
case 'X': // x X: send uninterpretedly to device
|
|
{
|
|
char *str_arg = get_extended_arg(); // includes line skip
|
|
if (npages <= 0)
|
|
error("`x X' command invalid before first `p' command");
|
|
else if (str_arg && (strncmp(str_arg, "devtag:",
|
|
strlen("devtag:")) == 0))
|
|
pr->devtag(str_arg, current_env);
|
|
else
|
|
pr->special(str_arg, current_env);
|
|
a_delete str_arg;
|
|
break;
|
|
}
|
|
default: // ignore unknown x commands, but warn
|
|
warning("unknown command `x %1'", subcmd);
|
|
skip_line();
|
|
}
|
|
a_delete subcmd_str;
|
|
return stopped;
|
|
}
|
|
|
|
|
|
/**********************************************************************
|
|
exported part (by driver.h)
|
|
**********************************************************************/
|
|
|
|
////////////////////////////////////////////////////////////////////////
|
|
/* do_file():
|
|
Parse and postprocess groff intermediate output.
|
|
|
|
filename: "-" for standard input, normal file name otherwise
|
|
*/
|
|
void
|
|
do_file(const char *filename)
|
|
{
|
|
Char command;
|
|
bool stopped = false; // terminating condition
|
|
|
|
#ifdef USE_ENV_STACK
|
|
EnvStack env_stack = EnvStack();
|
|
#endif // USE_ENV_STACK
|
|
|
|
// setup of global variables
|
|
npages = 0;
|
|
current_lineno = 1;
|
|
// `pr' is initialized after the prologue.
|
|
// `device' is set by the 1st prologue command.
|
|
|
|
if (filename[0] == '-' && filename[1] == '\0')
|
|
current_file = stdin;
|
|
else {
|
|
errno = 0;
|
|
current_file = fopen(filename, "r");
|
|
if (errno != 0 || current_file == 0) {
|
|
error("can't open file `%1'", filename);
|
|
return;
|
|
}
|
|
}
|
|
remember_filename(filename);
|
|
|
|
if (current_env != 0)
|
|
delete_current_env();
|
|
current_env = new environment;
|
|
current_env->col = new color;
|
|
current_env->fill = new color;
|
|
current_env->fontno = -1;
|
|
current_env->height = 0;
|
|
current_env->hpos = -1;
|
|
current_env->slant = 0;
|
|
current_env->size = 0;
|
|
current_env->vpos = -1;
|
|
|
|
// parsing of prologue (first 3 commands)
|
|
{
|
|
char *str_arg;
|
|
IntArg int_arg;
|
|
|
|
// 1st command `x T'
|
|
command = next_command();
|
|
if ((int) command == EOF)
|
|
return;
|
|
if ((int) command != 'x')
|
|
fatal("the first command must be `x T'");
|
|
str_arg = get_string_arg();
|
|
if (str_arg[0] != 'T')
|
|
fatal("the first command must be `x T'");
|
|
a_delete str_arg;
|
|
char *tmp_dev = get_string_arg();
|
|
if (pr == 0) { // note: `pr' initialized after prologue
|
|
device = tmp_dev;
|
|
if (!font::load_desc())
|
|
fatal("couldn't load DESC file, can't continue");
|
|
}
|
|
else {
|
|
if (device == 0 || strcmp(device, tmp_dev) != 0)
|
|
fatal("all files must use the same device");
|
|
a_delete tmp_dev;
|
|
}
|
|
skip_line_x(); // ignore further arguments
|
|
current_env->size = 10 * font::sizescale;
|
|
|
|
// 2nd command `x res'
|
|
command = next_command();
|
|
if ((int) command != 'x')
|
|
fatal("the second command must be `x res'");
|
|
str_arg = get_string_arg();
|
|
if (str_arg[0] != 'r')
|
|
fatal("the second command must be `x res'");
|
|
a_delete str_arg;
|
|
int_arg = get_integer_arg();
|
|
EnvInt font_res = font::res;
|
|
if (int_arg != font_res)
|
|
fatal("resolution does not match");
|
|
int_arg = get_integer_arg();
|
|
if (int_arg != font::hor)
|
|
fatal("minimum horizontal motion does not match");
|
|
int_arg = get_integer_arg();
|
|
if (int_arg != font::vert)
|
|
fatal("minimum vertical motion does not match");
|
|
skip_line_x(); // ignore further arguments
|
|
|
|
// 3rd command `x init'
|
|
command = next_command();
|
|
if (command != 'x')
|
|
fatal("the third command must be `x init'");
|
|
str_arg = get_string_arg();
|
|
if (str_arg[0] != 'i')
|
|
fatal("the third command must be `x init'");
|
|
a_delete str_arg;
|
|
skip_line_x();
|
|
}
|
|
|
|
// parsing of body
|
|
if (pr == 0)
|
|
pr = make_printer();
|
|
while (!stopped) {
|
|
command = next_command();
|
|
if (command == EOF)
|
|
break;
|
|
// spaces, tabs, comments, and newlines are skipped here
|
|
switch ((int) command) {
|
|
case '#': // #: comment, ignore up to end of line
|
|
skip_line();
|
|
break;
|
|
#ifdef USE_ENV_STACK
|
|
case '{': // {: start a new environment (a copy)
|
|
env_stack.push(current_env);
|
|
break;
|
|
case '}': // }: pop previous env from stack
|
|
delete_current_env();
|
|
current_env = env_stack.pop();
|
|
break;
|
|
#endif // USE_ENV_STACK
|
|
case '0': // ddc: obsolete jump and print command
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
{ // expect 2 digits and a character
|
|
char s[3];
|
|
Char c = next_arg_begin();
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
if (!isdigit((int) c)) {
|
|
error("digit expected");
|
|
c = 0;
|
|
}
|
|
s[0] = (char) command;
|
|
s[1] = (char) c;
|
|
s[2] = '\0';
|
|
errno = 0;
|
|
long int x = strtol(s, 0, 10);
|
|
if (errno != 0)
|
|
error("couldn't convert 2 digits");
|
|
EnvInt hor_pos = (EnvInt) x;
|
|
current_env->hpos += hor_pos;
|
|
c = next_arg_begin();
|
|
if ((int) c == '\n' || (int) c == EOF)
|
|
error("character argument expected");
|
|
else
|
|
pr->set_ascii_char((unsigned char) c, current_env);
|
|
break;
|
|
}
|
|
case 'c': // c: print ascii char without moving
|
|
{
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
Char c = next_arg_begin();
|
|
if (c == '\n' || c == EOF)
|
|
error("missing argument to `c' command");
|
|
else
|
|
pr->set_ascii_char((unsigned char) c, current_env);
|
|
break;
|
|
}
|
|
case 'C': // C: print named special character
|
|
{
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
char *str_arg = get_string_arg();
|
|
pr->set_special_char(str_arg, current_env);
|
|
a_delete str_arg;
|
|
break;
|
|
}
|
|
case 'D': // drawing commands
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
parse_D_command();
|
|
break;
|
|
case 'f': // f: set font to number
|
|
current_env->fontno = get_integer_arg();
|
|
break;
|
|
case 'F': // F: obsolete, replaced by `x F'
|
|
{
|
|
char *str_arg = get_string_arg();
|
|
remember_source_filename(str_arg);
|
|
a_delete str_arg;
|
|
break;
|
|
}
|
|
case 'h': // h: relative horizontal move
|
|
current_env->hpos += (EnvInt) get_integer_arg();
|
|
break;
|
|
case 'H': // H: absolute horizontal positioning
|
|
current_env->hpos = (EnvInt) get_integer_arg();
|
|
break;
|
|
case 'm': // m: glyph color
|
|
parse_color_command(current_env->col);
|
|
pr->change_color(current_env);
|
|
break;
|
|
case 'n': // n: print end of line
|
|
// ignore two arguments (historically)
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
pr->end_of_line();
|
|
(void) get_integer_arg();
|
|
(void) get_integer_arg();
|
|
break;
|
|
case 'N': // N: print char with given int code
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
pr->set_numbered_char(get_integer_arg(), current_env);
|
|
break;
|
|
case 'p': // p: start new page with given number
|
|
if (npages > 0)
|
|
pr->end_page(current_env->vpos);
|
|
npages++; // increment # of processed pages
|
|
pr->begin_page(get_integer_arg());
|
|
current_env->vpos = 0;
|
|
break;
|
|
case 's': // s: set point size
|
|
current_env->size = get_integer_arg();
|
|
if (current_env->height == current_env->size)
|
|
current_env->height = 0;
|
|
break;
|
|
case 't': // t: print a text word
|
|
{
|
|
char c;
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
char *str_arg = get_string_arg();
|
|
size_t i = 0;
|
|
while ((c = str_arg[i++]) != '\0') {
|
|
EnvInt w;
|
|
pr->set_ascii_char((unsigned char) c, current_env, &w);
|
|
current_env->hpos += w;
|
|
}
|
|
a_delete str_arg;
|
|
break;
|
|
}
|
|
case 'u': // u: print spaced word
|
|
{
|
|
char c;
|
|
if (npages <= 0)
|
|
fatal_command(command);
|
|
EnvInt kern = (EnvInt) get_integer_arg();
|
|
char *str_arg = get_string_arg();
|
|
size_t i = 0;
|
|
while ((c = str_arg[i++]) != '\0') {
|
|
EnvInt w;
|
|
pr->set_ascii_char((unsigned char) c, current_env, &w);
|
|
current_env->hpos += w + kern;
|
|
}
|
|
a_delete str_arg;
|
|
break;
|
|
}
|
|
case 'v': // v: relative vertical move
|
|
current_env->vpos += (EnvInt) get_integer_arg();
|
|
break;
|
|
case 'V': // V: absolute vertical positioning
|
|
current_env->vpos = (EnvInt) get_integer_arg();
|
|
break;
|
|
case 'w': // w: inform about paddable space
|
|
break;
|
|
case 'x': // device controlling commands
|
|
stopped = parse_x_command();
|
|
break;
|
|
default:
|
|
warning("unrecognized command `%1'", (unsigned char) command);
|
|
skip_line();
|
|
break;
|
|
} // end of switch
|
|
} // end of while
|
|
|
|
// end of file reached
|
|
if (npages > 0)
|
|
pr->end_page(current_env->vpos);
|
|
delete pr;
|
|
pr = 0;
|
|
fclose(current_file);
|
|
// If `stopped' is not `true' here then there wasn't any `x stop'.
|
|
if (!stopped)
|
|
warning("no final `x stop' command");
|
|
delete_current_env();
|
|
}
|